php-standard-library/psalm-plugin
Psalm plugin for PHP Standard Library (PSL) that improves static analysis of PSL Type specifications. It gives Psalm precise inferred types for shapes and coerce results, such as exact array shapes instead of broad unions. Install with Composer as a dev dependency and enable via psalm-plugin.
Architecture fit. The plugin is a dev-only Psalm extension that registers return-type providers for PSL's Type component (plus a long list of Str, Iter, and Regex functions). It has zero runtime footprint, no service surface, and no data path — it only changes what Psalm infers. For a Laravel codebase the fit is conditional: it adds value only where PSL is already (or will be) the validation/typing layer, e.g. Type\shape() used for request payloads, webhook bodies, config schemas, or third-party API ingestion. In a codebase that uses Laravel's Validator and plain arrays, the plugin is inert.
Integration feasibility. Mechanically trivial: composer require php-standard-library/psalm-plugin --dev and php vendor/bin/psalm-plugin enable php-standard-library/psalm-plugin. The real constraint is organizational, not technical — the Laravel ecosystem's default static analysis stack is PHPStan + Larastan, not Psalm. Adopting this plugin presupposes Psalm is already the analyzer of record, or that the team is willing to run a second analyzer. That is the gating decision, not the install.
Technical risk. Blast radius is low (dev dependency, removable in one commit), but several risks are non-obvious:
array<...> unions. You lose precision without a signal, which can create false confidence in CI gates.php-standard-library org, which is reassuring, but it is not a widely exercised dependency — we should expect to be early adopters encountering edge cases.Key questions for the team.
@psalm-trace snapshot assertions in CI)?Stack fit. Compatible with any Laravel/PHP project on the Composer side, but the analysis stack is the deciding factor:
| Our current state | Fit | Implication |
|---|---|---|
| Psalm already in CI | Strong | Add plugin, pin versions, done |
| PHPStan + Larastan only | Weak | Requires standing up Psalm (and a Laravel-specific Psalm config) or running dual analyzers |
| No static analysis | Poor | Must first adopt Psalm and PSL; plugin is the last step, not the first |
| PHPStan + PSL, no Psalm | Weak | PSL works fine without Psalm; plugin delivers nothing until Psalm exists |
Note that Larastan is PHPStan-only, so a Psalm-first posture means either losing Laravel-specific framework modeling or maintaining two configurations. That is the single largest adoption cost and should be surfaced to whoever owns developer tooling before any pilot.
Migration path. Additive and reversible:
require-dev dependency; confirm it lands in require-dev only and never in the production autoload.psalm-plugin enable, commit the resulting psalm.xml change.@psalm-trace on a handful of known PSL call sites to prove the plugin is actually firing.Compatibility. Pin the triple per the published matrix: PSL 2.x ↔ plugin ^2.1 ↔ Psalm v5; PSL 2.x ↔ plugin ~2.0.0 ↔ Psalm v4; PSL 1.x ↔ plugin 1.x ↔ Psalm v4. In a monorepo or a multi-package Laravel estate, enforce the pin at the root and in CI, because a transitive Psalm bump in one workspace can silently mismatch the plugin.
Sequencing.
Type\shape() for request/webhook payloads — without the plugin.Maintenance. Low steady-state cost, concentrated in upgrade events. The plugin must track both Psalm and PSL, two independently moving upstreams, and its release history shows reactive fixes (PHP 8.2 deprecations, Psalm 5 support, TList deprecation cleanup). Budget a named owner for the three-way version pin and expect work at each Psalm major, not continuously.
Support. Community OSS under MIT: GitHub issues, no SLA, no vendor to escalate to. Repo hygiene is reasonable — README, changelog, tagged releases, static-analysis and type-coverage badges — which is a positive signal for a small project. Practically, we should assume we are our own first line of support and that upstream response time is best-effort.
Scaling. Analysis runtime scales with codebase size; the plugin's marginal cost is small relative to Psalm itself. At scale the dominant costs are Psalm's own runtime, baseline management, and CI minutes — mitigate with incremental/baselined runs and caching. The plugin introduces no runtime scaling concerns whatsoever.
Failure modes.
| Failure | Detection | Impact |
|---|---|---|
| Plugin not enabled / provider not registered | None by default — types silently widen | False confidence in CI; schema drift goes uncaught |
| Psalm major upgrade without plugin upgrade | Composer resolve failure or provider runtime errors | CI red or degraded inference |
| PSL upgrade without plugin upgrade | Widened inferred types at converted call sites | Same as above, quietly |
| Provider bug on a given function/shape form | Only via trace inspection or escaped defects | Missed bugs, not noisy false positives |
| Plugin abandoned upstream | Stale constraints block Psalm/PSL upgrades | Forced removal and rework of call sites |
The first and fourth rows are the ones to design against. Both are addressed cheaply by pinning a small number of @psalm-trace (or equivalent asserted-type) fixtures on representative PSL shapes and running them in CI, so the plugin going quiet is a red build rather than a slow decline in coverage.
Ramp-up. Developer learning cost is near zero — no API to call, no runtime behavior to reason about. The actual ramp-up is on PSL's Type API (shape(), optional(), coerce() vs parse() semantics), which the team needs regardless of the plugin. A one-module pilot is realistically a sprint of part-time effort including the trace fixtures; broad rollout is then mostly a matter of converting call sites on the normal roadmap rather than a dedicated program. Recommended posture: adopt as a dev-only, low-commitment enhancement conditional on Psalm being (or becoming) the analyzer of record; if the team is PHPStan/Larastan-native, do not stand up a second analyzer for this alone.
How can I help you explore Laravel packages today?