skip to content

PHP CS Fixer

PHP CS Fixer rewrites PHP source to a rule set such as @PER-CS or @PhpCsFixer, configured in .php-cs-fixer.dist.php. Interviewers ask about risky rules, dry-run diffs in CI and fixing vs sniffing.

on this pageshow

explore

questions

6

What does PHP CS Fixer do to your PHP code, and how do its fix and check commands differ?

level: juniorimportance: must knowfreq 50%

answer

  1. every rule is a fixer
  2. rewrites files in place
  3. check equals fix --dry-run
  4. --diff prints a unified diff
  5. exit bit 8 means files need fixing

basics

~20 s

PHP CS Fixer rewrites PHP source so it matches a configured set of rules; fix changes the files, while check (fix --dry-run) changes nothing and only reports which files would change, exiting with bit 8 set.

solid answer

~40 s

PHP CS Fixer is a **fixer-first** tool: every rule, such as `array_syntax` or `no_unused_imports`, is an automatic transformation of the token stream, grouped into rule sets like `@PER-CS`. `vendor/bin/php-cs-fixer fix` applies the configured rules and writes the files back; it exits 0 even when it changed files, because making those changes was the job. `vendor/bin/php-cs-fixer check`, the shorthand for `fix --dry-run` added in 3.34, runs the same rules without writing and lists the files that would change. Adding `--diff` prints the exact changes as a unified diff. In check or dry-run mode the exit code gains bit 8 when some file needs fixing and bit 4 when a file has invalid syntax, so a pipeline can fail on a non-zero result.

code

bash · 4 lines
bash
vendor/bin/php-cs-fixer check --diff    # show what would change, write nothing
echo $?                                 # 8 when at least one file needs fixing
vendor/bin/php-cs-fixer fix             # apply the changes
echo $?                                 # 0, even though files were modified

go deeper

for a junior

Recall that PHP CS Fixer rewrites code to match rules, that fix writes files and check only reports, and that --diff shows the changes.

for a middle

Explain rules versus rule sets, why fix exits 0 after changing files, and how the check exit code is built from bit flags.

for a senior

Show you know the limits: no report-only rules, no type analysis, risky rules off by default, and how to read combined exit codes in automation.

for a principal

Frame automatic formatting as removing a whole category of review comments, and decide which transformations a team is willing to automate.

