Skip to content

Do not resolve ServiceLocator get()/has() against the global container#503

Draft
janedbal wants to merge 1 commit into
phpstan:2.0.xfrom
janedbal:jn-servicelocator-not-container
Draft

Do not resolve ServiceLocator get()/has() against the global container#503
janedbal wants to merge 1 commit into
phpstan:2.0.xfrom
janedbal:jn-servicelocator-not-container

Conversation

@janedbal

Copy link
Copy Markdown
Contributor

ServiceDynamicReturnTypeExtension and ContainerInterfaceUnknownServiceRule are registered for Psr\Container\ContainerInterface, which also matches an injected Symfony\Component\DependencyInjection\ServiceLocator (e.g. from #[AutowireLocator]). A locator's keys are local index keys (tag index attributes), not global container service ids — but both consulted the global ServiceMap.

Consequences for a ServiceLocator:

  • has('key') collapsed to a constant false (the key isn't a global service id), so if (!$locator->has('key')) { return; } became "always true" and the rest of the method unreachable;
  • get('key') was wrongly reported with symfonyContainer.serviceNotFound.

This PR skips ServiceLocator receivers in both places, leaving get()/has() to native (generic) inference. The global container, controllers, and Psr-typed ServiceSubscriber locators are unaffected (a ServiceLocator is not their static type; the existing testGetPrivateServiceInLegacyServiceSubscriber still passes).

Discovered via shipmonk-rnd/dead-code-detector#376 — the unreachable code led a dead-code detector to report the handler methods behind the locator as unused. Repro & analysis: shipmonk-rnd/dead-code-detector#376 (comment)

The ServiceDynamicReturnTypeExtension and ContainerInterfaceUnknownServiceRule
are registered for Psr\Container\ContainerInterface, which also matches an
injected Symfony\Component\DependencyInjection\ServiceLocator (e.g. from
#[AutowireLocator]). Their keys are local index keys (tag index attributes),
not global container service ids, but both looked the key up in the global
ServiceMap.

For a locator that meant has('key') collapsed to a constant `false` (the key is
not a global service id), so a `if (!$locator->has('key')) { return; }` guard
became "always true" and the rest of the method turned unreachable. get('key')
was also wrongly flagged with symfonyContainer.serviceNotFound.

Skip ServiceLocator receivers in both places, leaving get()/has() to native
(generic) inference. The global container, controllers and Psr-typed
ServiceSubscriber locators are unaffected (ServiceLocator is not their type).

Reported via shipmonk-rnd/dead-code-detector#376, where the resulting
unreachable code caused false unused-method reports.

Co-Authored-By: Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant