irstea/php-cs-fixer-config
A reusable PHP-CS-Fixer configuration package that provides an opinionated, consistent coding style for PHP and Laravel projects. Install it to standardize formatting and linting across repositories, with sensible defaults and easy customization.
This package is not on Packagist — it lives on IRSTEA's self-hosted GitLab. Before you can install it, Composer needs to know where to find it, and you need credentials to clone it.
1. Register the VCS repo and install
{
"repositories": [
{ "type": "vcs", "url": "https://gitlab.irstea.fr/pole-is/tools/php-cs-fixer-config.git" }
],
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"irstea/php-cs-fixer-config": "^1.0"
}
}
composer require --dev irstea/php-cs-fixer-config
Auth note: use either the SSH clone URL ([email protected]:...) or a GitLab personal access token / CI job token. Without working credentials, composer install fails hard at the clone step — and it will fail for every developer and every CI runner independently, so provision access for the whole team up front.
2. Where to look first
composer.json of the package — check the php and friendsofphp/php-cs-fixer constraints. That tells you which fixer major version the rules were written against; a mismatch means silently ignored or erroring fixers.Config subclass, or a Rules/Ruleset class with getRules(): array. That is the entire public API. Everything else is a wrapper around it.Finder scope, any project-type presets, and whether custom fixers are included.dev-main.3. First usage case
Create .php-cs-fixer.dist.php at the project root and delegate to the package's config:
<?php
declare(strict_types=1);
$config = new Irstea\PhpCsFixerConfig\Config();
$config->setFinder(
(new PhpCsFixer\Finder())
->in([__DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/config'])
->exclude(['vendor', 'var', 'public/build'])
);
return $config;
If it only exposes a rules array, the equivalent is:
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules(Irstea\PhpCsFixerConfig\Rules::getRules())
->setFinder(/* ... */);
First run on a clean branch:
vendor/bin/php-cs-fixer fix --dry-run --diff -v # see the damage first
vendor/bin/php-cs-fixer fix -v # apply
Commit the reformat as a single isolated commit so reviewers can skip it in git blame.
Composer scripts as the team-facing entry point
Hide the fixer CLI behind scripts so nobody has to remember flags:
{
"scripts": {
"cs": "php-cs-fixer fix",
"cs:check": "php-cs-fixer fix --dry-run --diff --show-progress=none",
"quality": ["@cs:check"]
}
}
composer cs for the local loop; composer cs:check in CI. Never let CI run in fixing mode — CI should only fail on a dirty diff. A pipeline that silently reformats and commits is a source of mystery merge conflicts.
Layered config: package defaults + project overrides
Treat the package as the baseline and add only what is genuinely project-specific:
$config = new Irstea\PhpCsFixerConfig\Config();
$config->setRules(array_merge(
$config->getRules(),
[
'declare_strict_types' => true,
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
]
));
Keep this minimal. If you find yourself overriding more than a handful of rules, the preset isn't the right authority for that repo — say so explicitly rather than shadowing it rule by rule.
Ratcheting rather than a big-bang reformat
On an existing Laravel service, a full reformat churns hundreds of files and collides with every in-flight branch. Safer sequence:
--dry-run --diff against one representative service to size the churn.A lightweight ratchet using git diff --name-only against the base branch and feeding those paths to the fixer is enough for most teams; it avoids fighting a legacy codebase while it's still being modernised.
CI integration
A minimal GitHub Actions / GitLab CI step:
- run: composer install --no-interaction --prefer-dist
- run: composer cs:check
Cache ~/.composer/cache and vendor/ to keep this under a minute. If the fixer is slow on large trees, scope the Finder to src/ and tests/ only — do not point it at vendor/, storage/, bootstrap/cache/ or built assets.
Blame hygiene
After the reformat commit, add its SHA to .git-blame-ignore-revs and configure git:
git config blame.ignoreRevsFile .git-blame-ignore-revs
This is the difference between a usable git blame and a permanently ruined one after a project-wide reformat.
Pre-commit hook (optional but cheap)
A captainhook or plain pre-commit that runs the fixer on staged files catches almost all violations before CI. Keep it scoped to staged files so it's fast; a full-tree run on every commit will get disabled by the team within a week.
Credentials are the biggest operational risk. Every developer machine and every CI runner needs access to gitlab.irstea.fr. If access is revoked, the repo moves, or a branch is rewritten, composer install breaks simultaneously across every project using it. Mitigate by mirroring the repo internally (or vendoring the rules file into an internal standards repo) and pinning by commit SHA. That converts an external availability dependency into an internal one at negligible cost.
Confirm the license before you depend on it. An internal institute tool may well be all-rights-reserved, which could block redistribution or even internal reuse. This is worth resolving before rollout — discovering it mid-migration means rewriting the config afterwards.
Pin, don't float. With no public consumers there is no external test surface, so a dev-main bump could change rules underneath you without a changelog. Use a tag, or dev-main#<sha> if no tags exist.
Risky rules are silently skipped. If the ruleset includes rules marked risky (e.g. declare_strict_types, strict_param, no_unused_imports in some configurations) and you haven't called ->setRiskyAllowed(true), those rules will not apply — and PHP-CS-Fixer will not shout about it in every mode. If the preset expects risky rules, confirm the flag is set.
Custom fixers change the integration profile. If the package ships custom fixer classes rather than pure config, they must be autoloadable in the tooling context. That raises the blast radius of a version bump (a broken fixer is a broken pipeline), so treat the dependency with more care than a plain config file would warrant.
Pint and this package are two style authorities — pick one. Laravel Pint wraps php-cs-fixer internally. Running both means two rulesets fighting over the same files and confusing, contradictory CI failures. If you're Laravel-first and don't have a hard reason to use this package, Pint is the lower-friction default; adopt this one only if the team is already standardised on it.
PHP-CS-Fixer major version drift. Check the friendsofphp/php-cs-fixer constraint in the package against what your project resolves. Rules are added, renamed, and removed between majors, and a mismatch produces "unknown fixer" errors or silently ignored config. Pin the fixer version explicitly in composer.json rather than letting it float.
The .php-cs-fixer.cache file. It caches per-file results keyed by content and PHP version. If rules change but cached results say files are clean, delete it (.php-cs-fixer.cache) and re-run. Stale-cache confusion is a classic false-negative in CI when the cache is committed or persisted across jobs with an incompatible key.
--using-cache=no in CI. Or at least key the cache on the fixer version and the .php-cs-fixer.dist.php content. A cache keyed only on branch is a trap the first time the ruleset bumps.
Extend via the Finder, not the rules. The most common real-world override is which files to check, not how to format them. Scope the Finder tightly (src/, tests/, config/) and exclude anything generated — otherwise you will reformat minified JS in public/build, migrations you don't own, or third-party vendored code.
Debugging a specific rule. If a file is unexpectedly reformatted:
vendor/bin/php-cs-fixer fix path/to/File.php --diff -vvv
-vvv names the fixer that made the change, which is usually the fastest way to identify an over-aggressive rule and decide whether to override it locally or raise it upstream.
Treat upstream as a reference, not a source of truth. The package gives you an opinionated starting point worth mining. The pragmatic posture is: read the rules, vendor the parts you agree with into a repo-local .php-cs-fixer.dist.php, pin the fixer version, and revisit only if the upstream becomes actively maintained and publicly consumable. The config is the interface — swapping the dependency in later should be a one-line change with no behavioural difference.
How can I help you explore Laravel packages today?