Sharing configurations for PHP-CS-Fixer across projects

You are working on PHP applications and packages and use friendsofphp/php-cs-fixer to enforce coding standards.

What are your options for sharing your configurations for friendsofphp/php-cs-fixer to enforce a consistent coding standard across these projects in your organization?

Configuring PHP-CS-Fixer

You can configure friendsofphp/php-cs-fixer with command line options only or by using a configuration file. I recommend using a configuration file.

The configuration file, typically with the name .php-cs-fixer.php or .php-cs-fixer.dist.php, contains PHP and needs to return a configuration object.

<?php

declare(strict_types=1);

use PhpCsFixer\Config;

$config = new Config();

// ...

return $config;

The configuration object must configure a finder that supplies friendsofphp/php-cs-fixer with a list of files to inspect.

<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
    ->exclude([
        '.build/',
        '.github/',
    ])
    ->in(__DIR__);

$config = new Config();

$config->setFinder($finder)

return $config;

The configuration object can optionally configure zero or more of the 38 rulesets and 250 rules.

If you do not configure any rulesets or rules, friendsofphp/php-cs-fixer currently uses @PSR12 as default ruleset.

If you configure one or more of the available rulesets and rules, friendsofphp/php-cs-fixer will create, configure, and apply the corresponding fixers to the PHP files.

<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
    ->exclude([
        '.build/',
        '.github/',
    ])
    ->in(__DIR__);

$config = new Config();

