romanzipp/php-cs-fixer-config
A reusable PHP-CS-Fixer configuration from romanzipp for enforcing consistent coding standards across PHP projects. It provides opinionated rules and a simple setup for composer-based workflows, helping teams keep formatting predictable and reduce style-related review noise.
Install as a dev dependency (never a production requirement), and install it alongside the PHP-CS-Fixer major it targets — the 3.0.0 release is built for PHP-CS-Fixer v3.0:
composer require --dev romanzipp/php-cs-fixer-config:^3.0 friendsofphp/php-cs-fixer:^3.0
If your project still runs PHP-CS-Fixer 2.x, stay on the package's 2.x line — do not mix majors (see Gotchas).
Where to look first: open the repo's README.md, then src/ (typically a single Config class or Rules class). Because this is a personal ruleset, the actual class name and whether it returns a PhpCsFixer\Config or a rules array is the thing you need to confirm before wiring it up. Expect one of two shapes:
// .php-cs-fixer.php — shape A: package returns a ready Config
<?php
$finder = PhpCsFixer\Finder::create()->in(__DIR__);
return (new RomanZipp\PhpCsFixerConfig\Config())->setFinder($finder);
// .php-cs-fixer.php — shape B: package returns rules only
<?php
$finder = PhpCsFixer\Finder::create()->in(__DIR__);
return (new PhpCsFixer\Config())
->setRules(RomanZipp\PhpCsFixerConfig\Rules::get())
->setFinder($finder)
->setRiskyAllowed(true);
First use case: don't fix blind. Run dry-run + diff first to see the blast radius of the opinionated ruleset on your codebase:
vendor/bin/php-cs-fixer fix --dry-run --diff
This matters twice over on 3.0.0: the ruleset was refreshed for PHP-CS-Fixer v3.0, and v3 renamed/removed a batch of rules relative to v2, so the diff you get is not the diff the old config would have produced. Verify it once, then stop talking about it.
Wrap, don't vendor-copy. Keep a thin .php-cs-fixer.php that imports the package's rules and layers your project-specific overrides on top. This makes upgrades a composer update rather than a manual diff of a sprawling config:
$rules = RomanZipp\PhpCsFixerConfig\Rules::get();
$rules = array_merge($rules, [
'declare_strict_types' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
]);
return (new PhpCsFixer\Config())
->setRules($rules)
->setFinder($finder)
->setRiskyAllowed(true);
Upgrading across the 2.x → 3.0.0 boundary is its own task. Do it on a dedicated branch, in this order: bump friendsofphp/php-cs-fixer and romanzipp/php-cs-fixer-config together, run --dry-run --diff, then commit the reformat separately from feature work and add the commit to .git-blame-ignore-revs. Expect two classes of diff: renamed/removed rules silently dropped from the set, and genuine style changes from the new defaults.
Two finders, one ruleset. PHP-CS-Fixer lets you split app code from tests (Finder supports ->in() chaining and per-path exclude). Use the package rules for src/, then relax a handful (phpdoc_*, strict_comparison) for tests/ if needed — most personal configs aren't test-aware.
Composer scripts to make it frictionless:
"scripts": {
"cs": "php-cs-fixer fix",
"cs:check": "php-cs-fixer fix --dry-run --diff"
}
CI gate. Add a cs:check job so PRs fail on style drift. This is where shared configs earn their keep — local editors and CI agree because they read the same file. Pin the fixer version in CI too, or a matrix runner will pull a different patch release than your laptop.
Pre-commit / GrumPHP. If the team already has GrumPHP, register the PHP-CS-Fixer task and point it at the same config; the shared ruleset means the commit hook and CI can't disagree.
Editor integration. PhpStorm, VS Code (PHP CS Fixer extension) and other tools all read .php-cs-fixer.php. Wiring it once gives on-save formatting consistent with CI — the single highest-ROI use of this package. Check that the editor extension is on a 3.x-capable fixer binary after the upgrade.
Scope discipline. Start by running the fixer on src/ on a dedicated branch, commit the reformatting separately from feature work, and add it to .git-blame-ignore-revs. Large auto-format commits are the #1 way these configs get reverted in anger.
3.0.0 is a breaking release, not a patch. The only thing the notes say is "updates for PHP-CS-Fixer v3.0", but that is a major version bump on both sides: the package's rules array moved to v3 rule names, and it is no longer usable with a 2.x fixer. Existing projects should treat this as a migration (^2.0 → ^3.0 with a fixer minor/major jump), not a routine composer update.
It's still a personal config. A 2021-era ruleset, a handful of stars, no dependents. 3.0.0 signals it isn't abandoned, but it is still one developer's opinions, not a community standard. Pin the exact constraint in composer.json and keep the override layer thin enough that forking or inlining the rules array is a ten-minute job if upstream lags the next fixer major.
Version coupling is the real hazard — and now it's a hard floor. A rules package written against PHP-CS-Fixer 2.x will throw (Unknown rule, Unknown option, or a missing Config argument) against 3.x, and vice versa. Control it explicitly:
"require-dev": {
"romanzipp/php-cs-fixer-config": "^3.0",
"friendsofphp/php-cs-fixer": "^3.0"
}
Match the fixer version the package was tested against, and upgrade them together — never one without the other.
Risky rules fail silently. If the pack is shape B and you forget ->setRiskyAllowed(true), risky rules in the array are just skipped — no error, no warning. Always run with -v and check the header shows the rules you expect. This is especially sneaky right after a major upgrade, where you can't tell a dropped risky rule from a renamed one.
Cache will lie to you. PHP-CS-Fixer caches on file mtime + hash. After editing the config itself it will re-run, but after a git-driven file swap (branch switch, rebase during the upgrade) it can miss changes. When diagnosing "why didn't it fix that," rerun with --using-cache=no (or delete .php-cs-fixer.cache). Clear it once immediately after the 3.0.0 upgrade so the first run reflects the new ruleset.
Finder pitfalls. Finder::create()->in(__DIR__) picks up vendor/, storage/, node_modules/ and legacy directories. Exclude them explicitly — personal configs rarely ship setFinder, so the finder is your responsibility.
Extending without forking. The clean extension points are: (1) merge your overrides over the returned rules array, (2) subclass the package's Config class if it's designed for it, (3) point a second config file at a different rules subset for generated code. Avoid editing inside vendor/ — it's invisible to code review and dies on composer install.
Debugging a surprising fix. Use --diff (shows the change) and --rules=<rule> (isolate a single rule) to confirm which rule is responsible. When two rules fight (e.g. single_line_* rules, or ordered_imports vs fully_qualified_strict_types), resolve by disabling one in your override layer rather than letting the package's priority decide. On 3.x, also check vendor/bin/php-cs-fixer describe <rule> — it prints the rule's options and whether it's risky, which is the fastest way to confirm a rule survived the rename.
Don't let it reformat generated or third-party code. Exclude generated models, migrations you don't own, or snapshot fixtures — otherwise the first CI run after the upgrade produces a thousand-line diff nobody wants to review.
How can I help you explore Laravel packages today?