phpstan / phpstan-symfony

Symfony extension for PHPStan
MIT License
698 stars 89 forks source link

Resolve services subscribed to with ServiceSubscriberTrait #366

Closed RobertMe closed 10 months ago

RobertMe commented 10 months ago

First off I do want to note that I've only looked at PHPStan source code for about a day. So please bear with me, and I'm certain this code can be cleaned up given some tips :)

So, the actual PR: Symfony has the feature of "service subscribers" where a service / class can implement the ServiceSubscriberInterface having a getSubscribedServices method returning a mapping of desired services / service ids to be injected and their name in the actual container. The DI container will then create a slimmed down container / locator with the requested services. This feature however isn't understood by this extension at all. So when requesting the service Foo using the name Bar (defined ['Bar' => 'Foo'] in getSubscribedServices(), requested as container->get('Bar')) this extension will "fail" and falsely report "Service "Bar" is not registered in the container".

To simplify the usage of this interface / mechanism Symfony also contains a trait, ServiceSubscriberTrait, which uses some "magic" to implement the getSubscribedServices() method. This by scanning all methods in the class, and, in Symfony 6, checking whether they have the #[SubscribedService] attribute. In these cases this method will be "mapped" to requesting a service, by using the method name (equaling __METHOD__) as service id and the return type as service to request (so [<__METHOD__> => <return type of method>]). In Symfony 6 it's also possible to explicitly set the service to request by using #[SubscribedService(attributes: new Autowire(service: '<service id>'))] and some variants (attributes can be an array, previously a value argument was the only argument for Autowire which could also explicitly request a service by prefixing it with @, obviously arguments can be positional as well, ...).

So what this PR tries to solve is to resolve the service id needed from the "actual" container. This by checking whether the method which calls ->get / ->has has this #[SubscribedService] attribute and if so it also tries to find whether Autowired is provided.

At the moment this PR is still missing some bits, mainly:

Further development might be target at actually inspecting getSubscribedServices as well. As this most likely will be a constant array (although it might merge with a parent call). But I have no idea whether that is actually possible and how to implement it

Fixes #321

RobertMe commented 10 months ago

Closing in favor of a completely new PR / solution I'm working on (which uses the container XML to extract the service locators per service class. So will work with most (/any) scenarios, including when getSubscribedServices is implemented manually, when the service locator is created in a compiler pass, when #[TaggedLocator] is used, ...)