spryker/code-sniffer
Spryker Code Sniffer is a PHPCS ruleset for PHP 8.3+ that follows PSR-2 and PSR-12 with extra Spryker convention sniffs. It ships Spryker, SprykerStrict, and opt-in SprykerConventions standards, and works via phpcs/phpcbf.
What this actually is. spryker/code-sniffer is a PHP_CodeSniffer standard, not an application library. It ships two rulesets (Spryker, SprykerStrict) built from PSR-2/PSR-12, Slevomat Coding Standard, and roughly a dozen Spryker-authored sniffs (namespace conventions, method annotations for Facade/Factory/Repository/QueryContainer, disallowed-function/PHP-version safety, strict_types enforcement). There is no runtime code path, no service, no database surface. It is a require-dev static-analysis dependency.
Fit with a Laravel stack. Partial, and this is the core TPM finding. The package is authored for the Spryker modular monolith; its differentiating sniffs encode Spryker architecture (Pyz,SprykerEco,Spryker namespaces, src/Generated, module folder layout). Those sniffs are documented as self-disabling outside Spryker folder structures, so they will mostly no-op in a Laravel codebase rather than break it — but they also deliver no value. What does transfer is the layered baseline: PSR-12 + Slevomat strictness + a well-documented customization model (severity 0 silencing, <exclude>, property overrides). Realistically a TPM should evaluate this as "a reference implementation of a strict PHPCS standard and a pattern for authoring custom sniffs," not as a Laravel coding standard to adopt wholesale.
Integration feasibility. High mechanically: one composer require --dev, one committed phpcs.xml.dist, one CI step. No autoload conflicts, no PHP extension requirements beyond tokenizer, MIT licensed. The friction is governance, not engineering: it overlaps with Laravel Pint (PHP-CS-Fixer engine), so a team that already runs Pint gets two formatters with different opinions, which produces churn and "my IDE reformatted your file" noise.
Technical risk.
installed_paths convenience relies on dealerdirect/phpcodesniffer-composer-installer; Composer 2.2+ blocks plugins unless allow-plugins is set, which breaks CI silently on fresh images.squizlabs/php_codesniffer internals; a PHPCS 4.x migration will require an upstream release and a ruleset re-validation pass.Key questions for the evaluation owner.
SlevomatCodingStandard.Namespaces, ControlStructures, PHP.DeclareStrictTypesAfterFileDoc — and which do we exclude on day one?phpcs.xml.dist and the quarterly upgrade PR?Stack fit. Target PHP 8.2+/Laravel 11–12 repositories. Install as a dev dependency only; never in require. Commit a root phpcs.xml.dist so IDE, local hooks, and CI share one source of truth. Declare allow-plugins for dealerdirect/phpcodesniffer-composer-installer in composer.json or set installed_paths explicitly via --config-set in CI bootstrap to avoid plugin-policy surprises.
Recommended shape — curated ruleset, not the stock Spryker standard. Use a thin project ruleset that references selected sniff families rather than <rule ref="vendor/spryker/code-sniffer/Spryker/ruleset.xml"/> wholesale:
<ruleset name="HouseStandard">
<rule ref="vendor/spryker/code-sniffer/Spryker/ruleset.xml">
<exclude name="Spryker.Namespaces.SprykerNamespace"/>
<exclude name="Spryker.MethodAnnotation.*"/>
<exclude name="Spryker.Internal.SprykerDisallowFunctions"/>
</rule>
</ruleset>
This preserves the Slevomat/PSR-12 value while dropping Spryker-architecture coupling. Start with Spryker (not SprykerStrict); strict is a later ratchet, not a starting point.
Compatibility and co-existence.
phpcbf so it never rewrites files Pint owns.vendor/, storage/, bootstrap/cache/, public/build/, and generated fixture folders (tests/_support/_generated, test_files) to avoid scanning non-source trees.severity 0 or <exclude> rather than editing vendor rules; keeps upgrades diff-clean.~ or an exact version — the package's own migration guidance explicitly recommends this for repositories with many contributors.Sequencing.
vendor/bin/phpcs --standard=.../Spryker/ruleset.xml --report=summary ./app on one representative repo. Capture total error count and top 10 sniffs by volume.phpcs.xml.dist, add a non-blocking CI job scoped to changed files (--diff against the merge base). Do not gate the first merge on error count.0 with a linked issue. Target a violation count the team can burn down in one sprint.strict_types later via DeclareStrictTypesAfterFileDoc with strictTypesMandatory=true, with an explicit note that it changes runtime type-casting behavior.phpcs.xml.dist template), not copy-pasted.Maintenance. Low code volume, non-trivial change management. Ruleset upgrades routinely introduce new violations across the whole codebase, so treat this as a dependency with a scheduled maintenance window, not a fire-and-forget dev tool. Adopt the upstream migration pattern: pin to patch/minor, run upgrades on a dedicated master-cs-style branch, merge that, then git merge origin/master into feature branches so no feature PR fails on CS "by accident." Budget ~2–4 hours per quarter for the upgrade + triage cycle, plus ad-hoc time when a sniff author writes rules your team disagrees with.
Support. No SLA, no paid support for the sniffer. Upstream is GitHub issues, vendor-prioritized. Internal ownership is required: one named maintainer for the ruleset, plus a documented process for "I think this sniff is wrong" → internal issue → upstream issue if warranted. Bus factor is effectively one team at one vendor; mitigate by keeping the custom ruleset small enough that replacing the dependency is a day of work, not a quarter.
Scaling. Horizontal scale is trivial — it's a composer dependency, so N repos cost O(N) config files, not O(N) infrastructure. Vertical scale (file count) is the only real cost: PHPCS is single-process by default; use --parallel (or the wrapper's equivalent) on large repos and scope CI to changed files in PRs to keep feedback under ~60–90 seconds. Full-repo scans belong on a nightly job, not the PR gate.
Failure modes and mitigations.
| Failure mode | Likelihood | Blast radius | Mitigation |
|---|---|---|---|
| Upstream release introduces hundreds of new errors | Medium | Blocks all PRs | Pin version; upgrade only via dedicated branch |
| PHPCS 4.x incompatibility | Medium (timing) | CI red across all repos | Monitor upstream; keep PHPCS version pinned in composer.lock |
| Composer plugin blocked on new CI image | Medium | Silent no-op or hard fail | Declare allow-plugins; assert phpcs -i lists the standard in bootstrap |
| Pint/PHPCS format war | High if both run | PR churn, developer frustration | Pint writes, PHPCS reads only; CI checks that formatters are not run together |
Alert fatigue → mass severity 0 silencing |
High | Standard erodes to noise | Require a linked issue for every silencing; review silenced list quarterly |
| IDE config diverges from CI | High | "Passes locally, fails in CI" | Single phpcs.xml.dist; IDE tools point at it, never at inline flags |
| Spryker-specific sniffs false-positive on Laravel idioms | Low | Noise | Exclude Spryker.Namespaces.*, Spryker.MethodAnnotation.* up front |
Ramp-up. Developers: ~1 hour. phpcs/phpcbf are industry-standard tools and the error output is self-explanatory. Ruleset maintainer: ~1–2 days to become fluent in PHPCS XML semantics, Slevomat rule naming (the x.y.z.code granularity for partial silencing), and the property-override model for custom namespaces and phpVersion. Sniff authoring, should you ever need it (e.g. a house rule banning a specific Laravel pattern), is a further 2–3 days and is genuinely niche work — the package does provide a generator (tests/generate.php) and a tokenizer debugging tool that materially reduce that cost.
Net TPM read. Low technical risk, low integration cost, moderate governance cost, and a strategic fit that is weak as a Laravel standard and strong as a curated source of strict PSR-12/Slevomat rules plus a proven pattern for authoring custom sniffs. Approve as a scoped pilot with a curated ruleset and an explicit Pint/PHPCS division of labor; reject a wholesale "adopt the Spryker standard" mandate.
How can I help you explore Laravel packages today?