7 tips to Improve your ECS Config

Easy Coding Standard got a new release last week with a couple of cool features and new dead docblock rules.

But the tool is only as powerful as its master. With agents, even more so. I've noticed people use ECS in an overly complex way, which is not needed... and definitely not fun. If it's longer than 15 lines, you're probably doing it wrong.

That's why I want to go back to basics and share my personal tips on what the ecs.php config should look like. Feed this to your agent, or learn it yourself.

1. Use Sets over Individual Rules

Some configs look like:

// ...
use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withRules([
        PhpUnitAssertNewNamesFixer::class,
        PhpUnitConstructFixer::class,
        PhpUnitDataProviderNameFixer::class,
        PhpUnitDataProviderReturnTypeFixer::class,
        PhpUnitDataProviderStaticFixer::class,
        PhpUnitDedicateAssertFixer::class,
        PhpUnitDedicateAssertInternalTypeFixer::class,
        // ...
    ]);


If you list 5+ rules from a certain shared group, there is a high chance you can use a set instead. With 2 options: ->withPreparedSets() or ->withPhpCsFixerSets(). For example, the above can be simplified to:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPhpCsFixerSets(
        phpunit75MigrationRisky: true,
        phpunit84MigrationRisky: true,
        phpunit100MigrationRisky: true,
    );

👍


2. Use ->withRootFiles() over file listing

Every project has a couple of *.php files outside of /src and /tests directories. For example, ecs.php, rector.php, setup.php, etc. Those files are often forgotten in the ECS config, which leads to false positive errors.

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withRootFiles([
        __DIR__ . '/ecs.php',
        __DIR__ . '/rector.php',
        __DIR__ . '/setup.php',
    ]);


Forget listing these files explicitly and use a single line instead:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withRootFiles();

👍


3. Use levels over apply all-sets-once

This year, we introduced levels to ECS. You may know them from Rector 2.0, as a tool to reach any upgrade in a step-by-step way. Very useful for agents that can create a 20-PR merge train, each with its own rule.

If you're introducing ECS to a new project, or still don't have a perfect CS score, avoid using sets at the start:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPreparedSets(
        arrays: true,
        docblocks: true,
        comments: true,
        controlStructures: true,
        spaces: true,
    );


This will only lead to huge changes that nobody understands or is willing to review and approve. Use levels instead, to introduce rules step by step:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withDocblockLevel(0)
    ->withArrayLevel(0)
    ->withSpacesLevel(0)
    ->withControlStructuresLevel(0);

👍


This way, you can create per-rule PRs that are a no-brainer to merge. Let the agent handle the job and let a human review and approve. Keep the value rising till you reach the maximum level in each group (there will be a warning about reaching it).

Also, this feature works perfectly to level up a coding standard on a PHP 7.2 project. ECS requires only PHP 7.2, so it's a perfect tool to handle coding standard on any legacy project, while you still have changes under control.


4. Use ->withPreparedSets() over full levels

Soon, you'll reach the full potential. That's what levels are for - to slowly climb a huge mountain. But don't stop there once you're on top:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withDocblockLevel(99)
    ->withArrayLevel(99)
    ->withSpacesLevel(99)
    ->withControlStructuresLevel(99);


Instead, use prepared sets that include all the rules in a single line:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPreparedSets(
        docblocks: true,
        arrays: true,
        spaces: true,
        controlStructures: true
    );

👍


This is easier to read, understand and maintain.


5. Use "common" over split sets

Before we had sets, we had to use prepared sets to apply common rules outside PSR-12 set:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPreparedSets(
        arrays: true,
        docblocks: true,
        comments: true,
        controlStructures: true,
        spaces: true,
        namespaces: true
    );

The idea was to use a set, then create a PR, then add another set etc. Avoid using such a method with all these named parameters.


Once you reach all of these sets, flip to a single one that includes them all.

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPreparedSets(common: true);

👍


6. Use config over closure

Last but not least, some people still use a closure to configure ECS:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return static function (ECSConfig $ecsConfig): void {
    $ecsConfig->withPreparedSets(common: true);
};


This is a legacy leftover, from when ECS ran on Symfony (many, many years ago). This was the only way to configure the Symfony container with our sniffs and fixers.

Now, we have a much simpler way to configure ECS, without the need for a closure:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return (ECSConfig::configure())
    ->withPreparedSets(common: true);

👍


7. The Final setup

In the end, this is the final setup I strive for in every project. Once we get through all the levels and all the prepared sets, and apply the coding standard everywhere (yes, including tests), we can shrink our config to this:

use Symplify\EasyCodingStandard\Config\ECSConfig;

return ECSConfig::configure()
    ->withPreparedSets(symplify: true, common: true, psr12: true)
    ->withPaths([__DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/tests'])
    ->withRootFiles();

👍


Keep it simple, keep it easy. Nothing else is needed.


Happy coding!