通过 "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 讨论了这个问题。
我正在尝试在我的控制器上使用 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 讨论了这个问题。