Adding services to a Controller through “container.service_subscriber” not working as expected

社会主义新天地 提交于 2021-01-26 02:07:50

问题


I am trying to use the container.service_subscriber tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring and also can't use the autoconfigure option.

The structure of the Controller is as follow:

I have a base BaseController which extends from the AbstractFOSRestController of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent for my other Controllers.

The service definition looks like this:

WM\ApiBundle\Controller\BaseController:
    class: WM\ApiBundle\Controller\BaseController
    abstract: true
    arguments:
        - "@service1"
        - "@service2"
        - ...

WM\ApiBundle\Controller\UserController:
    parent: WM\ApiBundle\Controller\BaseController
    public: true
    #autowire: true
    class: WM\ApiBundle\Controller\UserController
    tags:
        - { name: 'container.service_subscriber'}
        - { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }

The class looks like this:

/**
 * User controller.
 */
class UserController extends AbstractCRUDController implements ClassResourceInterface
{

    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
        'servicexyz' => ServiceXYZ::class,
        ]);
    }
   .......
}

The problem I have is, if I set autowire: false, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):

User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.

When setting autowire: true Symfony does respect the container.service_subscriber tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?


回答1:


The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.

# ApiBundle/Resources/config/services.yaml
services:
  _defaults:
    autowire: false
    autoconfigure: false

  Api\Controller\UserController:
    public: true
    tags: ['container.service_subscriber']
class UserController extends AbstractController
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
            // ...
            'logger' => LoggerInterface::class,
        ]);
    }
    public function index()
    {
        $url = $this->generateUrl('user'); // Works as expected

        // $signer = $this->get('uri_signer'); // Fails as expected

        $logger = $this->get('logger'); // Works as expected

        return new Response('API Index Controller ' . get_class($this->container));
    }
}

Results in:

    API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator

Indicating that a service locator (as opposed to the global container is being injected).

You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.

  Api\Controller\UserController:
    public: true
    tags: ['container.service_subscriber']
    calls: [['setContainer', ['@Psr\Container\ContainerInterface']]]




回答2:


Solution to the problem is to extend the service definition of the Controller with a call to setContainer to inject the '@Psr\Container\ContainerInterface' service:

WM\ApiBundle\Controller\BaseController:
    class: WM\ApiBundle\Controller\BaseController
    abstract: true
    arguments:
        - "@service1"
        - "@service2"
        - ...
    calls: 
        - ['setContainer', ['@Psr\Container\ContainerInterface']]

WM\ApiBundle\Controller\UserController:
    parent: WM\ApiBundle\Controller\BaseController
    public: true
    class: WM\ApiBundle\Controller\UserController
    tags:
        - { name: 'container.service_subscriber'}
        - { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }

This will give me a ServiceLocator as container containing only the regiestered services instead of the full container without using the autowire option.
Sidenote: Setting the @service_container would inject the full container.

For completeness, there was already an issue on the symfony project where this was discussed.



来源:https://stackoverflow.com/questions/60078704/adding-services-to-a-controller-through-container-service-subscriber-not-worki

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!