In PHP_CodeSniffer, how do phpcs:ignore, phpcs:disable and phpcs:enable comments differ, and why should they name a sniff code?
answer
- line vs region vs file
- own line means the next line
- Standard.Category.Sniff.Code from -s
- text after -- is a note
- @codingStandardsIgnore removed in 4.0
basics
~10 sphpcs:ignore suppresses one line, phpcs:disable suppresses everything until phpcs:enable or the end of the file, and phpcs:ignoreFile skips the file; naming a sniff code limits the suppression so other violations still surface.
solid answer
~40 s`// phpcs:ignore` on its own line silences the next line; placed at the end of a code line it silences that line. `// phpcs:disable` starts a region that lasts until `// phpcs:enable`, or to the end of the file if you forget it, and `// phpcs:ignoreFile` skips the whole file. Each can take a comma-separated list of sniff codes, such as `Generic.PHP.ForbiddenFunctions.Found`, which you read from `phpcs -s`; a partial code like `Generic.PHP.ForbiddenFunctions` covers every message of that sniff. A bare annotation silences **all** sniffs, so a new, unrelated violation on that line goes unnoticed. Text after ` -- ` is a free-form reason. PHP_CodeSniffer 4.0 removed the old `@codingStandardsIgnoreLine`/`Start`/`End` forms, and `--ignore-annotations` makes phpcs disregard every annotation for an audit.
code
php · 12 lines<?php
declare(strict_types=1);
// phpcs:disable Generic.Files.LineLength.TooLong -- generated lookup table
const COUNTRY_NAMES = ['AD' => 'Andorra', 'AE' => 'United Arab Emirates', 'AF' => 'Afghanistan', 'AG' => 'Antigua and Barbuda', 'AI' => 'Anguilla'];
// phpcs:enable Generic.Files.LineLength.TooLong
function debugDump(array $state): void
{
var_dump($state); // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- CLI-only helper
}go deeper
Recall the difference between ignore for one line and disable/enable for a region, and that phpcs -s shows the code to name.
Explain how a comment's position changes which line phpcs:ignore covers, how partial codes widen scope, and what the -- note is for.
Show the judgement to prefer ruleset exclude-patterns for whole file groups, audit suppressions with --ignore-annotations, and migrate removed 3.x syntax.
Treat suppressions as tracked exceptions: agree when an annotation is acceptable and how the count of them is reviewed over time.
## What an ignore annotation is PHP_CodeSniffer reads specially formatted comments in the scanned code, called **annotations**, that suppress violations. They are the in-code counterpart of excluding a rule in `phpcs.xml.dist`: an exclusion applies project-wide, an annotation applies to specific lines. Annotations start with `phpcs:` (or `@phpcs:` inside a docblock) and work in `//`, `#` and `/* */` comments. ## The four annotations | Annotation | Scope | Typical use | |---|---|---| | `phpcs:ignore` | one line | a single deliberate exception | | `phpcs:disable` | from here until `phpcs:enable` | a generated block or a legacy region | | `phpcs:enable` | ends a disabled region | closes `phpcs:disable`, fully or for listed codes | | `phpcs:ignoreFile` | the whole file | a vendored or generated file | A fifth, `phpcs:set`, changes a sniff property for the rest of the file; it configures rather than suppresses. The position of `phpcs:ignore` matters: - on a line **by itself**, it suppresses the **next** line; - at the **end** of a line of code, it suppresses **that** line. `phpcs:disable` without a matching `phpcs:enable` silently runs to the end of the file, which is a common way whole files escape the standard. ## Sniff codes: scoping the suppression Every message has a **sniff code** of the form `Standard.Category.Sniff.ErrorCode`, for example `Generic.PHP.ForbiddenFunctions.Found` or `Generic.Files.LineLength.TooLong`. The first three parts name the sniff class (`Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php`); the last part is chosen by the sniff for each distinct message. Run `phpcs -s` to print the code next to every message. Annotations accept a comma-separated list of codes, at any level of the hierarchy: 1. `Generic.PHP.ForbiddenFunctions.Found` silences exactly one message; 2. `Generic.PHP.ForbiddenFunctions` silences every message of that sniff; 3. `Generic.PHP` silences a whole category; 4. no code at all silences **everything**. Always name the narrowest code that fits. A bare `// phpcs:ignore` hides not only the violation you meant but any future violation on that line, from any sniff, so a later edit can introduce a real problem that no one sees. `phpcs:disable` and `phpcs:enable` can also be selective: `// phpcs:disable Squiz.Commenting` followed by `// phpcs:enable Squiz.Commenting` affects only that category, and other sniffs keep reporting inside the region. ## Leaving a reason Anything after ` -- ` in the annotation is ignored by the parser, which makes it the place for a justification a reviewer can read: ```php <?php // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- bootstrap needs raw output before the logger exists var_dump($config); ``` ## Legacy syntax and auditing - PHP_CodeSniffer 4.0 **removed** the old `@codingStandardsIgnoreFile`, `@codingStandardsIgnoreStart`, `@codingStandardsIgnoreEnd`, `@codingStandardsIgnoreLine` and `@codingStandardsChangeSetting` comments. Their replacements are `phpcs:ignoreFile`, `phpcs:disable`, `phpcs:enable`, `phpcs:ignore` and `phpcs:set`. After an upgrade, code still using the old form is checked again, and its violations reappear. - `phpcs --ignore-annotations` disregards every `phpcs:` annotation, which shows how much the codebase is suppressing. ## Annotation versus ruleset exclusion | | In-code annotation | Ruleset `<exclude>` / `<exclude-pattern>` | |---|---|---| | Scope | a line, a region or one file | every file, or files matching a pattern | | Visible in review | yes, next to the code it excuses | only when the ruleset changes | | Survives refactoring | moves with the code, can end up on the wrong line | independent of code layout | | Typical reason | a justified one-off exception | a rule the team does not use, or a directory it does not own | An annotation is documentation of an exception as much as a switch. When the same code starts appearing in dozens of annotations, the rule itself is probably wrong for the project, and the ruleset is the place to change it. ## When to use which tool - one deliberate exception on one line: `phpcs:ignore` with a code and a reason; - a whole class of files, such as generated code or migrations: `<exclude-pattern>` in the ruleset, not annotations scattered through files; - a rule the team rejects everywhere: `<exclude>` in the ruleset.
- Why can a bare // phpcs:ignore become a hidden bug later?Without a code it suppresses every sniff on that line. When someone later edits the line and introduces a different violation, such as a call to a forbidden function, it is silenced too, so neither phpcs nor the reviewer sees it. Naming the exact code keeps the suppression limited to the exception that was actually agreed.
- How do you find out how much a codebase is suppressing?Run `phpcs --ignore-annotations` and compare its report with a normal run; the difference is everything that phpcs:ignore, phpcs:disable and phpcs:ignoreFile are hiding. Searching the code for `phpcs:ignore` without a following sniff code finds the broad suppressions worth narrowing.
saying these in an interview costs you the question
- phpcs:ignore on its own line suppresses that same comment line only
- phpcs:disable stops at the end of the current function
- A bare phpcs:ignore silences only the rule that fired today
- @codingStandardsIgnoreLine still works in PHP_CodeSniffer 4.0
- Annotations are the right way to skip generated directories