Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpcsextra Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

PHPCSExtra is not a formatter you run on its own — it is a set of extra sniffs that plug into PHP_CodeSniffer and get composed into your ruleset. Your first job is not "pick a standard", it's "decide which sniffs belong in this Laravel app".

1. Install (dev only). The allow-plugins step must come before require, otherwise Composer 2.2+ blocks the installer plugin and the standards never register with PHPCS:

composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcsstandards/phpcsextra

Do not add squizlabs/php_codesniffer or phpcsstandards/phpcsutils to your own composer.json. PHPCSExtra (and any other external standard you install) manages those version constraints transitively — pinning them yourself is how you end up in --with-all-dependencies dependency-hell six months later. If they are already in your require-dev, remove them.

2. Verify registration — this is always your first debugging step:

vendor/bin/phpcs -i

You should see Modernize, NormalizedArrays, Universal appended to the list. If they're missing, the plugin didn't register: check allow-plugins, then composer dump-autoload.

3. Look at what you actually installed before writing a ruleset:

vendor/bin/phpcs -e --standard=Modernize      # list sniffs in a standard
vendor/bin/phpcs -e --standard=Universal      # ~40 sniffs, read this list
vendor/bin/phpcs --generator=Text --standard=NormalizedArrays   # read the docs

Most sniffs ship embedded documentation (:books: in the README) and many ship metrics (:bar_chart:, viewable with --report=info). Read the docs from the CLI — it's faster than the GitHub README and matches the exact version you installed.

4. First use case — audit config/ and lang/, not app/. This is the lowest-risk, highest-signal entry point in a Laravel codebase. Laravel config files are giant hand-merged arrays, which is exactly what Universal.Arrays.DuplicateArrayKey, Universal.Arrays.MixedArrayKeyTypes and Universal.Arrays.MixedKeyedUnkeyedArray are built for. These are analysis-only sniffs (no fixer), so you get findings without any risk of a tool rewriting 200 lines of config:

vendor/bin/phpcs --standard=Universal.Arrays.DuplicateArrayKey \
    --extensions=php --report=full config/ lang/

A duplicate key in config/services.php silently clobbers the earlier value and produces a mystery "why is my API key empty" bug — this sniff earns its keep on day one.

Implementation Patterns

Ruleset composition: block-adopt the formatters, cherry-pick Universal

Universal is explicitly not a standard — it's a sniff collection containing mutually contradictory rules (DisallowFinalClass vs RequireFinalClass, DisallowAnonClassParentheses vs RequireAnonClassParentheses, DisallowAttributeParentheses vs RequireAttributeParentheses). Including <rule ref="Universal"/> in a ruleset is the single most common mistake with this package. Modernize and NormalizedArrays are coherent standards and can be adopted as blocks.

A phpcs.xml.dist that a Laravel dev can actually live with:

