Skip to content

New rule ideas inspired by symfony/language-tools diagnostics #509

Description

@ondrejmirtes

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)

  1. 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).

  2. 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.

  3. route.not_found + route.missing_parametersgenerateUrl() / 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions