phpstan-symfony icon indicating copy to clipboard operation
phpstan-symfony copied to clipboard

ServiceSubscriberTrait

Open specdrum-agc opened this issue 2 years ago • 5 comments

I have an issue with ServiceSubscriberTrait (https://symfony.com/doc/5.4/service_container/service_subscribers_locators.html#service-subscriber-trait) on symfony 5.4 and PHP 7.4. Error message is like Service "App\Service\MyService::router" is not registered in the container.

How to reproduce:

  1. Install this extesion
  2. Add config as described in https://github.com/phpstan/phpstan-symfony#configuration
// phpstan.neon
    symfony:
        containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
  1. Create class MyService and copy code from symfony docs.
// src/Service/MyService.php
<?php

declare(strict_types=1);

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;

class MyService implements ServiceSubscriberInterface
{
    use ServiceSubscriberTrait;

    public function doSomething()
    {
        // $this->router() ...
        // $this->logger() ...
    }

    #[SubscribedService]
    private function router(): RouterInterface
    {
        return $this->container->get(__METHOD__);
    }

    #[SubscribedService]
    private function logger(): LoggerInterface
    {
        return $this->container->get(__METHOD__);
    }
}
  1. Run phpstan

The output will be:

 ------ ------------------------------------------------------------------
  Line   src/Service/MyService.php
 ------ ------------------------------------------------------------------
  17     Method App\Service\MyService::doSomething() has no return type
         specified.
  24     Method App\Service\MyService::router() is unused.
  26     Service "App\Service\MyService::router" is not registered in the
         container.
  30     Method App\Service\MyService::logger() is unused.
  32     Service "App\Service\MyService::logger" is not registered in the
         container.
 ------ ------------------------------------------------------------------

specdrum-agc avatar Jan 04 '23 10:01 specdrum-agc

What's the nature of your issue? What output do you expect instead? What do you need better understanding of?

ondrejmirtes avatar Jan 04 '23 10:01 ondrejmirtes

I would like that here wouldn't have errors of type Service is not registered in the container., as Symfony will inject defined services into the service locator injected into MyService.

specdrum-agc avatar Jan 04 '23 10:01 specdrum-agc

<?php

namespace App;

use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;

class Foo implements ServiceSubscriberInterface
{
    use ServiceSubscriberTrait;

    #[SubscribedService]
    private function logger(): LoggerInterface
    {
        return $this->container->get(__METHOD__);
    }
}

Symfony is using the method name as local name for the service inside injected service locator. The real service is configured by the return type of the method marked as SubscribedService.

So phpstan should not check for service with name Foo::logger. Instead it could check for LoggerInterface service.

It is related to https://github.com/phpstan/phpstan-symfony/pull/352. The following code is the same as above but without ServiceSubscriberTrait and SubscribedService:

<?php

namespace App;

use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

class Foo implements ServiceSubscriberInterface
{
    public function __construct(
        private readonly ContainerInterface $locator
    ) {}

    public static function getSubscribedServices(): iterable
    {
        return [
            // inject LoggerInterface service and use the method name as local name for the service inside this class
            self::class.'::logger' => LoggerInterface::class,
        ];
    }

    private function logger(): LoggerInterface
    {
        return $this->container->get(__METHOD__);
    }
}

gharlan avatar Aug 29 '23 15:08 gharlan

Yeah, it would be nice to have phpstan-symfony understand this out of the box.

My workaround for now: Just ignoring the error in phpstan.neon:

parameters:
    ignoreErrors:
        - '#Service ".+::get[a-zA-Z0-9]+" is not registered in the container.#'

Of course, that way you'd also miss invalid service names that accidentally match .*get:[a-zA-Z0-9]+, but I just assume that rarely happens.

It would be better to ignore container calls from methods that have the #[SubscribedService] attribute (or check of a service for the return type of that method exists).

pableu avatar Sep 14 '23 09:09 pableu

Sorry, but are there any updates on the issue using the #[SubscribedService] attribute inside a class?

Fyi: Using this inside a Trait works, but thats not always the best solution.

E.g:

trait LoggerAwareTrait
{
    #[SubscribedService]
    protected function logger(): LoggerInterface
    {
        return $this->container->get(__CLASS__ . '::' . __FUNCTION__);
    }
}

hadl avatar Aug 02 '24 10:08 hadl