vanio/coding-standards
Vanio Coding Standards provides a shared PHP_CodeSniffer ruleset for consistent PHP and Laravel code style across projects. Install via Composer, then run PHPCS to automatically detect formatting, style, and quality issues in your codebase.
vanio/coding-standards is a PHP_CodeSniffer ruleset package — it does not ship a fixer or a CLI of its own. You drive it entirely through phpcs / phpcbf.
Install alongside the sniffer (the package does not pull in PHPCS itself in most ruleset-style packages, so install both explicitly and pin them):
composer require --dev squizlabs/php_codesniffer vanio/coding-standards
Add the standards installer so --standard=Vanio resolves by name instead of a relative vendor path. Without it, PHPCS will only see its built-in standards:
composer require --dev dealerdirect/phpcodesniffer-composer-installer
Then confirm registration:
vendor/bin/phpcs -i # should list Vanio and friends
Look here first: vendor/vanio/coding-standards/. Check composer.json for an extra.phpcodesniffer.standard key, then open the ruleset XML it points to (ruleset.xml, Vanio/ruleset.xml, etc.). That file is the whole package — every rule you get is a <rule ref="..."> child. Read it top to bottom before adopting it; it tells you which upstream standards (PSR-12, Slevomat, Squiz, generic sniffs) are enabled and at what severity.
Create a project-level phpcs.xml at the Laravel repo root rather than invoking --standard=vendor/... on the command line. The project file is what editors, CI, and composer scripts all read, so it becomes the single source of truth:
<?xml version="1.0"?>
<ruleset name="App">
<description>Project rules: Vanio base + Laravel paths.</description>
<file>app</file>
<file>config</file>
<file>database</file>
<file>routes</file>
<file>tests</file>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/storage/*</exclude-pattern>
<exclude-pattern>*/bootstrap/cache/*</exclude-pattern>
<rule ref="vendor/vanio/coding-standards/ruleset.xml"/>
</ruleset>
Referencing by path (rather than ref="Vanio") keeps the ruleset working even if the composer-installer plugin is disabled in CI with --no-plugins.
First use case: run it report-only on a fresh Laravel skeleton before you wire it into anything:
vendor/bin/phpcs --standard=phpcs.xml -s --report=summary
-s prints sniff codes (e.g. Generic.Files.LineLength.TooLong), --report=summary gives a per-file/per-sniff count instead of the wall of line-by-line output. That summary is your adoption plan: fix the high-frequency, low-effort sniffs first, exclude or downgrade the rest.
Composer scripts as the entry point. Day-to-day you should never type the full path. Put the two verbs on composer:
"scripts": {
"lint": "phpcs --standard=phpcs.xml -s --report=summary",
"lint:full": "phpcs --standard=phpcs.xml",
"lint:fix": "phpcbf --standard=phpcs.xml || true",
"lint:changed": "phpcs --standard=phpcs.xml --report=summary $(git diff --name-only --diff-filter=ACMR -- '*.php' | tr '\\n' ' ')"
}
The || true on lint:fix matters: phpcbf exits 1 when it fixed something and 2 when it could not fix everything. Treating fixable findings as failure makes the script unusable in a pre-commit loop.
Incremental adoption via exclude-by-path. vanio/coding-standards is opinionated; a brownfield Laravel app will produce thousands of findings on day one. Rather than disabling sniffs, exclude directories you intend to migrate later and shrink the list over time:
<exclude-pattern>app/Legacy/*</exclude-pattern>
<exclude-pattern>database/migrations/*</exclude-pattern>
Migrations especially — they are generated, timestamped, and historically inconsistent; enforcing coding standards on them wastes time and creates churn in a directory nobody hand-edits.
Per-sniff tuning rather than all-or-nothing. Every rule in the Vanio ruleset can be re-referenced in your own file with a different severity, or with sniff properties changed:
<!-- Keep the sniff, downgrade to warning so it doesn't fail CI -->
<rule ref="vendor/vanio/coding-standards/ruleset.xml">
<exclude name="Generic.Files.LineLength"/>
</rule>
<!-- Or re-enable it with your own limit -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="0"/>
</properties>
</rule>
Note the ordering trick: when you <exclude> a sniff from a <rule ref="...ruleset.xml"/> and then re-add it as a separate <rule>, the later rule wins. This is how you get "Vanio's set, but with our line length."
Laravel-specific paths and extensions. PHPCS only looks at .php by default. If you lint .blade.php (rarely useful — the HTML confuses most sniffs) or custom extensions, set <arg name="extensions" value="php,blade.php"/>. More practically, target app/, routes/, config/, database/factories, database/seeders, and tests/ — those are your hand-written PHP surfaces.
CI: matrix the PHP versions you actually support. The Vanio ruleset may contain version-sensitive sniffs. Set the target explicitly so PHPCS doesn't guess from the CI runner:
<config name="testVersion" value="8.2-"/>
<arg name="parallel" value="8"/>
<arg name="cache" value=".phpcs.cache"/>
Then a GitHub Actions step:
- run: vendor/bin/phpcs --standard=phpcs.xml
Exit code 1 on findings is what you want — don't swallow it. Cache the .phpcs.cache file as a CI artifact keyed on composer.lock hash and PHP version to keep the job fast; PHPCS's cache is content-hash-based and survives across runs if the file persists.
Editor + pre-commit parity. Point PhpStorm (Settings → PHP → Quality Tools → PHP_CodeSniffer) at vendor/bin/phpcs and set the ruleset to the project phpcs.xml — not the global default. Same for the VS Code phpcs extension (phpcs.standard → ${workspaceFolder}/phpcs.xml). For pre-commit, use captainhook/captainhook or a plain .git/hooks/pre-commit calling composer lint:changed — linting only staged PHP files keeps the hook under a second on most commits.
Coexistence with php-cs-fixer. If you already run php-cs-fixer, do not run both as fixers on the same files in the same pass — they will fight over array syntax and spacing and produce non-deterministic diffs. Pick one owner for formatting. A workable split: php-cs-fixer owns whitespace/import-order/array-style, and PHPCS+Vanio runs report-only in CI for the structural sniffs (naming, complexity, unused params) that CS-Fixer can't express.
Per-module overrides. In a modular Laravel codebase (Domain/Modules layout), use <rule> blocks with a <file> child to scope an override to one subtree:
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint">
<exclude-pattern>app/Domain/Legacy/*</exclude-pattern>
</rule>
--standard=Vanio silently failing. If vendor/bin/phpcs -i doesn't list Vanio, PHPCS fell back to PEAR and will report nonsense. Causes: composer plugin blocked (--no-plugins, or allow-plugins not set in composer.json for dealerdirect/phpcodesniffer-composer-installer), or the package's extra.phpcodesniffer.standard key names a directory you're not looking at. The fix when you can't run plugins is vendor/bin/phpcs --config-set installed_paths vendor/vanio/coding-standards — but note this writes to vendor/squizlabs/php_codesniffer/CodeSniffer.conf, which is wiped by every composer install. Prefer the path-based <rule ref="vendor/vanio/coding-standards/ruleset.xml"/> in your own phpcs.xml.
Ruleset path refs are relative to the calling ruleset, not CWD. ref="vendor/vanio/..." works from the project root phpcs.xml, but if you factor your config into config/phpcs/base.xml, that path now resolves relative to config/phpcs/. Use ./vendor/... or absolute paths in included rulesets to avoid a confusing "referenced sniff does not exist" error.
phpcbf does a lot, fast, and unrecoverably. Sniffs like SlevomatCodingStandard.Arrays.TrailingArrayComma and the Generic.WhiteSpace family will rewrite hundreds of files in one pass. Commit or stash before the first lint:fix run, run it on a clean tree, and inspect git diff --stat before git add -A. Second run should produce zero diffs — if it doesn't, a sniff is non-idempotent and you should exclude it.
Exit codes are three-valued: 0 clean, 1 findings, 2 error (bad config, unparsable file, missing standard). CI scripts that only check if failed conflate 1 and 2, so a broken ruleset looks like a style violation. Check $? -eq 2 explicitly in wrapper scripts, or at minimum print phpcs -i output on failure.
testVersion unset = PHPCS guesses. It uses the PHP version of the running interpreter, which on CI is the matrix value but locally may be different from your Laravel require.php. Mismatched guesses produce or suppress sniffs like PHPCompatibility and nullable-type checks. Always set <config name="testVersion" value="8.2-"/> explicitly.
Debugging a sniff you don't recognize: phpcs -e lists every registered sniff in the active standard (huge, grep it); phpcs -s adds the sniff code to each violation; phpcs -vvv shows ruleset resolution. When a finding fires and you can't find it in vendor/vanio/coding-standards/ruleset.xml, it's coming from a transitive <rule ref="..."> deeper in the chain — grep -r "SniffName" vendor/ finds the source.
Low-maintenance risk. This package has essentially no adoption (0 stars, 0 dependents). Practical consequences: it will not be updated for PHPCS 4.x's renamed sniff classes, and it may pin an old squizlabs/php_codesniffer range. Pin both packages in composer.json with exact or tilde constraints ("vanio/coding-standards": "~1.0"), verify composer why vanio/coding-standards doesn't force an outdated PHPCS into a project that needs a newer one, and keep your own phpcs.xml thin enough that you could swap the base ruleset for slevomat/coding-standard or phpstan/phpstan-adjacent tools without rewriting your config. Forking the single ruleset.xml into config/phpcs/vanio.xml under your own repo is a legitimate, low-cost hedge.
Extension point: since the package is a ruleset, not a library, there is nothing to subclass. If you need a rule it lacks, write your own sniff under app/../phpcs/Sniffs/ and register it in phpcs.xml via <rule ref="./tools/phpcs/Sniffs"/>, or install slevomat/coding-standard and add individual sniffs on top of the Vanio base. That layered approach — Vanio as the base, your own sniffs as the delta — is the intended way to extend a ruleset package.
Interaction with Laravel's own tooling. Laravel Pint ships its own opinionated preset and cannot consume a PHPCS ruleset. Running both is possible but means maintaining two configs that disagree. If a project already uses Pint, use Vanio/PHPCS for CI reporting only, or drop one. Don't try to make Pint read phpcs.xml — it doesn't.
How can I help you explore Laravel packages today?