ergebnis/php-cs-fixer-config
A Composer package providing a configuration factory and version-specific rule set factories for friendsofphp/php-cs-fixer, with rule sets covering PHP 5.3 through 8.3.
Install as a dev dependency — the package never ships in production:
composer require --dev ergebnis/php-cs-fixer-config
Create .php-cs-fixer.php in the project root. The shape is always the same three-step pipeline: pick a rule set → build a config from it → attach your finder and cache.
<?php
declare(strict_types=1);
use Ergebnis\PhpCsFixer\Config;
use PhpCsFixer\Finder;
$ruleSet = Config\RuleSet\Php82::create(); // match your *minimum* supported PHP
$finder = Finder::create()
->in(__DIR__)
->exclude(['.build', 'storage', 'var', 'vendor']);
$config = Config\Factory::fromRuleSet($ruleSet);
$config->setCacheFile(__DIR__ . '/.build/php-cs-fixer/.php-cs-fixer.cache');
$config->setFinder($finder);
return $config;
Then run it:
mkdir -p .build/php-cs-fixer
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose
Add /.build/ to .gitignore.
Where to look first: src/RuleSet/ — one class per PHP version (Php53 … Php85), each extending a shared abstract rule set. That directory is the actual product; the Config\Factory just adapts it to PhpCsFixer\Config. Read Php82.php (or whichever you pick) to see exactly which rules are turned on, and skim CHANGELOG.md for "Enabled the … fixer" / "Disabled the … fixer" entries — those are the changes that will rewrite your code.
First use case: brownfield adoption. Run --dry-run first to see the blast radius, commit the mechanical fix separately, then add the CI gate.
Treat the rule set as a base and layer deltas on top. Rule set instances are immutable builders, so composition is additive and explicit:
$ruleSet = Config\RuleSet\Php82::create()
->withHeader($licenseHeader) // enables HeaderCommentFixer
->withRules(Config\Rules::fromArray([ // merged over the base rules
'mb_str_functions' => false,
'strict_comparison' => false,
'yoda_style' => false,
]));
Disabling strict_comparison and mb_str_functions is the most common two-line edit in a real Laravel app: strict_comparison rewrites == to === (behaviour change on null/0/'' comparisons), and mb_str_functions requires ext-mbstring and changes string semantics.
Custom fixers follow the same builder, with registration and configuration kept separate:
$ruleSet = Config\RuleSet\Php82::create()
->withCustomFixers(Config\Fixers::fromFixers(
new App\Support\Fixer\FinalInternalClassFixer(),
))
->withRules(Config\Rules::fromArray([
'App/final_internal_class' => ['exclude' => ['Tests']],
]));
One config file per context in monorepos. Keep a root .php-cs-fixer.php for app/, tests/, routes/, and a separate one (or a second Finder via setPathMode) for packages with their own PHP floor. Do not try to serve two PHP-version rule sets from one config — the fixer output would differ per path.
Automate at three tiers, all pointing at the same config file:
{
"scripts": {
"coding-standards": [
"mkdir -p .build/php-cs-fixer",
"php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose"
],
"coding-standards:check": [
"mkdir -p .build/php-cs-fixer",
"php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run --show-progress=dots --verbose"
]
}
}
Locally composer coding-standards (writes), in CI composer coding-standards:check (exit code 8 when files need changing, which fails the job as desired). Cache the .build/php-cs-fixer directory in CI keyed on branch + PHP version + composer.lock hash.
Changed-files-only mode for pre-commit hooks. Running the full fixer on every commit gets slow as the repo grows; either scope by path or run the full pass only in CI:
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php \
--path-mode=intersection app/Http/Controllers/FooController.php
Couple rule-set bumps to PHP-version bumps. When you raise composer.json's php constraint from ^8.1 to ^8.2, change Php81 → Php82 in the same PR. That's the whole migration story for this package, and it keeps the fixer emitting syntax your floor can actually parse.
Php84 emits 8.4-only constructs (e.g. property hooks, #[\Deprecated]-adjacent rewrites). If a downstream consumer runs 8.1, you've shipped unparseable code. Pick the lowest version you support.Factory::fromRuleSet() sets riskyAllowed). That's why you don't pass --allow-risky=yes on the CLI — but it also means the tool will make semantic changes. Review the first diff; don't blanket-git add.setCacheFile('.build/php-cs-fixer/.php-cs-fixer.cache') fails or silently skips caching if the directory doesn't exist. Always mkdir -p before invoking, in every script and in CI.Config\RuleSet\Php83 (matching src/RuleSet/), while some changelog entries write Config\Ruleset\Php85. PHP class names are case-insensitive but PSR-4 autoloading on case-sensitive filesystems is not — copy the namespace from the source file, not from the changelog.withHeader() on an established repo touches every file. Land it as a single mechanical commit, then add that SHA to .git-blame-ignore-revs and configure git blame --ignore-revs-file.require in the config file), and memory/--sequential is your first debugging step when a custom fixer behaves oddly..build/php-cs-fixer or run with --using-cache=no.Finder::create()->in(__DIR__) is not enough. Explicitly exclude vendor, storage, bootstrap/cache, var, node_modules, .build. Compiled Blade views, generated migrations, and IDE stubs will otherwise be reformatted (or crash the fixer).ordered_class_elements, method_argument_space, and brace rules, producing alternating diffs. Disable rearrange-on-save and run composer coding-standards instead — or wire the IDE's external formatter to that script.friendsofphp/php-cs-fixer and kubawerlos/php-cs-fixer-custom-fixers can shift output. Pin deliberately (^6.54), bump in a dedicated PR, and let CI's --dry-run show the diff before you merge.withHeader, withRules, withCustomFixers) or a setter on the returned PhpCsFixer\Config (setFinder, setCacheFile, setUsingCache, setPathMode, setLineEnding, setIndent). You should never need to subclass or copy rule arrays out of the package.vendor/bin/php-cs-fixer describe <rule> works for built-ins; for custom ones, grep the fixer's getDefinition() to confirm option names before putting them in Rules::fromArray() — a typo'd option name is silently ignored in some versions and throws in others.How can I help you explore Laravel packages today?