phpcsstandards/phpcsextra
PHPCSExtra is a collection of extra sniffs and coding standards for PHP_CodeSniffer, including Modernize, NormalizedArrays, and Universal rules to help enforce consistent, modern PHP code. Requires PHP 5.4+ and PHPCS 3.13.6/4.x.
What this package actually is. PHPCSExtra is not an application library — it is a static-analysis ruleset bundle for PHP_CodeSniffer. It ships three registered standards (Modernize, NormalizedArrays, Universal) plus a PHPCSUtils helper dependency. There is no runtime code path in the product; the only artifact that reaches production is developer discipline. That framing matters for every downstream decision: value is realized entirely through CI enforcement and developer habit, not through deployment.
Architecture fit. Strong fit for any PHP codebase already standardizing on PHPCS — Laravel included, since Laravel projects are plain PHP and PHPCS scans app/, routes/, tests/, database/ normally. The value proposition is the gap PHPCS's bundled standards (PSR-12, PEAR, Generic) leave open: modern-PHP modernization (dirname(__FILE__) → __DIR__, nested dirname() collapsing), array normalization, and semantic "correctness" sniffs PHPCS native rulesets do not cover (DuplicateArrayKey, ForeachUniqueAssignment, NoDoubleNegative, NoEchoSprintf, StaticInFinalClass). Several sniffs include auto-fixers and honor a php_version runtime setting, which means the ruleset can be tuned to a minimum-supported-PHP floor rather than the team's newest local PHP.
The Universal trap. Universal is explicitly not a standard — it is a sniff collection containing mutually contradictory rules (Classes.DisallowFinalClass vs Classes.RequireFinalClass, Attributes.DisallowAttributeParentheses vs RequireAttributeParentheses, Classes.DisallowAnonClassParentheses vs RequireAnonClassParentheses). It must never be adopted wholesale. Adoption is therefore a product decision exercise — someone must pick a coherent policy set and document the rationale. This is the single largest effort driver and the main reason a "just install it" rollout fails.
Integration feasibility. Low technical friction, high organizational friction.
dealerdirect/phpcodesniffer-composer-installer, which must be allowed in config.allow-plugins (a common first-run failure in locked-down CI images).composer.json and letting the standards manage them — good hygiene, but it means version resolution is delegated to a third party and can shift on composer update, which is exactly the kind of thing that breaks CI on an unrelated PR.Technical risk.
Universal.Classes.RequireFinalClass is flagged by the maintainers as potentially behavior-changing; auto-fixing it across a codebase can break mocking/proxying patterns silently. Any fixer marked risky needs a human-reviewed, single-purpose commit.Universal.Arrays.DisallowShortArraySyntax is directly at odds with idiomatic Laravel/Pint output; NormalizedArrays overlaps with what Pint already enforces. Enabling these without a decision creates hundreds of "violations" that are really tool-policy disagreements, and developers learn to ignore the linter.Key questions.
--runtime-set php_version), and does it match our actual support matrix per service?Universal sniffs do we adopt, and who signs off on each policy? Who owns the ruleset file?Stack fit. Treat PHPCSExtra as a dev-only quality gate, layered as: laravel/pint (formatting, unchanged) → phpcsstandards/phpcsextra + internal ruleset (semantics and modernization) → CI. Concretely, one small internal Composer package (e.g., acme/phpcs-standard) that requires phpcsstandards/phpcsextra and defines a single AcmeStandard ruleset. That package is the delivery vehicle; individual repos only add acme/phpcs-standard and a phpcs.xml.dist that references it. This avoids each repo inventing its own policy and avoids every repo resolving PHPCS/PHPCSUtils versions independently.
Ruleset scoping — the actual design work. Start from Modernize as a whole (low controversy, high value, mostly auto-fixable, PHP-version-aware). For NormalizedArrays, adopt only after confirming it doesn't fight Pint — likely enable CommaAfterLast for multi-line arrays and skip brace-spacing if Pint already owns it. For Universal, cherry-pick: high-value/low-controversy first (DuplicateArrayKey, ForeachUniqueAssignment, NoDoubleNegative, NoEchoSprintf, ConstructorDestructorReturn, LowercaseClassResolutionKeyword); explicitly exclude DisallowShortArraySyntax, RequireFinalClass, DisallowFinalClass, and the attribute/anonymous-class parentheses pairs unless a policy decision says otherwise. Document exclusions in the ruleset file with one-line justifications — future maintainers will otherwise re-litigate them.
Migration path.
phpcs -i to confirm the three standards register, run phpcs --standard=Modernize --report=summary on app/ only, and estimate violation volume. Deliverable: a written recommendation on which sniffs to adopt and which to exclude, plus a measured CI runtime number.phpcbf per sniff family, one commit per sniff, no functional changes. Add a .git-blame-ignore-revs entry so git blame stays usable. Risky fixers (RequireFinalClass) are manual-review only, never auto-applied.acme/phpcs-standard, version it, and roll it out repo by repo. Treat ruleset changes like any other dependency change: changelog, review, and a "this will surface N new violations" note.Compatibility considerations. Pin the internal standard package's constraints rather than the underlying tools; avoid direct require-dev on squizlabs/php_codesniffer or phpcsstandards/phpcsutils in app repos (per the README's own guidance) unless a repo consumes them directly. Verify allow-plugins is set in both local and CI Composer configs. Confirm the PHP version used in CI matches the php_version runtime setting, otherwise sniffs will report modernizations the deployed runtime can't support.
Sequencing rationale. Modernize → semantic Universal sniffs → NormalizedArrays. Formatting-adjacent rules come last because they collide with the existing formatter and generate the most diff noise; semantic rules come first because a violation there is usually a genuine bug or a real maintainability win, which builds credibility for the gate.
Maintenance. Very low ongoing cost if the shared-standard pattern is used. One ruleset package, one changelog, quarterly dependency refreshes. Without that pattern, N repos each pin their own version and drift into inconsistent enforcement — measurable as "PRs that pass locally but fail CI" and "why did this repo's style change." The upstream maintenance profile is favorable: the package is maintained by the PHPCS Standards organization alongside PHPCSUtils, has an active release cadence, and documents per-sniff properties and error codes, which means ruleset changes are addressable without forking.
Support. Support burden is almost entirely educational, not technical. Expect recurring questions in three buckets: (1) "the fixer rewrote my code and the diff is huge" — mitigated by one-sniff-per-commit policy and blame-ignore; (2) "Pint and PHPCS disagree" — mitigated by the documented division of responsibility; (3) "why is this a violation?" — mitigated by phpcs --generator=Text per sniff, which emits the sniff's own documentation. A short internal style guide page covering adopted sniffs, excluded sniffs, and the two-command workflow (vendor/bin/phpcs, vendor/bin/phpcbf) removes most of it.
Scaling. PHPCS is single-threaded and file-based; runtime scales roughly with scanned file count. At monorepo scale this becomes a CI bottleneck. Mitigations: scan changed files in PRs, run the full scan in a nightly or post-merge job, parallelize in CI via a matrix split by directory or via --parallel, and always exclude vendor/, storage/, bootstrap/cache/, and build artifacts. Cache the Composer layer in CI so the PHPCS install isn't the dominant cost. Budget: expect CI to add low-single-digit minutes for a mid-size app on a changed-files scan; full scans on large repos can reach several minutes and belong off the critical path.
Failure modes to plan for.
phpcs -i, rule appears to pass. Add an assertion step in CI that the expected standards are registered.composer update bumps PHPCS/PHPCSUtils and changes the ruleset. Constrain and renovate deliberately.php_version-aware sniffs behave differently. Pin the PHP version in both.Ramp-up. Estimate one engineer-week for the spike plus ruleset design, and 2–5 engineer-days per repo for the initial remediation commit depending on size and existing adherence. Developer-facing ramp-up is genuinely small — two commands, one style guide — but the organizational ramp-up (agreeing the policy, legal clearance, deciding the Pint relationship, deciding PHPCS 3 vs 4) is where the calendar time goes and should not be underestimated in the plan. Ramp-up cost is inversely proportional to how early the shared-standard package exists; building it in the spike rather than after three repos have already adopted ad-hoc configs is the highest-leverage sequencing decision.
How can I help you explore Laravel packages today?