suin/phpcs-psr4-sniff
suin/phpcs-psr4-sniff is a PHP_CodeSniffer sniff that validates PSR-4 autoloading rules, flagging namespace, directory, and filename mismatches so Composer autoloading stays reliable in Laravel and other PHP projects.
Architecture fit. This is not a runtime library — it is a PHP_CodeSniffer (PHPCS) sniff, i.e., a plugin that extends an existing static-analysis tool. That makes it architecturally cheap: a require-dev dependency, zero production footprint, no service container bindings, no config publishing, no Laravel service provider. It slots into the lint stage you already run, alongside Pint/PHP-CS-Fixer and PHPStan. The functional gap it addresses is real: PHPCS core and the PSR-1/PSR-2/PSR-12 standards validate namespace declaration but do not verify that a class's namespace and file path actually resolve under a configured PSR-4 prefix. Neither does Pint's default preset. So the package occupies a genuine, narrow niche.
Integration feasibility. High for a first pass, contingent on three verifications. Install via Composer; standard discovery is handled automatically if the project already uses dealerdirect/phpcodesniffer-composer-installer (common in Laravel projects via PHPCS tooling), otherwise an installed_paths entry is required. Activation is a <rule ref="..."/> line in phpcs.xml.dist plus prefix/base-directory configuration. The friction point is that the sniff must be told the PSR-4 prefix → directory map, and that map duplicates composer.json's autoload.psr-4 block. Two sources of truth for the same fact is a guaranteed drift vector unless the sniff reads composer.json directly — which must be confirmed, not assumed.
Technical risk. Low runtime/blast-radius risk (dev-only, no shipped code); medium-to-high sustainability risk. Signals: 27 stars, single-maintainer project, last release 2021-06-02, and a [READ-ONLY] prefix indicating a subtree split of a monorepo — meaning the GitHub repo is not where development or PR review happens. Roughly four years without a release spans PHP 8.1 enums, 8.2 readonly classes, and 8.3/8.4 syntax, plus the PHPCS 3.x → 4.x transition. A token-based sniff authored against PHPCS 3 token streams is exactly the class of tooling that breaks silently on a major PHPCS upgrade. Treat this as adopt-and-own, not adopt-and-depend.
Key questions to resolve before committing.
composer.json autoload.psr-4, or require the mapping to be duplicated in the ruleset?App\ vs App\Modules\), plus Laravel's Database\Factories\, Database\Seeders\, and Tests\ mappings?Stack fit. Strong fit for a Laravel/PHP monolith or a modular Laravel codebase. Default Laravel layout is already PSR-4-clean (App\ → app/, Tests\ → tests/, Database\* → database/*), so the sniff's steady state is "green with occasional catches" rather than "thousands of pre-existing violations." The value is preventive: it stops the class of defect where a developer renames or moves a class and Composer's autoloader resolves it on their machine but not in production (Class not found at deploy — a failure mode that is expensive to diagnose and trivially preventable at lint time).
Migration path. Three viable routes, in ascending order of ownership cost:
dump-autoload --optimize --strict-psr (fails the build on PSR-4 violations), PHP-CS-Fixer's psr_autoloading rule, or Slevomat's TypeNameMatchesFileName sniff. Lower ceremony, but each covers a slightly different slice (Composer validates autoloadability at dump time, not per-file at lint time) and none gives file-level, line-level CI feedback as cleanly.Do not pursue route 1 without a documented exit trigger.
Compatibility. Verify against the project's pinned PHPCS major version first — if the team is on or moving to PHPCS 4, expect breakage and budget for the vendored fork. Pin the package to an exact version (no ^ range) so an upstream subtree-split tag cannot silently alter lint behavior. Confirm CI runs on Linux so path/case behavior matches production.
Sequencing.
app/ to inventory violations; do not gate CI yet.files autoload entries.--strict-psr.Maintenance. The dominant cost is ownership, not operation. With no upstream release since mid-2021 and a read-only split repository, treat the package as frozen third-party code: pin the version, vendor or fork it, and assign a named owner for the PHPCS 4 migration. Ongoing maintenance is otherwise near-zero — sniffs require no runtime updates, no dependency CVE triage of consequence (it is dev-only and never deployed), and no schema or API surface to track.
Support. Community-only, single maintainer, no commercial backing and realistically no timely upstream fixes. Acceptable precisely because the blast radius is a CI lint failure, not a production incident; a broken sniff is disabled in one line of phpcs.xml.dist. Do not build any workflow that cannot function for a week with this dependency removed.
Scaling. Negligible. The sniff piggybacks on the existing PHPCS pass — one additional token traversal per file, sub-second on typical Laravel codebases, and it parallelizes with phpcs --parallel. It scales linearly with file count like every static-analysis tool, so the real lever is the same as your existing lint stage: cache, parallelize, and restrict full runs to CI while developers lint changed files locally. No memory or I/O profile concerns.
Failure modes to plan for.
App\ and App\Modules\ both mapping into app/ can produce ambiguous or wrong expected paths if longest-prefix matching is not implemented. Test this explicitly with a fixture module.--strict-psr in CI to close the gap.Ramp-up. Effectively zero for anyone who has touched phpcs.xml. The genuine onboarding work is cultural, not technical: developers need to internalize that PSR-4 is case-sensitive and one-class-per-file, and that a local green run does not imply a CI green run if their filesystem is case-insensitive. Budget a short note in the contributing guide and expect a brief spike in CI failures in the first two sprints after enforcement flips to error-level — that spike is the tool doing its job.
How can I help you explore Laravel packages today?