$config
    ->setFinder($finder)
    ->setRules([
        // one or more of 38 rulesets
        '@PSR12' => true,

        // one or more of 250 rules for fixers
        'blank_line_before_statement' => [
            'statements' => [
                'break',
                'case',
                'continue',
                // ...
        ],

        // ...

        'yoda_style' => true,
    ]);

return $config;

Since the location of PHP files will differ from one project to another, you will want to configure the finder per project and are only concerned with sharing the configuration of rulesets and rules across projects.

Sharing configurations for PHP-CS-Fixer using a GitHub template repository

If you use GitHub and do not feel like setting up a new project from scratch every time, you probably already have a GitHub template repository.

A GitHub repository template, such as ergebnis/php-package-template enables you to set up a new project quickly - which could be an ideal location for maintaining a configuration of rulesets and rules for friendsofphp/php-cs-fixer.

But, you may realize that the PHP applications and packages you build have very different requirements and setups, so maybe you need more than one GitHub repository template.

So which of these GitHub template repositories should now be the source of truth for your configurations of rulesets and rules for friendsofphp/php-cs-fixer?

Also, you may realize that you need to support a wide range of PHP versions, requiring different configurations for friendsofphp/php-cs-fixer. So how can you make sure that you use the correct configuration?

And, even if you had only a single GitHub repository template where you maintain a configuration of rulesets and rules for friendsofphp/php-cs-fixer, how would you propagate the configuration of rulesets and rules to all of your other projects?

Copying the configurations from there and pasting them into a PHP project whenever you pick up work could be an option.

But this option is prone to errors. And how do you know whether the configuration in the central location has changed since you last worked on a specific project?

If we only had a delivery mechanism for distributing configurations!

Sharing configurations for PHP-CS-Fixer using a PHP package

As Paul Redmond rightly points out in his recent article about sharing configurations of rules for squizlabs/php_codesniffer (an alternative to friendsofphp/php-cs-fixer), the ideal solution for sharing and propagating configurations is a PHP package.

A PHP package can not only provide files, such as the XML configuration files for PHPCS in Paul's article, or PHP files or classes returning or creating arrays of rulesets and rules configurations for friendsofphp/php-cs-fixer, but also declare dependencies.

As friendsofphp/php-cs-fixer evolves, contributors and maintainers add, deprecate, and remove fixers and configuration options. You certainly want to ensure that your configuration of rulesets and rules works well with the version of friendsofphp/php-cs-fixer you use in your projects. You can achieve that by declaring the dependency on friendsofphp/php-cs-fixer in the PHP package instead of the consuming application or package.

A PHP package can receive automated updates via Dependabot, Renovate Bot, or similar services.

If you set up the PHP package cleverly, you can use these automated updates to discover added, deprecated, and removed fixers and configuration options with automated tests. Automatic discovery of new fixers and configuration options allows you to use the full potential of fixers that friendsofphp/php-cs-fixer provides and can free up time for more pressing problems than manually fixing coding standard violations.

Last but not least, by using Dependabot, Renovate Bot or similar services, you can propagate the configurations of rulesets and rules from your PHP package, which can now become the single source of truth of configurations for friendsofphp/php-cs-fixer, to all of your other PHP applications and packages.

The simplest solution would be to create one or more PHP files in your package that return the configuration of rulesets and rules.

<?php

declare(strict_types=1);

return [
    // one or more of 38 rulesets
    '@PSR12' => true,

    // one or more of 250 rules for fixers
    'blank_line_before_statement' => [
        'statements' => [
            'break',
            'case',
            'continue',
            // ...
    ],

    // ...

    'yoda_style' => true,
];

Then you can require the PHP file downstream in your configuration for PHP-CS-Fixer.

<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = require __DIR__ . '/vendor/ergebnis/php-cs-fixer-config/config/php82.php';

$finder = Finder::create()
    ->exclude([
        '.build/',
        '.github/',
    ])
    ->in(__DIR__);

$config = new Config();

$config
    ->setFinder($finder)
    ->setRules($rules);

return $config;

But why not extract a factory and concrete classes providing configurations of rulesets and rules that create the configurations for you?

For example, a factory could ensure that you are running friendsofphp/php-cs-fixer on the appropriate version of PHP. A factory could also further prepare the configuration object. Concrete classes for PHP-version-specific rulesets could allow you to override rules for project-specific configurations. A factory and classes also have the advantage that they are autoloadable, and you do not have to deal with exact file locations.

<?php

declare(strict_types=1);

use Ergebnis\PhpCsFixer;

$config = PhpCsFixer\Config\Factory::fromRuleSet(new PhpCsFixer\Config\RuleSet\Php82('', [
    'date_time_immutable' => false,
    'mb_str_functions' => false,
]);

$config->getFinder()
    ->exclude([
        '.build/',
        '.github/',
        'var/',
    ])
    ->in(__DIR__);

return $config;

Concrete examples from the field

In 2015, while supplying services for Refinery29, Inc., I started working on refinery29/php-cs-fixer-config.

As far as I can tell, this was the first PHP package that shared configurations of rulesets and rules for friendsofphp/php-cs-fixer, inspiring many others you can find on Packagist today.

Since 2015, I have successfully applied and refined this approach in ergebnis/php-cs-fixer-config, which I use in all my projects.

For your convenience, I have extracted a GitHub template repository at ergebnis/php-cs-fixer-config-template as a perfect starting point to create, share, and distribute configurations of rulesets and rules for friendsofphp/php-cs-fixer.

Take a look - you will not regret it!

Projects on GitHub

ergebnis/php-cs-fixer-config

Integrate WorkflowCode CoverageType CoverageLatest Stable VersionTotal DownloadsMonthly Downloads

📓 Provides a composer package with a configuration factory and multiple rule sets for friendsofphp/php-cs-fixer.

Find out more at ergebnis/php-cs-fixer-config.

ergebnis/php-cs-fixer-config-template

Integrate WorkflowCode CoverageType Coverage

📓 Provides a GitHub template repository for a composer package with a configuration factory and a custom rule set for friendsofphp/php-cs-fixer.

Find out more at ergebnis/php-cs-fixer-config-template.

ergebnis/php-package-template

Integrate WorkflowCode CoverageType CoverageLatest Stable VersionTotal DownloadsMonthly Downloads

Provides a GitHub repository template for a composer package with GitHub Actions workflows using standard PHP development tools."

Find out more at ergebnis/php-package-template.

Do you find this article helpful?

Do you have feedback?

Do you need help with your PHP project?