phpcs.xml ruleset file
| Anatomy of phpcs.xml | |
| Exclude files | |
| Exclude single sniffs | |
| The duplicate rule trap | |
| Tune a sniff | |
| Silence one line | |
| Verify the config |
Anatomy of phpcs.xml
A ruleset starts from a standard and subtracts from it. This one takes all of PSR-12, then carves out two sniffs and two files:
<?xml version="1.0"?>
<ruleset name="aredel">
<rule ref="PSR12">
<exclude name="Generic.Files.LineLength"/>
<exclude name="PSR1.Files.SideEffects"/>
</rule>
<exclude-pattern>*/BasePage.php</exclude-pattern>
</ruleset>
Use it explicitly or make it the default:
phpcs --standard=phpcs.xml src/
Exclude files
<exclude-pattern> skips whole files: generated code, vendor copies, or classes whose exact bytes matter (byte-identical twins that must never be reformatted apart). The pattern matches the full path, so a trailing filename is enough:
<exclude-pattern>*/BasePage.php</exclude-pattern>
<exclude-pattern>*/ArticlePage.php</exclude-pattern>
Excluded files are skipped by reporting and fixing runs alike — there is no per-run opt-out flag for them, only a deliberate temporary ruleset copy.
Exclude single sniffs
<exclude name="..."/> inside the rule drops one sniff while keeping the rest of the standard. Two real cases:
- Generic.Files.LineLength — a 400-character ad HTML blob assigned to one variable cannot be wrapped safely: splitting inside the string literal would change the output.
- PSR1.Files.SideEffects — legacy page scripts mix class definitions with top-level echo/require logic. Splitting them is a refactor, not a style fix.
Exclude only what you cannot fix; every exclusion is technical debt with your future self as the creditor.
The duplicate rule trap
This looks equivalent but is not clean:
<rule ref="PSR12"/>
<rule ref="PSR12">
<exclude name="Generic.Files.LineLength"/>
</rule>
The bare first line imports everything with no excludes, the second imports it again minus one sniff. In PHPCS 4.x the exclude still wins, so it works — but the file now includes the whole standard twice, and the next reader cannot tell which block is authoritative. One block, excludes inside:
<rule ref="PSR12">
<exclude name="Generic.Files.LineLength"/>
</rule>
Tune a sniff
Excluding is binary; properties are a dial. The line-length sniff takes two: warn at lineLimit, error at absoluteLineLimit (zero disables the error):
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="150"/>
<property name="absoluteLineLimit" value="0"/>
</properties>
</rule>
Prefer this over excluding whenever the default threshold is merely too strict rather than wrong.
Silence one line
For the single line that cannot comply, annotate it instead of weakening the ruleset for everyone:
// phpcs:ignore Generic.Files.LineLength -- long ad HTML blob
$ads_block = '...';
The reason after -- is mandatory by courtesy: the next reader must know why this line is special.
Verify the config
A ruleset is code: a typo in a sniff name silently matches nothing. List what the standard actually contains, then run one file:
phpcs -e --standard=phpcs.xml phpcs --standard=phpcs.xml -s --report=full src/Page.php
The -s flag prints the sniff code behind each message — the exact string an <exclude> or phpcs:ignore needs.
Article author: Andrei Olegovich
| Development with PHP | |
| PHP linters we use | |
| php -l and lint-php.sh | |
| PHPCS: PHP CodeSniffer | |
| phpcs.xml ruleset file | |
| PHPCBF: fix style automatically | |
| wrap-long-lines.sh | |
| PHPStan and Psalm |