通过 "container.service_subscriber" 向控制器添加服务未按预期工作

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

我正在尝试在我的控制器上使用 container.service_subscriber 标记来提供一些服务,而无需通过构造函数注入它们。在我们的项目中,我们不想使用 autowiring,也不能使用自动配置选项。

Controller的结构如下:

我有一个基础 BaseController,它从 FOSRestBundle 的 AbstractFOSRestController 扩展而来,它有一些适用于我所有控制器的常用方法。该服务将用作我的其他控制器的 parent

服务定义如下所示:

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' }

class 看起来像这样:

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

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

我遇到的问题是,如果我设置 autowire: false,它总是会自动设置完整的容器和相应的弃用消息(因为我没有自己设置):

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

当设置 autowire: true 时,Symfony 确实尊重 container.service_subscriber 标签并且只设置部分容器(ServiceLocator),这也将解决弃用消息。我原以为自动装配在这种情况下不会有任何区别,因为我明确告诉服务它应该有哪些其他服务。
我是不是使用了错误的标签,或者我在理解如何向 Controller 订阅服务时遇到了一般性问题?

基本问题是内置服务订阅者功能只会将服务定位器注入构造函数。扩展 AbstractController 的传统控制器使用自动配置基本上覆盖它并使用 setContainer 而不是构造函数。

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

结果:

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

表示正在注入服务定位器(与全局容器相对)。

您还可以将服务配置为使用 setContainer 方法并消除对构造函数的需要。两种方法都行得通。

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

问题的解决方案是通过调用 setContainer 来扩展 Controller 的服务定义以注入 '@Psr\Container\ContainerInterface' 服务:

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' }

这将给我一个 ServiceLocator 作为仅包含已注册服务的容器而不是完整的容器,而不使用 autowire 选项。
旁注:设置 @service_container 将注入完整的容器。

为了完整起见,symfony 项目上已经有一个 issue 讨论了这个问题。