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.
@required/#[Required] autowiring, PHP config files (Symfony 5.3+), and console command structures. It is a leading indicator of upgrade breakage: run it against the next major Symfony version in a branch and the error delta becomes your migration backlog. This directly de-risks a framework upgrade epic and lets you scope it with evidence rather than estimates.HandleTrait wrapper configuration means CQRS/query-bus abstractions stop being mixed and start being typed—this is the difference between a refactor you can measure and one you hope went well.$container->get(), Controller::get(), ParameterBagInterface::get(), SerializerInterface::deserialize(), Request::getContent(), form errors, cache callbacks, and mailer envelopes propagate into the editor. New hires get accurate autocompletion and inline errors on day one instead of tribal knowledge.KernelTestCase::getContainer() correctly and supports console application loaders, you can analyze test code and console tooling, not just src/. That extends the quality gate to the areas where wiring bugs most often hide.Adopt when:
var/cache/dev/*_KernelDevDebugContainer.xml artifact or can generate one in CI (container cache warmup in the pipeline). This is the single largest adoption prerequisite and worth validating with a spike before committing.Look elsewhere or defer when:
consoleApplicationLoader and container XML dependencies make this a pipeline change, not a one-line composer require.Practical adoption notes a PM should plan for: budget a spike to prove container XML generation in CI; decide up front whether constantHassers: false is acceptable (it hides genuine errors—default to leaving it on); and treat the scanDirectories/scanFiles settings for Symfony 5.3+ PHP config as required configuration, not optional polish.
For executives (one paragraph). This is a free, MIT-licensed, dev-only add-on that makes our static analysis Symfony-aware. In plain terms: it turns "the app starts and the tests pass" into "every service reference, console argument, and query-bus return type is verified automatically on every pull request." Setup is a Composer dev dependency plus a CI config line, with no runtime footprint and no production risk. The payoff is fewer container-wiring and configuration defects reaching staging, faster and safer Symfony upgrades, and a shorter ramp for new engineers—at effectively zero licensing cost. The main cost is engineering time to establish a clean baseline and keep the analyzer current.
For engineering leads. PHPStan alone does not understand Symfony's container, so $container->get() returns object/mixed and every downstream call is untyped. This extension supplies that missing type information: correct returns for ContainerInterface, Controller/AbstractController, ParameterBagInterface, Request::getContent(), InputBag, ResponseHeaderBag, serializer denormalization, form errors, cache callbacks, and HandleTrait::handle() via configurable wrappers. It also adds rules that flag unregistered services, private services fetched directly, and undefined or invalid console arguments and options, and it recognizes #[Required]/@required autowiring. Configuration is a container XML path plus optional console application loader and scanDirectories/scanFiles entries for PHP config. It is dev-only, MIT, and maintained by the PHPStan organization, so maintenance risk is low and the upgrade path tracks PHPStan releases—note that 2.1.0 requires PHPStan 2.3.0, so plan analyzer and extension upgrades together.
For the team doing the work. Start with a timeboxed spike: generate the dev container XML in CI, wire up extension.neon and rules.neon, and run at your current PHPStan level with a baseline. Publish the error count and top error categories—that number is the business case. Then ratchet: one level per sprint, no baseline growth allowed, and a rule that any new service or console command must analyze clean. Add consoleApplicationLoader early if console commands matter, and add messenger.handleTraitWrappers as soon as a query bus exists, since that converts mixed into real types across the codebase.
constantHassers. Disabling it hides real errors. Mitigation: keep the default; only disable per-project with a documented justification.How can I help you explore Laravel packages today?