phpstan/phpstan-symfony
PHPStan extension for Symfony that provides accurate return types for the container, controllers, requests, responses, forms, serializer, cache, and other framework APIs, helping catch bugs earlier and improving static analysis in Symfony projects.
What it is. phpstan/phpstan-symfony is a developer-only PHPStan extension that teaches static analysis about Symfony's dynamic runtime contracts. Symfony's container, console input, serializer, form, and Messenger APIs are all string-keyed and return mixed/object natively; this package reads the compiled DI container dump and stubs those methods so PHPStan can infer real types and validate service IDs, console argument/option names, and default values.
Architecture fit. Strong fit for any team already running PHPStan on a Symfony codebase, and effectively only for that combination — it is a bolt-on to two existing dependencies and adds no runtime code to the application (no bundle, no autoloader entry in prod, no production traffic). Blast radius is limited to the static analysis pipeline. The value proposition is precise: it removes a well-known blind spot ($container->get() returning mixed, console getArgument() returning mixed, deserialize() returning mixed) that otherwise silently disables type checking downstream of nearly every service lookup. The optional messenger.handleTraitWrappers inference is a genuine differentiator for CQRS/query-bus code, where every dispatch result is mixed without it.
Integration feasibility. Mechanically trivial (composer require --dev, plus phpstan/extension-installer or two includes: lines). The real work is operational: the extension needs a compiled container XML at analysis time (var/cache/dev/App_KernelDevDebugContainer.xml), which means CI must warm the Symfony cache with a known APP_ENV before PHPStan runs. Teams with a build stage already get this nearly free; teams that run PHPStan directly against source do not. Symfony 5.3+ PHP-config users additionally need scanDirectories and scanFiles entries, and the console-analysis feature typically requires a dedicated phpstan_env with container.dumper.inline_class_loader: false to avoid a PhpParser class collision.
Technical risk.
constantHassers: false is documented as potentially hiding genuine errors; consoleApplicationLoader boots a real kernel during analysis (slower, side effects, possible deprecation noise).Dependents: 0 figure is an artifact of dev-only consumption and understates reality; weigh instead the phpstan org ownership, MIT license, 806 stars, and visible release cadence. The 55/86 score split reads as "high opportunity, moderate readiness" — i.e., the prerequisites, not the package, are the constraint.Key questions.
APP_ENV should own the analysis container, and will we commit to a dedicated phpstan env?rules.neon checks at launch or defer?Stack fit. Requires PHPStan (2.x for extension 2.x), a Symfony application with a compiled container, and Composer. No requirements on the runtime stack, web server, or hosting model. Ideal profile: PHP 8.x, Symfony 5.4+/6.x/7.x, PHPStan already gating PRs, and a build step that produces var/cache.
Migration path. The migration is baseline-shaped, not code-shaped:
mixed values currently flow from container/console/serializer calls (grep for ->get(, getArgument(, getOption(, deserialize() to size the payoff.extension.neon) — no new rules — and regenerate the baseline. Expect new errors where previously-untyped values are now typed, and disappearing ignored errors where a manual @var or ignore comment is now redundant. Set reportUnmatchedIgnoredErrors deliberately during this window.@var annotations on $container->get() results; they will be validated against inferred types and can conflict.rules.neon (unregistered service, private service, undefined console argument/option, default-value validation) as a second, separately-baselined step.messenger.handleTraitWrappers for query buses, then consoleApplicationLoader if console coverage matters, with the phpstan_env workaround.Compatibility. Symfony 4.2+ through 7.x are covered with different container XML filenames — pick the path matching the installed version. Older applications on PHPStan 1.x stay on extension 1.4.x. The phpstan/extension-installer route is the least-maintenance; manual includes: is the fallback for teams that vendor control their config.
Sequencing. Ship in a dedicated PR that touches only the analysis config and baseline, behind no feature flag but with a rollback of one line. Land it before a Symfony upgrade, not after, so the extension's stubs are already aligned when signatures move. Announce it as "CI now requires a warmed cache" — that's the only externally visible change.
Maintenance. Low and routine: a dev dependency with no runtime footprint and no production deployment path. The recurring cost is upgrade pairing — PHPStan and this extension move together, so each PHPStan bump is a two-package change with a rebuild of the container in CI. Budget a small, predictable trickle of PRs rather than a project.
Support. Community OSS under MIT, maintained within the PHPStan organization; no vendor SLA, no commercial support tier. Support surface is GitHub issues and the README, which is good but assumes fluency with PHPStan configuration. Internal ownership should sit with whoever owns the PHPStan baseline, not with application teams.
Scaling. No scaling dimension in the usual sense — no traffic, no memory ceiling, no latency budget. The only scaling axes are wall-clock analysis time (parsing container XML; booting a kernel if consoleApplicationLoader is enabled) and repository size. Both grow linearly and predictably; the 2.1.0 cache-invalidation improvement (invalidating results based on which container entries analysed code touches) keeps incremental reruns cheap.
Failure modes.
containerXmlPath — extension degrades or errors; most common first-day failure.phpstan_env / inline_class_loader: false workaround.constantHassers: false — only relevant if that flag is used; default it to on.Ramp-up. For engineers already fluent in PHPStan, under a day: install, point at the container XML, regenerate baseline, done. For teams new to PHPStan, this extension is not the place to start — land PHPStan first at a modest level, then add this. The one conceptual hurdle to socialize is that CI now depends on a built artifact (the container dump), which changes the mental model from "lint the source" to "analyze the source against a built application." Document that explicitly and the ramp-up is otherwise unremarkable.
How can I help you explore Laravel packages today?