phpmyadmin/coding-standard
PHP_CodeSniffer ruleset from phpMyAdmin, built on the Doctrine Coding Standard, for enforcing consistent PHP code style. Install it with Composer as a dev dependency, then run vendor/bin/phpcs --standard=PhpMyAdmin against your codebase to catch style issues early.
Install and verify registration (2 minutes).
composer require --dev phpmyadmin/coding-standard
vendor/bin/phpcs -i
-i lists every standard PHPCS knows about. You want to see PhpMyAdmin in that list — the standard's name is PhpMyAdmin, not the Composer package name. If it's missing, the Composer plugin that registers standards didn't run (see Gotchas).
Look first at the sniffs, not the docs. There is no documentation beyond a two-line README, so the ruleset XML is the documentation:
vendor/bin/phpcs -e --standard=PhpMyAdmin # every sniff in the standard
This is the one command that answers "why did it flag that?" faster than any web search. Because the standard extends doctrine/coding-standard, most rules you'll see are SlevomatCodingStandard.* and Doctrine.* — the PhpMyAdmin layer is thin.
First use case: audit, don't enforce. Run it against a single slice of the app and read the report before you wire anything into CI:
vendor/bin/phpcs --standard=PhpMyAdmin app/Http/Controllers -s --report=summary
-s shows the sniff code next to each violation. Without it you get prose; with it you get something you can act on, suppress, or grep. --report=summary gives per-file counts so you can see where the heat is concentrated.
Then make the invocation permanent so nobody has to remember the flag. Create phpcs.xml.dist at the project root:
<?xml version="1.0"?>
<ruleset name="App">
<description>App coding standard</description>
<file>app</file>
<file>config</file>
<file>database</file>
<file>routes</file>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg name="parallel" value="8"/>
<arg name="cache" value=".phpcs.cache"/>
<rule ref="PhpMyAdmin"/>
</ruleset>
Now vendor/bin/phpcs with no arguments does the right thing, and the file is committed so the whole team gets the same behaviour. Add it to composer.json:
"scripts": {
"cs": "phpcs",
"cs:fix": "phpcbf"
}
The day-to-day loop is: composer cs:fix (auto-fixes the mechanical stuff) → composer cs (reports what's left) → fix by hand. PHPCS only reports; phpcbf fixes a subset — roughly whitespace, alignment, import ordering, and simple type-hint additions. Do not expect phpcbf to clean a codebase the way Pint does.
Split the responsibility between Pint and PHPCS explicitly. Laravel ships Pint by default and Pint auto-formats; PHPCS flags. Running both blind produces a loop where each tool undoes the other. The workable patterns are:
Pattern A — Pint formats, PHPCS audits non-formatting concerns. Exclude the whitespace/formatting sniffs from the PHPCS run in phpcs.xml.dist so PHPCS becomes a type-safety and strictness gate:
<rule ref="PhpMyAdmin">
<exclude name="SlevomatCodingStandard.Whitespaces.DuplicateSpaces"/>
<exclude name="Generic.WhiteSpace.ScopeIndent"/>
<exclude name="Generic.Files.LineLength"/>
</rule>
Pattern B — PHPCS is the only authority, Pint is removed from the toolchain. Cleaner conceptually, but you lose auto-fix, which is a real day-to-day cost. Pick one and write the choice into a comment at the top of phpcs.xml.dist; the file itself is the only place a future developer will look.
Relax the standard per-directory rather than globally. Tests and migrations are the two places where the standard's strictness buys nothing:
<rule ref="PhpMyAdmin"/>
<rule ref="PhpMyAdmin">
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint"/>
<exclude name="SlevomatCodingStandard.Functions.RequireSingleLineCall"/>
</rule>
Or scope a directory out entirely with <exclude-pattern>tests/*</exclude-pattern>. Per-directory overrides keep app/ strict while avoiding an avalanche of noise in database/migrations/ where the code is generated and rarely read.
Gate only changed files in PRs, run repo-wide on a schedule. This is the highest-leverage sequencing decision you'll make. PHPCS takes a file list, so in CI:
git diff --name-only --diff-filter=ACMR origin/main...HEAD \
| grep -E '\.php$' \
| xargs -r vendor/bin/phpcs -s
New violations block the PR; the pre-existing backlog gets burned down separately. This converts a multi-sprint cleanup into a one-sprint change and avoids the "everything is red" moment that gets a standard switched off.
Use a baseline if you want a green gate on day one. PHPCS supports baseline generation, which records current violations in a file and only reports new ones:
vendor/bin/phpcs --generate-baseline=.phpcs-baseline.xml
vendor/bin/phpcs --baseline=.phpcs-baseline.xml
Commit the baseline and shrink it over time. It's the difference between adopting a standard this sprint and adopting it next quarter.
Suppress with the precise code, not with a blanket disable. Since you ran with -s, you have the code:
// phpcs:ignore SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint -- Eloquent magic
public function scopeActive($query)
For a whole file (common with facades and legacy helpers):
// phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint
Prefer phpcs:disable/phpcs:enable with a scoped sniff code and a reason comment over bare phpcs:ignore. Bare ignores on the wrong line silently do nothing, and a reviewer can't tell whether it was necessary.
Wire it into the IDE so the feedback loop is seconds, not minutes. PhpStorm: Settings → PHP → Quality Tools → PHP_CodeSniffer, point the config path at vendor/bin/phpcs, set the standard to phpcs.xml.dist, and enable Check files on save. VSCode: shevaua.phpcs or bmewburn.vscode-intelephense-client with the phpcs path configured. Same phpcs.xml.dist for both, so editor and CI never disagree.
When the standard is genuinely wrong for Laravel, patch the config once — not the codebase N times. Every inline ignore is a decision made privately; every ruleset exclusion is a decision made publicly. If three developers independently add phpcs:ignore on the same sniff, that sniff belongs in an <exclude> with a comment explaining why.
"ERROR: the PhpMyAdmin standard is not installed" — almost always the Composer plugin being blocked. PHPCS standards are registered by dealerdirect/phpcodesniffer-composer-installer; Composer 2.2+ requires explicit consent:
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer dump-autoload
Verify with vendor/bin/phpcs -i. If you're in a monorepo where the plugin really can't run, fall back to <config name="installed_paths" value="vendor/phpmyadmin/coding-standard"/> in phpcs.xml.dist.
Pin the version and commit composer.lock. The release history here is almost entirely "bump doctrine/coding-standard" (2.1.0, 3.0.0, 3.1.0, 4.1.0, 5.0.0). Every such bump can introduce new sniffs across your entire codebase at once. An auto-merged Dependabot PR on this package can turn every open PR red simultaneously. Require a human to read the sniff diff before merging a standard bump — and diff it with phpcs -e --standard=PhpMyAdmin before and after, not by reading the changelog.
declare(strict_types=1); is mandatory in the Doctrine base. New files that omit it will fail. It's a one-line fix but it's the single most common first-run violation in a Laravel codebase, and it's easy to forget when scaffolding by hand.
Full native type hints are required. Parameters, return types, and properties. This is where Laravel idiom fights the standard hardest: Eloquent magic (dynamic attributes, scope* methods, relationship return types that PHPDoc expresses better than PHP can), facades, and macros. Expect to exclude SlevomatCodingStandard.TypeHints.* sniffs for app/Models/ rather than fight them — or don't scan app/Models/ at all.
Version 5.0 tightened single-line rules. RequireSingleLineCondition and RequireSingleLineCall now flag multi-line conditions and calls that could fit on one line. This produces a lot of churn on a codebase formatted by Pint, which has the opposite opinion on readability. If you're on 5.x and formatting with Pint, exclude both sniffs. (Note 3.1.1 already added an escape hatch: long simple conditions are exempt.)
Blade templates are invisible to PHPCS. resources/views/**/*.blade.php is not PHP it can parse. You must <exclude-pattern> it or you'll get parse errors, not style violations. Same for storage/, bootstrap/cache/, public/, node_modules/, and vendor/.
phpcbf won't fix what you think it will. It handles whitespace, blank lines, import alphabetical order, and some docblock alignment. It will not add native return types, will not add declare(strict_types=1) in every case, and will not fix line lengths. Run it, then re-run phpcs and look at the ratio — if phpcbf cleared less than half, that's your real effort estimate, not the tool's fault.
Never run phpcbf on the whole repo in one PR. Put it in its own commit, merged separately, before any feature work lands, or every open branch gets a merge conflict on nearly every file.
Use caching and parallelism or CI gets slow. --cache=.phpcs.cache makes re-runs near-instant on unchanged files. --parallel=8 cuts wall-clock meaningfully on a large app/. Cache the .phpcs.cache file in CI keyed on the commit. For PRs, scan only changed files — a full-tree scan on every push is wasted work.
Don't assume all non-zero exit codes mean "style violations." PHPCS returns a distinct code for internal/processing failure versus "found violations." In CI, distinguish them, or you'll spend an afternoon debugging a "formatting failure" that's actually a broken sniff or a PHP version mismatch.
Warnings don't fail the build by default — and that's usually what you want. <config name="ignore_warnings_on_exit" value="1"/> in the ruleset makes the distinction explicit. Many of the softer Slevomat rules emit warnings; treating them as blocking on day one is how you get a team that hates the tool.
Check the PHP version floor before you commit. Version 5.0.0 of the standard dropped PHP 8.1 and moved to PHPCS 4. If your CI image or a legacy service is on 8.1, you'll get a resolver conflict or silent sniff differences. Verify with composer why phpmyadmin/coding-standard and check the resolved squizlabs/php_codesniffer version in composer.lock.
Override creep is the real long-term failure. Every <exclude> you add is a fork in all but name. Keep a comment on each one — <!-- excluded: PHPCS and Pint disagree on call formatting, 2025-03 -->. Without it, eighteen months from now nobody can tell which rules are phpMyAdmin's, which are Doctrine's, and which your team disabled for a reason that no longer applies. If you pass ~20% excluded, you're maintaining your own standard and should say so explicitly.
The ruleset is a few hundred lines of XML — the lock-in is low. If phpMyAdmin's style drifts from your needs, copy the XML into your repo and drop the dependency. Pre-decide that trigger rather than discovering it during an upgrade.
How can I help you explore Laravel packages today?