skip to content

In PHP CS Fixer, how do you configure .php-cs-fixer.dist.php with a Finder and rule sets such as @PER-CS or @PHP8x5Migration?

level: middleimportance: must knowfreq 45%

answer

  1. the file returns a Config object
  2. Finder chooses the files
  3. @ prefix marks a rule set
  4. rule => false switches one off
  5. local .php-cs-fixer.php takes precedence

basics

~20 s

The committed .php-cs-fixer.dist.php is a PHP file that returns a PhpCsFixer\Config whose Finder selects the files and whose setRules() array enables rule sets like @PER-CS or @PHP8x5Migration plus individual rules, which can be tuned or switched off.

solid answer

~40 s

`.php-cs-fixer.dist.php` is plain PHP that returns a `PhpCsFixer\Config`. `setFinder()` takes a `PhpCsFixer\Finder`, a Symfony Finder that by default keeps only `*.php` files, skips `vendor` and, in 3.x, dot-files; add `->in(__DIR__.'/src')`, `->exclude()` for directories and `->notPath()` for files. `setRules()` takes an array where `'@PER-CS' => true` enables a set, `'strict_param' => true` adds a rule, `'array_syntax' => ['syntax' => 'short']` configures one and `'yoda_style' => false` removes one. `@PER-CS` tracks the latest PER Coding Style (currently `@PER-CS3x0`), `@PhpCsFixer` extends `@PER-CS` and `@Symfony`, and `@PHP8x5Migration` modernises code for PHP 8.5. An uncommitted `.php-cs-fixer.php` takes precedence over the `.dist` file. With no config at all, 3.x applies `@PSR12`.

code

php · 17 lines
php
<?php

declare(strict_types=1);

$finder = (new PhpCsFixer\Finder())
    ->in([__DIR__ . '/src', __DIR__ . '/tests'])
    ->exclude('Fixtures')
    ->notPath('Legacy/bootstrap.php');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PER-CS' => true,
        '@PHP8x5Migration' => true,
        'no_unused_imports' => true,
        'array_syntax' => ['syntax' => 'short'],
    ])
    ->setFinder($finder);

go deeper

for a junior

Recall that the committed config is .php-cs-fixer.dist.php, that it returns a Config with a Finder, and that @ names a rule set.

for a middle

Explain Finder defaults, exclude versus notPath, how rules override sets in setRules(), and how the local config takes precedence.

for a senior

Show you pin versioned sets, match the migration set to the project's minimum PHP, and inspect the effective config with describe.

for a principal

Decide whether a shared base configuration belongs in a package reused across repositories, and who approves changes to it.

