squizlabs/php_codesniffer
PHP_CodeSniffer tokenizes PHP files to detect coding standard violations and automatically fix them. It ships two scripts: phpcs for sniffing and phpcbf for auto-correction, keeping code clean and consistent across teams and CI. The official continuation of the Squizlabs package.
Architecture fit. PHP_CodeSniffer is a development-time static analysis tool, not a runtime library. phpcs tokenizes PHP source and reports violations of a coding standard; phpcbf rewrites files to fix the fixable subset. It belongs in the developer toolchain layer of a Laravel monorepo: local pre-commit hooks, CI pipelines, and IDE integrations — never in require. Treat it as infrastructure with the same governance as the linter config itself. Its tokenizer-based design (rather than a full AST) makes it fast enough for incremental runs but less semantically precise than PHPStan/Psalm; it complements those tools rather than replacing them. For a Laravel codebase the realistic role is style and convention enforcement (PSR-12 plus framework-specific sniffs), with PHPStan/Psalm owning type and logic analysis.
Integration feasibility. Low friction. Installation is a single composer require --dev "squizlabs/php_codesniffer=^4.0" (note: the Packagist name remains squizlabs/php_codesniffer even though the project is now maintained by PHPCSStandards — the README explicitly states it is the official continuation of the abandoned Squizlabs package). Configuration is a checked-in phpcs.xml / ruleset.xml, which is diffable, reviewable, and version-controlled — that is the main reason this succeeds where ad-hoc lint scripts fail. PHP 7.2+ is supported and the 4.0.1 release adds PHP 8.5 runtime support, so it will not block a modern Laravel runtime matrix. Exit codes (0 clean, 1 violations, 2 fixer failure, 3 execution error) map cleanly onto CI step failures and are already understood by build tooling.
Technical risk. Three material risks. First, versioning semantics: the project’s own policy states that PATCH releases may change which violations are reported and what fixes are applied (“most bugs are the result of coding standard errors being incorrectly reported… the messages produced… are likely to be different between PATCH versions”). A pinned-but-auto-updated CI gate can therefore fail a green build with no code change — this is a policy problem to solve, not a bug to wait out. Second, fixer safety: phpcbf edits source files, and the changelog contains several real-world fixer-conflict and file-mangling fixes (e.g., the PHP < 8.0 unclosed-attribute tokenizer bug that could “mangle the file under scan”). Autofix must be gated behind a clean VCS state. Third, syntax lag: tokenizer support for new PHP language features trails the runtime, so adopting PHP 8.4/8.5 syntax early can produce false positives or parse noise until a release catches up. Overlap with Laravel Pint (PHP-CS-Fixer under the hood) is a fourth, softer risk: two formatters with different fix sets will fight over the same files.
Key questions.
ruleset.xml, PHPCSExtra/Slevomat supplements, or the Laravel preset already served by Pint? Running both Pint and phpcbf on the same tree is the failure mode to avoid.ignore_warnings_on_exit decision..php view files? PHPCS does not parse Blade natively; this usually requires a preprocessing step or exclusion.Stack fit. Strong. Composer-native, PHP 7.2–8.5, runs identically on developer machines, CI runners, and containers. It slots into an existing Laravel repo without touching application code, service providers, or config caching. It does not need a database, queue, or any runtime service — pure CLI, deterministic given a pinned version and pinned ruleset. BSD-3-Clause is permissive and imposes no distribution obligations on the product.
Migration path. Recommended sequence:
^4.0 as a dev dependency, commit a minimal phpcs.xml (PSR-12 + Laravel-relevant exclusions), run report-only in CI for a trial period to size the violation backlog.app/, tests/, and new code first. Do not attempt a big-bang cleanup.phpcbf once on a clean branch, review the diff by hand (it will be large), and land it as a dedicated no-logic-change PR.phpcs extension pointing at the repo-local vendor/bin/phpcs).Compatibility. Watch three seams: (a) the 3.x → 4.x major bump — BC breaks in CLI usage, ruleset.xml format, and the sniff-developer API mean any third-party standard not yet 4.x-ready will need to stay on 3.x (3.13.x is still maintained and its fixes fold into 4.x); (b) PHP version drift — pin the PHPCS version in composer.lock and re-test on every PHP minor upgrade; (c) PHAR-vs-Composer installs — pick one for reproducibility; the Composer path is preferable in a Laravel repo since it is lockfile-governed, while PHARs (signed, with GitHub attestations) suit polyglot CI images.
Sequencing. Tool decision → ruleset draft → report-only CI → baseline → one autofix PR → blocking gate → pre-commit/IDE → optional custom sniffs. Each step is independently revertable; the gate should never be enabled before the baseline is committed and the autofix diff reviewed.
Maintenance. Low ongoing effort, but not zero. The recurring tasks are: bumping the pinned version deliberately (never via blind composer update in CI), reviewing changelog entries for new or changed sniffs each minor/patch, and maintaining phpcs.xml as the codebase grows. The changelog is high quality and release notes are explicit about fixer-affecting changes — this materially reduces surprise. Budget a small recurring maintenance allocation (a few hours per quarter) plus a named owner.
Support. Upstream is active: regular releases, a maintained wiki, an issue tracker, GitHub Actions validation and test workflows with coverage reporting, and a funded OpenCollective. The principal risk is bus factor rather than abandonment — it is a small maintainer group carrying a tool that the entire PHP ecosystem depends on. Community size is a mitigation: answers exist for nearly every ruleset question. Internal support burden is mostly “why is CI red on a dependency bump,” which the versioning policy causes and a documented bump procedure resolves.
Scaling. Scales well horizontally at the file level and is embarrassingly parallel (--parallel) with a results cache for repeat runs. Two caveats: tokenization cost grows with codebase size, so full-repo runs on every PR become slow at scale — prefer changed-files-only on PRs plus a full nightly run; and cache invalidation across branches/CI runners needs explicit configuration or the cache silently helps nothing. For a large Laravel monorepo, expect full scans to move from seconds to minutes over a few years and plan the changed-files path early.
Failure modes.
phpcbf only on clean working trees, review diffs, never autofix in CI, keep --report=diff available.phpcs:ignore annotations with justification, or hold the PHP upgrade until PHPCS supports it.Ramp-up. Very fast for developers: the CLI is two commands, defaults to PSR-12, and error messages are self-describing with file, line, and sniff name. A competent Laravel developer is productive in under an hour; the onboarding document only needs the repo command, the ruleset location, and the ignore-annotation syntax. The genuinely specialized skills — writing custom sniffs against the tokenizer API, or debugging fixer conflicts — are rare, rarely needed, and should be explicitly out of scope unless a specific policy gap justifies them. The realistic ramp-up burden is on whoever owns CI: understanding exit codes, caching, and parallel execution is a half-day, not a project.
How can I help you explore Laravel packages today?