wucdbm/php-cs-fixers
A collection of custom PHP-CS-Fixer fixers for enforcing additional coding-standard rules in PHP projects, useful for teams that want stricter automated style checks beyond the default PHP-CS-Fixer rule set.
This is a development-time tool, not a runtime dependency. It ships custom PHP-CS-Fixer fixers plus opinionated rule sets, so the fastest path is: install as a dev dependency, point a config file at one of its presets, and run the fixer in dry-run mode before you let it touch the tree.
Minimum steps:
composer require --dev wucdbm/php-cs-fixers:^0.3 friendsofphp/php-cs-fixer
vendor/wucdbm/php-cs-fixers/src before writing any config. The README plus the Fixer/ and rule-set classes are the real documentation — the exact class names for the config builder, the individual fixers, and the preset rule set names are what you need, and they are the one detail you must not guess. Also check the package's own composer.json for the friendsofphp/php-cs-fixer constraint it was built against; match that major version..php_cs.dist for PHP-CS-Fixer 2.x, .php-cs-fixer.dist.php for 3.x. It must require vendor/autoload.php (or be run through vendor/bin/php-cs-fixer, which autoloads) and return a PhpCsFixer\ConfigInterface:
<?php
require __DIR__.'/vendor/autoload.php';
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
// use Wucdbm\PhpCsFixers\... ← take exact FQCNs from src/
$config = new Config();
// 1) custom fixers must be registered or their rule names are "unknown rule"
$config->registerCustomFixers([
// new Wucdbm\PhpCsFixers\Fixer\SomeFixer(),
]);
// 2) start from the package's preset, then override per-rule
$config->setRules([
// '@Wucdbm/...' or the array the preset exposes
]);
$config->setFinder(
Finder::create()
->in(__DIR__)
->exclude(['vendor', 'var', 'storage', 'node_modules'])
->notPath(['bootstrap/cache'])
);
return $config;
vendor/bin/php-cs-fixer fix --dry-run --diff -v
Read the diff like a code review. If a preset rule is too opinionated for the repo, disable that one rule rather than abandoning the preset.First use case: adopt the package's rule set on a new or small project greenfield (or a branch), where the first fix run is a single reviewable commit. Retro-fitting a whole preset onto a mature codebase produces a huge unreviewable diff and usually ends with the config being reverted — stage it rule by rule instead (see below).
Ship a *.dist config in VCS, allow a local override. Commit .php-cs-fixer.dist.php so every developer and CI runs the same preset; add a .php-cs-fixer.php (gitignored) for local experiments. The fixer prefers the non-dist file when both exist, which is exactly the override behaviour you want.
Composer scripts are the interface. Nobody should memorise the binary flags:
"scripts": {
"cs": "php-cs-fixer fix",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:changed": "php-cs-fixer fix --path-mode=intersection"
}
--path-mode=intersection is the key to pre-commit hooks and lint-staged: pass the staged file list as arguments and only those files get fixed, so a commit does not rewrite unrelated code.
CI runs check-only; developers run fix. Two jobs with different contracts:
# CI — must fail, never mutate
composer install --no-interaction
vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no -v
--using-cache=no matters in CI because a stale cache can hide violations (and caching a throwaway workspace buys you nothing).
Compose, don't fork. Start from a baseline the ecosystem already agrees on, then layer the package's preset, then your own overrides last — in setRules later entries win:
$config->setRules(array_merge(
['@PSR12' => true],
$packagePresetRules, // from the package's rule-set class
['declare_strict_types' => false]
));
This is what makes the package worth using over hand-rolling rules: you inherit its opinions and still keep the escape hatch.
Adopt incrementally on legacy code. Do not run the preset over app/ on day one. Order of operations that actually ships:
--rules=@PSR12 over the whole tree, one commit.tests/ and new code).--path-mode=intersection pre-commit so new code is clean immediately while old code is cleaned opportunistically.Share one style across repositories. This is the package's stated purpose. Put the config in a small internal meta-package (or a composer path/VCS repo) that requires wucdbm/php-cs-fixers and returns the Config, then each app's .php-cs-fixer.dist.php is a one-liner return require 'vendor/acme/php-style/config.php';. Version-bump the meta-package to roll out a style change org-wide — one PR per repo, all identical.
Test the config itself. The config is PHP; a broken config file fails at fixer startup with a confusing message. A throwaway CI step (php -l .php-cs-fixer.dist.php) plus the --dry-run job covers this.
.php_cs → .php-cs-fixer.php), moved classes, and added parallel workers that require fixers to be serialisable. Pin both packages explicitly (friendsofphp/php-cs-fixer:^2.16, wucdbm/php-cs-fixers:0.3.*) and stop Dependabot from auto-bumping the fixer across a major. If you must be on 3.x, budget time to verify each custom fixer still instantiates — 0.x semver on this package gives you no guarantee it will.--allow-risky=yes is required for most useful presets. Rules like strict_param, declare_strict_types, no_unreachable_default_argument_value and many opinionated custom fixers are flagged risky and are silently skipped without setRiskyAllowed(true) and the CLI flag. If a rule you enabled "does nothing", check this first.registerCustomFixers() before their rule names appear in setRules, otherwise the run aborts with "unknown rule". If you get that error, it is nearly always a missing registration, not a typo in the rule name.vendor/bin/php-cs-fixer describe <rule> only knows the built-in rule registry. To find the actual names the package emits, run fix --dry-run -vvv (applied rules are printed) or read the rule-set class's getRules() array directly..php_cs.cache / .php-cs-fixer.cache keyed on file hash + config hash. Moving or renaming the config file invalidates it correctly, but changing a rule's behaviour inside a custom fixer does not (the config hash is unchanged). Add the cache to .gitignore, and pass --using-cache=no whenever you are debugging "my rule change had no effect".vendor/bin/php-cs-fixer fix app/Foo.php --rules=YourCustomRule -vvv --diff
--rules on the CLI overrides the config's setRules, so this is the fastest way to isolate whether a custom fixer is even running.exclude from other tools. vendor/ is the classic omission — if you forget to exclude it, fix rewrites dependencies and you get a git diff full of third-party code. Double-check ->exclude(['vendor', ...]) and that bootstrap/cache and storage are out.composer install --no-dev in production must not break anything — it won't, because this is dev-only, but make sure nothing in your build pipeline (e.g. a post-install-cmd that runs cs) references the binary when dev dependencies are absent.vendor/wucdbm/php-cs-fixers/src against the previous version's rule sets after each bump. New or changed default rules land silently in a 0.x minor release and can produce a surprise reformat on the next run.composer why-not wucdbm/php-cs-fixers 0.3.1 names the conflicting constraint (usually friendsofphp/php-cs-fixer or symfony/*), and composer show wucdbm/php-cs-fixers --all shows what is actually reachable from your root constraints.How can I help you explore Laravel packages today?