Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ServiceSubscriberTrait #321

Open
specdrum-agc opened this issue Jan 4, 2023 · 5 comments
Open

ServiceSubscriberTrait #321

specdrum-agc opened this issue Jan 4, 2023 · 5 comments

Comments

@specdrum-agc
Copy link

specdrum-agc commented Jan 4, 2023

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.
 ------ ------------------------------------------------------------------
@ondrejmirtes
Copy link
Member

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

@specdrum-agc
Copy link
Author

specdrum-agc commented Jan 4, 2023

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.

@gharlan
Copy link

gharlan commented Aug 29, 2023

<?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 #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__);
    }
}

@pableu
Copy link

pableu commented Sep 14, 2023

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

@hadl
Copy link

hadl commented Aug 2, 2024

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__);
    }
}

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 a pull request may close this issue.

5 participants