## What the tool is **PHP CS Fixer** (Composer package `friendsofphp/php-cs-fixer`, binary `vendor/bin/php-cs-fixer`) is a code **formatter and modernizer** for PHP. It reads PHP files, splits them into tokens, applies a list of **rules**, and writes the transformed code back. Two words are easy to mix up: - **rule** (also called a *fixer*): one transformation, such as `array_syntax` (turn `array()` into `[]`), `no_unused_imports` or `single_quote`. Many rules take options, for example `['syntax' => 'short']`. - **rule set**: a named bundle of rules whose name starts with `@`, such as `@PSR12`, `@PER-CS` or `@PhpCsFixer`. The key design point is that **every rule is a fixer**. PHP CS Fixer does not have a class of report-only checks; if a rule notices something, it can also rewrite it. That makes it fast to adopt, and it also means it has nothing to say about problems that need a human decision, such as a badly named method. ## fix: change the files ```bash vendor/bin/php-cs-fixer fix # use .php-cs-fixer(.dist).php vendor/bin/php-cs-fixer fix src/ -v # one path, list the rules applied per file ``` `fix` applies every configured rule to every file the configuration selects and writes the results. After fixing, it lints each changed file, and if the result would not parse it reports an error rather than keeping broken code. `fix` exits with **0** when it ran successfully, **even if it modified files**. Changing files is its purpose, not a failure. ## check: report, never write `check` was introduced in 3.34 as a shorthand for `fix --dry-run`, and accepts the same options. It runs the same rules in memory and prints the files that **would** change, without touching them. - `--diff` adds a unified diff for each file, which shows exactly what the fixer wants to change. - `--format` chooses the output (`txt`, `json`, `junit`, `checkstyle`, `gitlab`, `xml`; in 3.x the default is `txt`). - `--stop-on-violation` stops after the first file that needs fixing. ## Exit codes are bit flags | Bit | Meaning | When it can appear | |---|---|---| | 0 | OK | always | | 1 | general error, or PHP minimum not met | always | | 4 | some files have invalid syntax | `check` / `fix --dry-run` only | | 8 | some files need fixing | `check` / `fix --dry-run` only | | 16 | configuration error of the application | always | | 32 | configuration error of a rule | always | | 64 | exception raised within the application | always | Because they are bit flags, `12` means "some files need fixing **and** some have invalid syntax". A CI job simply fails on any non-zero value from `check`. ## Everyday workflow 1. Locally, run `fix` before committing, or wire it into the editor. 2. Review the resulting diff like any other change, especially the first time a rule set is adopted. 3. In automation, run `check --diff`, so a failing job shows the exact change the author needs to make. ## Understanding what a run did - `-v` lists, per file, the rules that changed it, which is the quickest way to find the rule behind a surprising edit. - `vendor/bin/php-cs-fixer describe single_quote` prints a rule's description, options, before-and-after examples and whether it is risky. - `vendor/bin/php-cs-fixer describe @PER-CS --expand` lists every rule a set contains, including nested sets. - `vendor/bin/php-cs-fixer list-files` prints only the paths that need fixing, which is handy for scripting. Reading a rule's own description before disabling it usually shows whether an option would fit better than switching it off. ## What it does not do - It does not analyse types or logic; a formatter that knows nothing about what `$user` holds cannot find a null dereference. - It does not report problems it cannot fix, because it has no report-only rules. - By default it does not run **risky** rules, those that may change behaviour, unless the configuration or `--allow-risky=yes` allows them.

  • Why does php-cs-fixer fix exit with 0 after it modified files?
    Because rewriting files is the command's purpose. Only `check` or `fix --dry-run` treat a needed change as a finding and set bit 8. That is why automation that must fail on unformatted code runs `check`, not `fix`.
  • What does the exit code 12 from php-cs-fixer check mean?
    The exit code is built from bit flags: 8 means some files need fixing and 4 means some files have invalid syntax, so 12 means both happened in the same run. The syntax errors must be fixed by hand, because the fixer skips files it cannot parse.

saying these in an interview costs you the question

  • php-cs-fixer only reports problems and never edits files
  • check is a separate engine with different rules from fix
  • fix exits non-zero whenever it changed a file
  • PHP CS Fixer can flag badly named methods it cannot fix
  • Risky rules run by default
open as a page

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%

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.

open as a page

How would you use PHP CS Fixer to reformat a whole PHP codebase to PER Coding Style in one commit without disrupting the team?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Pin PHP CS Fixer and a versioned set such as @PER-CS3x0, keep risky rules out, preview with check --diff, apply fix in a commit that contains nothing else, list that commit in .git-blame-ignore-revs, and start enforcing with check.

open as a page

In PHP CS Fixer, what makes a rule risky, and how should a team enable risky rules with --allow-risky or setRiskyAllowed()?

level: seniorimportance: should knowfreq 35%

basics

~10 s

A risky PHP CS Fixer rule is one whose rewrite can change how the code behaves, such as strict_param or declare_strict_types; they run only when the config calls setRiskyAllowed(true) or the command passes --allow-risky=yes.

open as a page

When would you choose PHP CS Fixer over PHP_CodeSniffer, or run both, to enforce a PHP team's coding style?

level: seniorimportance: should knowfreq 35%

basics

~20 s

PHP CS Fixer suits teams that want every rule fixed automatically, including modernising and risky rewrites; PHP_CodeSniffer suits teams that must also report conventions no tool can fix. Running both works when only one of them owns layout.

open as a page

How do PHP CS Fixer's cache file and parallel runner speed up runs, and when can each cause trouble?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

PHP CS Fixer skips files whose content hash matches .php-cs-fixer.cache and reruns everything when the rules or tool version change, and since 3.94 it spreads files across worker processes by default; stale caches and constrained CI machines are the usual sources of trouble.

open as a page