## The config file is PHP PHP CS Fixer is configured with a **PHP file that returns a config object**, not with XML or YAML. When you run the tool without `--config`, it looks in the target directory (and the working directory) for: 1. `.php-cs-fixer.php`, a local, usually git-ignored override; 2. `.php-cs-fixer.dist.php`, the committed project configuration. The first one found wins. The documented pattern is for the local file to `require` the `.dist` file and adjust only personal preferences such as output format, so the team's rules and paths stay shared. If no config exists, PHP CS Fixer 3.x applies `@PSR12` to the path you give it (version 4 plans to switch this default). ## Choosing files with Finder `PhpCsFixer\Finder` extends Symfony's Finder component with sensible defaults: - only files matching `*.php`; - the `vendor` directory excluded; - VCS directories ignored, and in 3.x dot-files and dot-directories as well. You then narrow or widen it: | Method | Purpose | |---|---| | `->in([__DIR__.'/src', __DIR__.'/tests'])` | directories to scan | | `->exclude('fixtures')` | skip **directories**, relative to `in()` | | `->notPath('legacy/bootstrap.php')` | skip specific **files**, relative to `in()` | | `->append([__DIR__.'/bin/console'])` | add files the name filter would miss | A path given on the command line **overrides** the Finder by default (`--path-mode=override`); `--path-mode=intersection` keeps only files that are both on the command line and in the Finder. ## Choosing rules with setRules() `setRules()` takes an associative array. Keys starting with `@` are **rule sets**; other keys are individual **rules**: - `'@PER-CS' => true` enables a whole set; - `'no_unused_imports' => true` adds a single rule; - `'array_syntax' => ['syntax' => 'short']` enables a rule with options; - `'yoda_style' => false` turns off a rule that an enabled set would include. Later entries refine earlier ones, so the usual shape is "one or two sets, then a few overrides". ## The rule sets that matter | Set | What it is | |---|---| | `@PSR12` | the PSR-12 style; the 3.x default when there is no config | | `@PER-CS` | PER Coding Style, an alias for the newest revision, currently `@PER-CS3x0` | | `@PER-CS3x0` | PER Coding Style 3.0, pinned | | `@Symfony` | extends `@PER-CS` with the Symfony project's conventions | | `@PhpCsFixer` | the maintainers' opinionated set; extends `@PER-CS` and `@Symfony` | | `@PHP8x5Migration` | modernisations for PHP 8.5; builds on `@PHP8x4Migration` and adds `switch_case_semicolon_to_colon` | | `@auto` | newest PER-CS plus the PHP migration set matching the minimum PHP in `composer.json` | Each set that contains behaviour-changing rules has a `:risky` companion, such as `@PER-CS:risky` or `@PHP8x5Migration:risky`, which only runs when risky rules are allowed. Naming changed during 3.x: the migration sets now use `x` notation (`@PHP8x5Migration`), and the dotted and older names (`@PHP85Migration`, `@PER-CS3.0`) are deprecated and slated for removal in 4.0. Pick the migration set that matches the **lowest PHP version the project supports**, not the version on your laptop. For example `@PHP8x4Migration` includes `new_expression_parentheses`, which rewrites `(new Foo())->bar()` to `new Foo()->bar()`, syntax that only parses on PHP 8.4 or newer. `@auto` and `@autoPHPMigration` read that minimum from `composer.json` for you. ## Other useful settings - `setRiskyAllowed(true)` permits risky rules. - `setCacheFile()` and `setUsingCache()` control the cache (`.php-cs-fixer.cache` by default). - `setParallelConfig()` tunes the parallel runner. - `setIndent()` and `setLineEnding()` set the whitespace style. Two more tools are worth knowing: - `--rules` on the command line **replaces** the configured rules for one run, for example `--rules=@PER-CS,-single_quote` (a leading dash removes a rule), which is useful for trying a rule before committing to it. - A file-level `// @php-cs-fixer-ignore rule_name` comment skips one rule for one file. The feature is marked **experimental** in 3.95, and the tool raises an error when an ignored rule is not part of the configured rules at all, so an ignore cannot silently outlive a rule set change. To see what a configuration expands to, run `vendor/bin/php-cs-fixer describe @PER-CS --expand`, or `describe @` for the configuration currently in use.

  • Why pin @PER-CS3x0 instead of @PER-CS?
    `@PER-CS` is an alias for the latest PER Coding Style revision, and rule sets are not covered by the backward-compatibility promise. A tool upgrade can therefore start reformatting files when a new revision lands. Pinning `@PER-CS3x0` keeps the style fixed until the team decides to move.
  • A developer's .php-cs-fixer.php makes their run ignore the team rules. What went wrong?
    `.php-cs-fixer.php` is found before `.php-cs-fixer.dist.php`, and only the first file is used. If it returns a fresh `Config` instead of requiring the `.dist` file and adjusting it, the shared rules and Finder are lost. The local file should `require` the project config and change only personal settings.

saying these in an interview costs you the question

  • The config is a YAML or XML file
  • Finder exclude() works for single files too
  • @PER-CS is frozen at one PER revision forever
  • A local .php-cs-fixer.php is merged with the .dist file automatically
  • Choose the migration set for the PHP version on your laptop