In PHP_CodeSniffer, what is the difference between the phpcs and phpcbf commands, and which violations can phpcbf fix?
answer
- one reports, one rewrites
- both read the same ruleset
- fixable only if the sniff ships a fix
- [x] marker in the full report
- 4.0: exit 0 when nothing remains
basics
~20 sphpcs scans PHP files against a coding standard and reports violations without changing anything; phpcbf reads the same ruleset and rewrites files, fixing only the violations whose sniffs implement an automatic fix and leaving the rest for a human.
solid answer
~40 s`phpcs` is the reporter: it tokenizes each file, runs the sniffs of the chosen standard and prints errors and warnings, never touching the code. `phpcbf` (the beautifier and fixer) loads the **same** ruleset, but when a sniff raises a *fixable* violation it also applies that sniff's fix, re-running the sniffs pass after pass until the file stops changing. Only violations raised with `addFixableError()`/`addFixableWarning()` can be fixed; the full report marks them with `[x]` and ends with "PHPCBF CAN FIX THE n MARKED SNIFF VIOLATIONS AUTOMATICALLY". Whitespace, indentation and brace placement are usually fixable; a long line, a bad name or a missing doc comment is not. In PHP_CodeSniffer 4.0, `phpcbf` exits 0 when nothing is left, and 2 when unfixable violations remain.
code
bash · 5 linesvendor/bin/phpcs src/ # report only, files untouched
vendor/bin/phpcs -s src/ # also print each message's sniff code
vendor/bin/phpcs --report=diff src/ # preview what phpcbf would change
vendor/bin/phpcbf src/ # apply every automatic fix
echo $? # 4.0: 0 = clean, 2 = manual fixes remaingo deeper
Recall that phpcs only reports and phpcbf rewrites files, and that phpcbf can fix only the violations marked [x] in the report.
Explain that fixability is decided per sniff and error code, that phpcbf loops until the file stabilises, and how --report=diff previews its changes.
Show you can read the 4.0 exit codes, recognise a fixer conflict from the failed-to-fix bit, and resolve it by reconfiguring one of the sniffs.
Frame the split as a cost decision: mechanical fixes should never reach human review, while decisions like naming stay reported, not automated.
## Two commands, one engine **PHP_CodeSniffer** ships two executables in `vendor/bin/` that share one engine and one ruleset: - **`phpcs`** (the *sniffer*) reads PHP files, splits them into tokens, runs every **sniff** in the selected coding standard over those tokens and prints a report of **errors** and **warnings**. It is read-only: your files are never modified. - **`phpcbf`** (the *beautifier and fixer*) loads exactly the same standard or `phpcs.xml.dist` ruleset, runs the same sniffs, and when a sniff reports a violation it knows how to repair, it applies the repair and writes the file back. Because both commands use the same configuration, a file that `phpcbf` has cleaned up should produce no fixable violations when `phpcs` checks it next. A **sniff** is a small class that listens for certain tokens (a `function` keyword, an opening brace, a comment) and inspects the code around them. ## What makes a violation fixable A violation is fixable only when the sniff that raised it reports it with `addFixableError()` or `addFixableWarning()` and then performs the change through the fixer object. That is a decision made per sniff and per error code, not a global property: | Typically fixable | Typically not fixable | |---|---| | indentation (`Generic.WhiteSpace.ScopeIndent`) | line longer than the limit (`Generic.Files.LineLength.TooLong`) | | brace and blank-line placement | a class or method name in the wrong case | | lower-case keywords and constants | a missing doc comment | | spacing after casts or around operators | a forbidden function call with no safe replacement | The rule of thumb: if repairing the code needs a **decision** (what to name something, where to break a long line, what the comment should say), the sniff only reports it. If the repair is **mechanical** and cannot change behaviour, the sniff usually ships a fixer. In the default `full` report, each message on a file that has fixable violations carries a `[x]` (fixable) or `[ ]` (manual) marker, and the file's report ends with a line such as `PHPCBF CAN FIX THE 7 MARKED SNIFF VIOLATIONS AUTOMATICALLY`. ## How phpcbf applies fixes `phpcbf` does not run the sniffs once. It works in **passes**: 1. Tokenize the file and run all sniffs; every fixable violation records its change. 2. Apply the recorded changes to produce new file contents. 3. Re-tokenize the new contents and run the sniffs again, because one fix can create or remove another violation. 4. Stop when a pass makes no more changes, or after 50 passes. If two sniffs keep undoing each other's work (one adds a space, another removes it), the file never stabilises. That is a **fixer conflict**, and `phpcbf` reports that it failed to fix the file. To preview what `phpcbf` would change without writing anything, run `phpcs --report=diff`, which prints a unified diff of the fixes. ## Exit codes in 4.0 PHP_CodeSniffer 4.0 redefined the exit codes so both tools return 0 when there is nothing left to do: | Code | `phpcs` meaning | `phpcbf` meaning | |---|---|---| | 0 | no violations found | nothing left: all fixable violations fixed, none other remain | | 1 | only fixable violations found | set together with the 4 bit when fixable violations could not be fixed | | 2 | only non-fixable violations found | non-fixable violations remain after fixing | | 3 | both kinds found | both kinds remain | | 4 bit | not used | a file failed to fix, usually a fixer conflict (so 5 or 7) | | 16 | processing problem such as an invalid flag or a broken ruleset | same | In the 3.x series `phpcbf` returned 1 after it successfully fixed everything, which scripts often misread as a failure; 4.0 removed that trap. ## Practical takeaways - Run `phpcbf` locally to clear the mechanical noise, then read what `phpcs` still reports. - Add `-s` to `phpcs` to print each message's **sniff code**, which you need to exclude or suppress a rule. - Never assume `phpcbf` makes code compliant; it only closes the fixable subset, and it never analyses types or logic.
- Why can phpcbf report that it failed to fix a file?`phpcbf` re-runs the sniffs after each round of fixes until the file stops changing, up to 50 passes. When two sniffs disagree, for example one inserts a blank line and another removes it, the file never converges. That fixer conflict sets the failed-to-fix bit (4) in the 4.0 exit code, so you see 5 or 7. The cure is to exclude or reconfigure one of the conflicting sniffs in the ruleset.
- How do you see what phpcbf would change before letting it write files?Run `phpcs --report=diff` on the same paths. It runs the fixers in memory and prints a unified diff of the result instead of writing the files, so you can review the proposed changes, or pipe them into a patch, before running `phpcbf` itself.
saying these in an interview costs you the question
- phpcbf fixes every violation that phpcs reports
- phpcs rewrites files when it finds violations
- phpcbf needs its own separate configuration file
- Any non-zero phpcbf exit code means the tool crashed
- phpcbf also fixes type errors and logic bugs