yiisoft/yii2-coding-standards
The PHP_CodeSniffer coding standard used by Yii2 core and official extensions. Based on PSR-12 with documented Yii-specific exceptions, it lets you check and automatically fix PHP code with phpcs and phpcbf via the Yii2 ruleset. Requires PHP 7.4+ and PHP_CodeSniffer 4.0.2+.
This is not a Laravel package — there is no service provider, no config to publish, no config/ merge. It is a PHP_CodeSniffer standard (a directory of sniffs plus a ruleset.xml) that happens to describe Yii2 conventions, built on top of PSR-12 since 3.0.0. Day-to-day value for a Laravel developer comes from three concrete situations:
phpcs toolchain.Install (dev only):
composer require --dev yiisoft/yii2-coding-standards
composer require --dev dealerdirect/phpcodesniffer-composer-installer
The second package is the important one — it registers PHPCS "installed paths" automatically. On Composer ≥ 2.2 you must approve the plugin:
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
Look here first, in this order:
| Where | Why |
|---|---|
vendor/yiisoft/yii2-coding-standards/Yii2/ruleset.xml |
The actual standard name (Yii2) and every <rule>/<exclude> it applies. This is the real docs. |
vendor/yiisoft/yii2-coding-standards/Yii2/Sniffs/ |
Yii2-only sniffs — the part you can't get from vanilla PSR-12. |
README.md |
Install + CLI invocation; it is thin, don't expect more. |
Release 3.0.0 notes |
The PSR-12 migration — the single most consequential change if you inherit a 2.x config. |
First use case — prove it runs before wiring anything:
vendor/bin/phpcs -i # 'Yii2' must appear in the list
vendor/bin/phpcs --standard=Yii2 --extensions=php app/legacy-yii/
vendor/bin/phpcs -e --standard=Yii2 # enumerate every sniff the standard enables
If Yii2 is not in phpcs -i, the installed-path registration failed — stop and fix that before writing any XML.
Do not run this standard across a whole Laravel app. Scope it. A single .phpcs.xml.dist at repo root with per-directory <rule> blocks is the cleanest pattern for a hybrid repo:
<?xml version="1.0"?>
<ruleset name="Hybrid">
<description>Yii2 legacy + Laravel</description>
<file>app</file>
<file>legacy</file>
<arg name="extensions" value="php"/>
<arg name="parallel" value="8"/>
<arg name="cache" value=".phpcs.cache"/>
<!-- Only the legacy Yii2 subtree gets Yii2 rules -->
<rule ref="Yii2">
<include-pattern>legacy/*</include-pattern>
</rule>
<!-- Laravel side: PSR-12 only -->
<rule ref="PSR12">
<include-pattern>app/*</include-pattern>
</rule>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/storage/*</exclude-pattern>
<exclude-pattern>*.blade.php</exclude-pattern>
</ruleset>
<include-pattern> inside a <rule> is the key trick — it lets you keep one config and one CI step while applying two different standards.
A common refinement once the legacy tree is "frozen":
.phpcs-legacy.xml → ref="Yii2", run in CI as non-blocking (report only)..phpcs.xml.dist → ref="PSR12" (+ your own sniffs), run as blocking.Wire both into composer.json:
"scripts": {
"lint": "phpcs",
"lint:legacy": "phpcs --standard=.phpcs-legacy.xml --report=summary",
"lint:fix": "phpcbf --standard=.phpcs-legacy.xml legacy/"
}
--report=summary for the legacy run keeps CI noise to one line per file instead of thousands of violation rows.
phpcbf against the Yii2 standard will rewrite whitespace, array syntax, and docblock layout. On Laravel code that is destructive (it will touch things Pint owns). The safe pattern:
vendor/bin/phpcbf --standard=Yii2 legacy/ # Yii2 subtree only, one commit, then review
vendor/bin/pint app/ # Laravel side stays with Pint
Never point both phpcbf and pint at the same directory — see Gotchas.
- name: Install
run: composer install --no-interaction --prefer-dist
- name: PHPCS (blocking, Laravel side)
run: vendor/bin/phpcs --standard=PSR12 --report=checkstyle --report-file=phpcs.xml app/
- name: PHPCS (informational, Yii2 legacy)
run: vendor/bin/phpcs --standard=.phpcs-legacy.xml --report=summary legacy/ || true
|| true is deliberate during migration — you want the violation count visible in the log, not a red build on day one.
PhpStorm: Settings → PHP → Quality Tools → PHP_CodeSniffer, set the interpreter and the phpcs path, then Settings → Editor → Inspections → PHP → PHP CodeSniffer validation and pick Yii2 as the coding standard. This is the highest-leverage step for a team — violations show up inline before a commit, not in CI after review. Note the README's PhpStorm advice (Set from... → PSR12) applies to the formatter, not the linter; the linter's standard is still Yii2.
The package ships sniff/token-parsing classes built for Yii2 syntax. If you're writing house rules, extending them beats extending AbstractSniffUnitTest from scratch — you inherit the correct token handling for Yii2 idioms. Register your sniffs in your own standard directory and reference it; don't patch into vendor/.
phpcs --standard=Yii2 --report=source legacy/ — get a sniff-level histogram, not a file-level one.phpcbf once, review the diff, commit it as a single "style baseline" commit — this makes git blame recoverable.|| true to blocking on the legacy tree only after violation count drops under a threshold you chose in step 1.The standard is named Yii2, not yiisoft. A lot of blog posts and even internal specs reference <rule ref="yiisoft"/>. That will fail with "Referenced sniff 'yiisoft' does not exist." Verify with vendor/bin/phpcs -i before trusting any snippet.
Installed paths are the #1 setup failure. Without dealerdirect/phpcodesniffer-composer-installer (or a manual --runtime-set installed_paths vendor/yiisoft/yii2-coding-standards), Yii2 won't resolve even though the package is installed. The plugin itself silently does nothing until you approve it in allow-plugins — Composer 2.2+ will prompt, CI will not. Pin it in composer.json explicitly.
PSR-12 baseline means heavy overlap with Laravel's own tooling. Since 3.0.0 the ruleset is PSR-12-derived. That means the Yii2 standard will duplicate most of what laravel/pint (which wraps PHP-CS-Fixer) already formats. Running both produces conflicting auto-fixes and merge churn. Pick one formatter per directory; use PHPCS as the linter only.
Upgrading 2.x → 3.x is a breaking style change, not a patch. 2.x was PSR-2-based; 3.x is PSR-12. Pin ^3.0 and treat the upgrade as a separate formatting commit across the legacy tree. Same applies to 3.0.1, which specifically excludes a PSR12.Files.FileHeader.SpacingAfterBlock false positive — if you're on 3.0.0 and seeing spurious file-header errors, that's why.
Exclude Blade and anything non-.php. PHPCS's default --extensions can surprise you; set <arg name="extensions" value="php"/> explicitly and add <exclude-pattern>*.blade.php</exclude-pattern>. Yii2 sniffs have no concept of Blade directives and will emit nonsense.
Expect Yii2-idiom false positives on Laravel code. Static facade calls (Route::get(), Cache::put()), container-resolved $request injection, and Laravel's Arr::/Str:: helpers can trip Yii2 naming/static-call sniffs. Don't try to make Laravel code pass the Yii2 standard — that's the wrong direction. Scope the standard to the legacy tree (<include-pattern>), full stop.
Disable sniffs surgically, not the whole standard. When a rule is genuinely wrong for your context:
<rule ref="Yii2">
<exclude name="Yii2.Commenting.FunctionComment"/>
<exclude name="PSR12.Files.FileHeader"/>
</rule>
Use phpcs -s to get the sniff code in the violation output — you need the exact Standard.Category.SniffName to exclude it, and the default output only gives you the message.
Debugging toolkit:
vendor/bin/phpcs -s --standard=Yii2 file.php # show sniff codes alongside messages
vendor/bin/phpcs --report=source --standard=Yii2 . # which sniffs fire most, across the codebase
vendor/bin/phpcs --report=diff --standard=Yii2 . # what phpcbf would change, without changing it
vendor/bin/phpcs -e --standard=Yii2 # everything enabled
vendor/bin/phpcs --severity=5 --standard=Yii2 . # only the nasty stuff
The --report=source histogram is the single most useful debugging command here — it tells you whether your ruleset is badly configured or your codebase is just messy.
Warnings vs. errors. PHPCS distinguishes them; CI fails on both by default. For a gradual rollout:
vendor/bin/phpcs --warning-severity=0 --standard=Yii2 legacy/
That lets style warnings pass while still blocking genuine errors.
Performance. PHPCS is a token-streaming parser and is slow on large trees. Always set <arg name="parallel" value="8"/> and <arg name="cache" value=".phpcs.cache"/>, and gitignore the cache file (.phpcs.cache) so it doesn't get committed or leak across CI runs in a stale state.
No Laravel-side extension points exist. There's no config/coding-standards.php, no facade, no publishable config. Everything is .phpcs.xml XML plus sniff classes. If someone on the team expects "install and it just works," reset that expectation early — this is a ruleset, and rulesets are configured, not bootstrapped.
Maintenance posture. Last release is 3.0.1 and the package is maintained by the Yii core team for Yii's own repo. It is not tracking Laravel or PHP 8.x language features. Pin the exact version in composer.lock, and if you find yourself heavily excluding sniffs, that's a signal to fork the ruleset internally rather than accumulate a 200-line exclusion list.
How can I help you explore Laravel packages today?