The new official symfony/language-tools (symfony-lsp) ships a set of 28 diagnostics, enumerated in src/Check/DiagnosticCodeRegistry.php and described in docs/features/*.rst. Several of them target PHP code and would make good phpstan-symfony rules. This issue maps their diagnostics against what we already have and ranks the candidates.
Already covered
service.not_found — covered by ContainerInterfaceUnknownServiceRule for $container->get() / controller get(). Language-tools additionally checks #[Autowire(service: '...')] attributes, which we don't touch at all (see candidate 1 below). They have no counterpart to our ContainerInterfacePrivateServiceRule or the four console rules.
A few more are covered indirectly by the type system rather than by a rule: the messenger handler/message pairing feeds MessengerHandleTraitReturnTypeExtension via MessageMap, and hasParameter() narrowing (with constantHassers) catches some parameter mistakes — but we never report an unknown parameter or a bad handler signature.
Candidates for new rules (ranked by effort-to-value)
-
parameter.not_found — the most obvious gap. ParameterMap already exists; a rule reporting getParameter('unknown') would be exactly symmetric to ContainerInterfaceUnknownServiceRule. Today ParameterDynamicReturnTypeExtension silently falls back to the general union type when the parameter isn't in the map. Natural follow-up: cover #[Autowire(param: ...)] and #[Autowire('%foo%')] (we currently have zero Autowire attribute support).
-
event.invalid_listener_method — class-level #[AsEventListener(method: 'onFoo')] where the method doesn't exist on the class. Pure reflection, no container XML needed, zero-config for users.
-
route.not_found + route.missing_parameters — generateUrl() / redirectToRoute() / UrlGeneratorInterface::generate() with an unknown route name, or a literal parameter array missing required path/host placeholders (parameters with route defaults are optional; dynamic arrays are skipped). Routes aren't in the container XML, so this needs a new data source — a routerLoader config option following the consoleApplicationLoader pattern (load the Kernel/Router, read the RouteCollection). Probably the highest user value on this list.
-
messenger.invalid_handler_signature — a handler whose first parameter type is incompatible with the message class Messenger routes to it (their version flags scalar params receiving object messages). MessageMapFactory already parses the messenger.message_handler tags, so a rule comparing the handler's parameter type against the handled message class is mostly plumbing we have.
-
template.not_found — literal template names in render() / renderView() / #[Template] checked against Twig filesystem loader paths. Needs a new config surface (Twig paths or a Twig environment loader). Medium effort, high value.
-
translation.placeholders (and optionally translation.not_found / domain_not_found) — for a known message, report %placeholder%s the message expects that a supplied literal parameter map doesn't provide (extra params allowed, dynamic arrays skipped). Needs loading the translation catalogue. Note language-tools ships missing-key diagnostics off by default because external translation providers can make the runtime catalogue incomplete — worth mirroring that caution.
-
form.unknown_option — unknown literal options for a statically-known form type in createForm() / createNamed() / $builder->add(). Highest value of the "hard" ones, but it requires resolving the full OptionsResolver chain (language-tools does it via runtime indexing). A formFactoryLoader-style runtime approach à la ConsoleApplicationResolver is realistic; static resolution of configureOptions() is not.
Out of scope for PHPStan
The remaining diagnostics target non-PHP files: the five config.* codes (YAML config vs. bundle Configuration trees), env.* (mostly %env(...)% in YAML — though #[Autowire(env: 'json:FOO')] could give an unknown-processor rule a small PHP surface), security.unknown_firewall / security.unknown_provider (security.yaml), messenger.unknown_bus / messenger.unknown_transport (messenger.yaml), stimulus.unknown_controller, twig_callable.unknown_argument, twig_component.not_found, and importmap.unknown_entrypoint (Twig/HTML).
validation.unknown_constraint_option is largely moot in PHP: with attribute constraints PHPStan core already validates named constructor arguments, and the legacy options-array style is deprecated in Symfony 7.
The new official symfony/language-tools (
symfony-lsp) ships a set of 28 diagnostics, enumerated insrc/Check/DiagnosticCodeRegistry.phpand described indocs/features/*.rst. Several of them target PHP code and would make good phpstan-symfony rules. This issue maps their diagnostics against what we already have and ranks the candidates.Already covered
service.not_found— covered byContainerInterfaceUnknownServiceRulefor$container->get()/ controllerget(). Language-tools additionally checks#[Autowire(service: '...')]attributes, which we don't touch at all (see candidate 1 below). They have no counterpart to ourContainerInterfacePrivateServiceRuleor the four console rules.A few more are covered indirectly by the type system rather than by a rule: the messenger handler/message pairing feeds
MessengerHandleTraitReturnTypeExtensionviaMessageMap, andhasParameter()narrowing (withconstantHassers) catches some parameter mistakes — but we never report an unknown parameter or a bad handler signature.Candidates for new rules (ranked by effort-to-value)
parameter.not_found— the most obvious gap.ParameterMapalready exists; a rule reportinggetParameter('unknown')would be exactly symmetric toContainerInterfaceUnknownServiceRule. TodayParameterDynamicReturnTypeExtensionsilently falls back to the general union type when the parameter isn't in the map. Natural follow-up: cover#[Autowire(param: ...)]and#[Autowire('%foo%')](we currently have zeroAutowireattribute support).event.invalid_listener_method— class-level#[AsEventListener(method: 'onFoo')]where the method doesn't exist on the class. Pure reflection, no container XML needed, zero-config for users.route.not_found+route.missing_parameters—generateUrl()/redirectToRoute()/UrlGeneratorInterface::generate()with an unknown route name, or a literal parameter array missing required path/host placeholders (parameters with route defaults are optional; dynamic arrays are skipped). Routes aren't in the container XML, so this needs a new data source — arouterLoaderconfig option following theconsoleApplicationLoaderpattern (load the Kernel/Router, read theRouteCollection). Probably the highest user value on this list.messenger.invalid_handler_signature— a handler whose first parameter type is incompatible with the message class Messenger routes to it (their version flags scalar params receiving object messages).MessageMapFactoryalready parses themessenger.message_handlertags, so a rule comparing the handler's parameter type against the handled message class is mostly plumbing we have.template.not_found— literal template names inrender()/renderView()/#[Template]checked against Twig filesystem loader paths. Needs a new config surface (Twig paths or a Twig environment loader). Medium effort, high value.translation.placeholders(and optionallytranslation.not_found/domain_not_found) — for a known message, report%placeholder%s the message expects that a supplied literal parameter map doesn't provide (extra params allowed, dynamic arrays skipped). Needs loading the translation catalogue. Note language-tools ships missing-key diagnostics off by default because external translation providers can make the runtime catalogue incomplete — worth mirroring that caution.form.unknown_option— unknown literal options for a statically-known form type increateForm()/createNamed()/$builder->add(). Highest value of the "hard" ones, but it requires resolving the fullOptionsResolverchain (language-tools does it via runtime indexing). AformFactoryLoader-style runtime approach à laConsoleApplicationResolveris realistic; static resolution ofconfigureOptions()is not.Out of scope for PHPStan
The remaining diagnostics target non-PHP files: the five
config.*codes (YAML config vs. bundle Configuration trees),env.*(mostly%env(...)%in YAML — though#[Autowire(env: 'json:FOO')]could give an unknown-processor rule a small PHP surface),security.unknown_firewall/security.unknown_provider(security.yaml),messenger.unknown_bus/messenger.unknown_transport(messenger.yaml),stimulus.unknown_controller,twig_callable.unknown_argument,twig_component.not_found, andimportmap.unknown_entrypoint(Twig/HTML).validation.unknown_constraint_optionis largely moot in PHP: with attribute constraints PHPStan core already validates named constructor arguments, and the legacy options-array style is deprecated in Symfony 7.