Easy Coding Standard puts PHP_CodeSniffer and PHP-CS-Fixer behind one config file
ECS runs PHP-CS-Fixer and PHP_CodeSniffer as a single, parallel fast tool with zero dependencies. Run on PHP 7.2+
At a glance
- What is it?
- ECS is a Composer dev dependency that runs PHP_CodeSniffer and PHP-CS-Fixer rules together in parallel through a single PHP config file, with prepared sets, per-rule configuration and skip lists. It installs on PHP 7.2 through 8.5 and ships with its dependencies bundled.
- Who is it for?
- Adopt ECS if you want one tool instead of two, and start with a withPreparedSets(common: true) run in report mode before letting --fix touch a codebase. Do not adopt it if your project already has a tuned PHP-CS-Fixer or PHPCS configuration you would have to re-express.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 6, 2026, and from our analysis. They are not legal advice.
Star history
- ecsphp/ecs1,630 ★ · +6
| Repository | 2026-10-16 | 2026-11-06 |
|---|---|---|
| ecsphp/ecs | 1.6k | 1.6k |
6 Stars in the last 21 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Two linters, one entry point, one config file
PHP_CodeSniffer and PHP-CS-Fixer solve overlapping problems with different rule sets, and running both means two config files and two invocations. ECS sits on top of both and gives them one configuration file.
composer require symplify/easy-coding-standard --devThe package installs on any project from PHP 7.2 to PHP 8.5 regardless of what else is in composer.json, and the stated reason for that range is zero dependencies: everything the tool needs is bundled rather than resolved against your existing lock file. That matters more than it sounds. A linter that pulls in its own copy of PHP-CS-Fixer cannot conflict with the version your IDE or another tool pins.
The binary is vendor/bin/ecs. Run it with no arguments and it creates an ecs.php config file containing your directories and one starting rule, so the first run is a bootstrap rather than an error. Run it again and you get suggested diffs. Paths given on the command line override the configured ones, and --fix applies the changes.
vendor/bin/ecs src tests
vendor/bin/ecs --fixThe parallel run is on by default rather than being something you configure, which is the other half of why it is faster than running each tool separately.
Prepared sets cover the common ground without a rule list
The configuration style is a fluent call chain on ECSConfig::configure(), and the entry points decide how much control you take.
use Symplify\EasyCodingStandard\Config\ECSConfig;
return ECSConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withPreparedSets(
arrays: true,
spaces: true,
namespaces: true,
docblocks: true,
controlStructures: true,
comments: true,
casing: true,
cleanup: true,
);Each set covers a named topic. `arrays` is array syntax, spacing, trailing commas and indentation. `spaces` is whitespace, operator and type spacing, and blank lines. `namespaces` is import ordering plus unused and needless-alias imports. `docblocks` is phpdoc tags, types, alignment and cleanup. `controlStructures` is control flow, casing, operators and class structure. `comments` is comment style, spacing and empty-comment cleanup. `casing` is native function, type, magic method and literal casing. `cleanup` is dead statements, useless returns and casts, and unused closure imports.
Passing common: true enables all of them at once. There are also standalone presets you can mix in: psr12, perCs, cleanCode for dead code and unused imports, standaloneLine which puts each constructor parameter on its own line, and laravel, which is Laravel Pint's ruleset.
If a set is too coarse, withRules and withConfiguredRule take individual fixers by class name, and withConfiguredRule passes a configuration array, for example setting ArraySyntaxFixer to 'long'. The rule of thumb is that you can start with sets and drop to individual rules only where the output is wrong.
Levels are the migration path for an existing codebase
Dropping a large ruleset on an old codebase produces a diff nobody will review, so ECS offers levels instead.
return ECSConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withSpacesLevel(0)
->withArrayLevel(0)
->withControlStructuresLevel(0)
->withDocblockLevel(0);Each level enables the first N plus one rules from a curated list ordered from safest to most invasive, so level 0 is the formatting that changes nothing semantically and higher numbers reach into structure. You bump the number once the codebase is clean at the current level, which turns a single enormous diff into a series of reviewable ones.
The companion mechanism for the cases levels do not cover is withSkip, which takes a mix of rule classes, rule classes mapped to path arrays, and directory paths or masks.
->withSkip([
ArraySyntaxFixer::class,
ArraySyntaxFixer::class => [__DIR__ . '/src/ValueObject/'],
__DIR__ . '/src/Migrations',
__DIR__ . '/src/*/Legacy',
])Skipping a rule for a specific path is the version that matters most in practice, because it lets you keep a rule enabled everywhere except the legacy corner you have not converted yet.
Two smaller options round this out. withRootFiles() checks every .php file in the project root, which covers ecs.php and rector.php without listing them, and withFileExtensions(['php']) sets what gets scanned.
Cache, parallelism and CI output format
Three settings exist for controlling the internal process, and they exist because of specific CI failures.
The cache one is the notable case. withCache takes a directory and a namespace, and the namespace defaults to getcwd() normalised to a directory separator. The documented reason is GitLab CI, where getcwd() produces a different path each run, which would defeat a path-keyed cache. If you cache ECS results in CI, set the namespace to a fixed string.
withParallel takes timeoutSeconds, maxNumberOfProcess and jobSize, with the defaults given as 120, 32 and 20. Reducing maxNumberOfProcess can tune parallelism for a container with a low CPU quota.
withSpacing sets the indentation style and the line ending, taking Option::INDENTATION_SPACES and PHP_EOL as the documented defaults, so that an editor writing tabs does not fight the tool.
For reporting, there are two formatters. `console` is human-oriented printing in the style of PHP CS Fixer, and `json` is a custom JSON blob for other tooling.
vendor/bin/ecs --output-format=jsonTwo operational commands are worth knowing before you debug anything. `vendor/bin/ecs --clear-cache` resets the cache when results look stale, and `vendor/bin/ecs list-checkers` prints every rule in use, with the same --output-format json option if you want to diff the active rule set between branches.
Where ECS is the wrong tool
The first wrong case is a project with a settled PHP-CS-Fixer configuration. Migrating means expressing your existing rules, skip lists and risky settings in ECS terms, and any option ECS does not surface becomes a rule you can no longer change. If your fixer config is already working and you are not also running PHPCS, there is little to gain.
The second is a rule disagreement between the two engines. ECS runs PHP_CodeSniffer rules and PHP-CS-Fixer rules in the same pass, and two rules that want different formatting produce a --fix that flips a construct between runs rather than converging. The README's migration guidance for other tools is worth reading for this reason, since a tool you are replacing had one opinion per construct and ECS may have two.
The third is PHP version support at the edges. The stated range is 7.2 to 8.5, so a project on 8.6 or a nightly, or one pinned to an old PHP for a legacy product outside that range, is outside what is claimed.
On licensing and maintenance, the package is MIT and the release cadence is very active: version 13.3.7 was published on 2026-10-14, 13.3.5 earlier the same day and 13.3.4 on 2026-10-09, and the last push to the repository was on 2026-10-14. One structural detail to keep in mind is stated in the README's own note: this repository is the deployed package, development happens in the separate ecs-src repository, and issues and pull requests belong there.
Editorial conclusion
Adopt ECS if you want one tool instead of two, and start with a withPreparedSets(common: true) run in report mode before letting --fix touch a codebase. Do not adopt it if your project already has a tuned PHP-CS-Fixer or PHPCS configuration you would have to re-express. Before installing, confirm your PHP version is between 7.2 and 8.5 and read the ecs-src repository, since that is where issues and pull requests belong.
Frequently asked questions
How do I install Easy Coding Standard?
Run composer require symplify/easy-coding-standard --dev. It installs on projects from PHP 7.2 to PHP 8.5 with any existing dependencies, then run vendor/bin/ecs, which creates an ecs.php config file on the first run.
How do I apply the coding standard fixes?
Run vendor/bin/ecs to see suggested diffs first, then vendor/bin/ecs --fix to apply them. Passing paths such as vendor/bin/ecs src tests overrides the paths configured in ecs.php.
What is the difference between prepared sets and levels in ECS?
Prepared sets are curated topic groups such as arrays, spaces, namespaces and cleanup, enabled all at once with common: true or individually. The with*Level methods enable the first N+1 rules from a list ordered from safest to most invasive, so you can raise the level gradually as the codebase gets clean.
Why is my ECS cache not reused in GitLab CI?
The cache namespace defaults to getcwd() normalised, and in GitLab CI getcwd() produces a different path every run. Set a fixed namespace with withCache(directory: ..., namespace: ...). You can also reset a stale local cache with vendor/bin/ecs --clear-cache.
Where should I report ECS bugs?
In the ecs-src repository, not this one. The README notes that this repository is the deployed package and that development happens in ecs-src, where issues and pull requests should go.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ecsphp-ecs)Star history chart for your README
[](https://hysenlabs.com/projects/ecsphp-ecs)Was this useful?