<?xml version="1.0"?>
<ruleset name="Laravel App">
    <description>Analysis sniffs from PHPCSExtra; formatting handled by Pint.</description>

    <file>app</file>
    <file>config</file>
    <file>database</file>
    <file>routes</file>

    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*/bootstrap/cache/*</exclude-pattern>
    <exclude-pattern>*/node_modules/*</exclude-pattern>

    <arg name="extensions" value="php"/>
    <arg name="colors"/>
    <arg name="cache" value=".phpcs.cache"/>
    <config name="php_version" value="80200"/>

    <rule ref="Universal.Arrays.DuplicateArrayKey"/>
    <rule ref="Universal.Arrays.MixedArrayKeyTypes"/>
    <rule ref="Universal.Arrays.MixedKeyedUnkeyedArray"/>
    <rule ref="Universal.CodeAnalysis.ForeachUniqueAssignment"/>
    <rule ref="Universal.CodeAnalysis.NoDoubleNegative"/>
    <rule ref="Universal.CodeAnalysis.NoEchoSprintf"/>
    <rule ref="Universal.CodeAnalysis.ConstructorDestructorReturn"/>
</ruleset>

Then wire it into Composer so nobody has to remember the invocation:

"scripts": {
    "lint": "phpcs --standard=phpcs.xml.dist",
    "lint:fix": "phpcbf --standard=phpcs.xml.dist"
}

Coexisting with Laravel Pint

Pint is PHP-CS-Fixer; PHPCSExtra is PHP_CodeSniffer. They are unrelated engines that will both try to own the same whitespace. The stable arrangement is a split of responsibilities:

  • Pint owns formatting. Don't enable NormalizedArrays.Arrays.ArrayBraceSpacing, NormalizedArrays.Arrays.CommaAfterLast, or the Attributes.* spacing/parentheses sniffs while Pint is the formatter — you'll get two tools fighting over the same lines, phpcbf rewriting what pint just wrote, and a diff that flip-flops forever in CI.
  • PHPCSExtra owns semantic analysis. The CodeAnalysis.* and Arrays.Duplicate* / Arrays.Mixed* sniffs are exactly the class of checks Pint cannot do, which makes this a genuinely complementary install rather than a duplicate one.

If you're dropping Pint in favour of PHPCS, then adopt NormalizedArrays as a block — its defaults (CommaAfterLast: forbid on single-line, enforce on multi-line) match modern Laravel style closely and phpcbf will handle migration in one pass. Just remember to pair it with an indentation sniff; ArrayBraceSpacing explicitly does not handle indentation even when set to newline.

The php_version config is mandatory, not optional

Several sniffs branch on the target PHP version: Modernize.FunctionCalls.Dirname (won't suggest __DIR__/$levels above the configured version), Universal.Arrays.DuplicateArrayKey (negative numeric keys behave differently pre-8.0 and post-8.0), Universal.CodeAnalysis.ConstructorDestructorReturn (PHP4-style constructors only flagged below 8.0). Set it once in the ruleset, or per-run:

vendor/bin/phpcs --runtime-set php_version 80200 --standard=phpcs.xml.dist

Leave it unset and the sniffs infer from the host PHP, which means your CI result changes when the runner image bumps a minor version. That's a maddening class of flake.

Targeted modernisation passes for legacy code

For a codebase inherited from PHP 5.x/7.x, don't fold Modernize into the main ruleset — run it as a one-shot, reviewable pass:

git switch -c lint/modernize
vendor/bin/phpcbf --standard=Modernize --extensions=php app/ database/ routes/
git diff --stat && git diff

Modernize.FunctionCalls.Dirname is the whole content here (dirname(__FILE__) → __DIR__, nested dirname() → $levels). On a fresh Laravel app it will find almost nothing — its value is entirely in migration work, and it's the safest fixer in the package.

Granular suppression by error code

Every sniff exposes error codes, and many are modular specifically so you can disable one behaviour without losing the rest. Get the source code with -s, then exclude just that:

vendor/bin/phpcs -s --standard=phpcs.xml.dist app/
<!-- static is fine here; we only care about instanceof/new -->
<rule ref="Universal.CodeAnalysis.StaticInFinalClass">
    <exclude name="Universal.CodeAnalysis.StaticInFinalClass.ReturnType"/>
</rule>

Same trick for Universal.CodeAnalysis.NoDoubleNegative.FoundDoubleWithInstanceof (not auto-fixable) and Universal.CodeAnalysis.NoDoubleNegative.FoundTriple. Use inline // phpcs:ignore Universal.Arrays.DuplicateArrayKey -- intentional override for deliberate cases; phpcs:disable/phpcs:enable around generated blocks.

Gotchas and Tips

  • Never enable Universal.Arrays.DisallowShortArraySyntax in a Laravel project. Laravel style is short arrays everywhere; the sniff's existence is for legacy codebases migrating toward []. (It is, however, the correct choice over the PHPCS-native Generic.Arrays.DisallowShortArraySyntax in those legacy repos specifically because it ignores short list syntax and won't corrupt [$a, $b] = $x when the fixer runs.)
  • Universal.Classes.RequireFinalClass is a flagged risky fixer — treat the fixer as off-limits. It will mark your Eloquent models and service classes final, which breaks Mockery/PHPUnit mocking, and breaks any consumer that extends them. If you want the sniff, run it as an error-only advisory and exclude tests/ and app/Models/, and never let phpcbf touch it. readonly classes are safer territory, but the same mocking caveat applies.
  • Universal.Classes.RequireFinalClass + Universal.CodeAnalysis.StaticInFinalClass are a package deal. The latter only reports static → self inside classes that are actually final. Enabling it before you've adopted finality produces zero findings and looks broken.
  • Fixers and dirty trees. phpcbf is aggressive. Always run it on a clean working tree with the ruleset committed, so git diff is your review surface. For the whole-repo migration case, run phpcbf, inspect, then phpcs again — some sniff/fixer pairs need two passes to settle.
  • composer update needs --with-all-dependencies if you have leftovers. If squizlabs/php_codesniffer or phpcsstandards/phpcsutils are still pinned in your composer.json, a plain composer update phpcsstandards/phpcsextra --with-dependencies will refuse to move them. Remove them from your manifest instead of escalating the flag — that's the actual fix.
  • Cache invalidation. --cache=.phpcs.cache makes iterative runs 5–10x faster on a mid-size app, but the cache doesn't always invalidate when you change the ruleset, only when files change. If a sniff you just enabled reports nothing, delete .phpcs.cache before you start debugging the sniff. Add it to .gitignore.
  • phpcs -i vs phpcs -e. -i proves registration; -e proves the sniff name string you typed is real. Typo'd sniff names in a ruleset are silently ignored by PHPCS — no error, no warning, just a rule that never fires. When a rule "isn't working", verify with -e first.
  • Performance on large config arrays. Universal.Arrays.DuplicateArrayKey and the Mixed* sniffs do real token work on big array literals. On generated files (translation dumps, huge config/*.php), scope the ruleset with <file> entries per directory rather than pointing it at the project root. Split slow CI runs by directory (app/, database/, config/) if a single invocation times out.
  • --report=info is underused. The :bar_chart: sniffs produce metrics — useful when you're deciding whether to adopt a rule ("how many single-line arrays have trailing commas today?") rather than guessing.
  • Scope before suppressing. <exclude-pattern> for vendor/, storage/, bootstrap/cache/, node_modules/, and any *.blade.php-adjacent PHP. PHPCS walking vendor/ is the usual reason a first run takes 90 seconds.
  • Extension point for house rules: put project-specific sniffs in a separate standard (or a phpcs.xml.dist <rule ref="path/to/MySniff"/>) and compose PHPCSExtra underneath it — never fork the package. It's a Composer dependency; local patches get blown away on the next composer update.
  • Licensing is a non-issue but worth knowing: LGPL-3.0, dev-dependency only, not distributed with your application. No copyleft exposure for a standard Laravel app; it would only matter if you redistributed a PHPCS standard that bundled the code.
  • Version floor is real: PHPCS 3.13.6/4.0.2+ and PHPCSUtils 1.2.3+. On older PHPCS the standards register but individual sniffs can E_DEPRECATED or throw. If you're pinned to an old PHPCS for another standard's sake, resolve that first — PHPCSExtra is not the package to downgrade around.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc