在控制器的构造函数中自动装配的 Symfony 4.4 服务不会从服务定义 (setContainer) 获得调用
Symfony 4.4 service autowired in controller's constructor doesn't get call from service definition (setContainer)
前言:请忽略注入整个容器和其他不干净的东西,我只是想展示不工作的例子,这是在重构之前。
我在 YAML 中定义了一个服务:
app.service.my_service:
class: App\Services\MyService
public: true
calls:
- [setContainer, ['@service_container']]
我的部分服务:
class MyService implements ContainerAwareInterface
{
/** @var ContainerInterface */
private $container;
/** @var EntityManagerInterface */
private $em;
public function setContainer(?ContainerInterface $container = null)
{
$this->container = $container;
$this->em = $container->get('doctrine')->getManager();
}
然后我有一个控制器,它在构造函数中自动装配了该服务,而不是从容器中实例化:
class MyController
{
/**
* @var MyService
*/
private $my_service;
function __construct(MyService $my_service) {
$this->my_service = $my_service;
}
虽然它实际上自动装配服务本身,它完全忽略了 setContainer
调用,所以我最终得到空容器。
我想避免在任何地方调用 $this->get('app.service.my_service')
,我不能简单地在控制器的构造函数中调用一次,或者在自动装配服务的构造函数中调用 setContainer
,因为那时容器是空的。
我有没有机会以干净的方式做到这一点?
既然你已经知道注入容器是个坏主意,那么我想我就不给你讲课了。我没有遇到这个特殊问题,但您可以尝试使用鲜为人知的“@required”容器指令,看看它是否有帮助。
/** @required */
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
$this->em = $container->get('doctrine.orm.default_entity_manager);
}
这与其说是答案,不如说是猜测,但很容易尝试。
更新:有效。酷
我只是想补充一点,在大多数情况下,应该将依赖项注入到构造函数中。这避免了部分初始化的服务。另一方面,只要您通过容器生成服务,那么这就不是真正的问题。
我发现它在特质方面非常有用。例如:
trait RouterTrait
{
private RouterInterface $router;
/** @required */
public function setRouter(RouterInterface $router)
{
$this->router = isset($this->router) ? $this->router: $router;
}
// These are just copied from AbstractController
protected function generateUrl(
return $this->router->generate(...
protected function redirectToRoute(
#
class SomeService
use RouterTrait;
public function someMethod()
$this->generateUrl('''
因此,如果一个服务需要做一些路由,那么他们只需要使用路由器特性,路由器就会自动注入。将样板代码添加到服务的构造函数中。我确实在 setRouter 周围设置了保护措施,因此它只能被有效地调用一次,这消除了对依赖项使用 setter 的另一个担忧。
更新 #2:
原始代码的问题是 MyService 是用 app.service.my_service 而不是 class 名称的服务定义的。 Autowire 实际上生成了第二个 MyService 并将其注入,因为 autowire 查找与 class 名称匹配的服务 ID。将服务定义更改为:
App\Services\MyService:
public: true
calls:
- [setContainer, ['@service_container']]
也可以。或者明确地将 app.service.my_service 注入控制器。
前言:请忽略注入整个容器和其他不干净的东西,我只是想展示不工作的例子,这是在重构之前。
我在 YAML 中定义了一个服务:
app.service.my_service:
class: App\Services\MyService
public: true
calls:
- [setContainer, ['@service_container']]
我的部分服务:
class MyService implements ContainerAwareInterface
{
/** @var ContainerInterface */
private $container;
/** @var EntityManagerInterface */
private $em;
public function setContainer(?ContainerInterface $container = null)
{
$this->container = $container;
$this->em = $container->get('doctrine')->getManager();
}
然后我有一个控制器,它在构造函数中自动装配了该服务,而不是从容器中实例化:
class MyController
{
/**
* @var MyService
*/
private $my_service;
function __construct(MyService $my_service) {
$this->my_service = $my_service;
}
虽然它实际上自动装配服务本身,它完全忽略了 setContainer
调用,所以我最终得到空容器。
我想避免在任何地方调用 $this->get('app.service.my_service')
,我不能简单地在控制器的构造函数中调用一次,或者在自动装配服务的构造函数中调用 setContainer
,因为那时容器是空的。
我有没有机会以干净的方式做到这一点?
既然你已经知道注入容器是个坏主意,那么我想我就不给你讲课了。我没有遇到这个特殊问题,但您可以尝试使用鲜为人知的“@required”容器指令,看看它是否有帮助。
/** @required */
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
$this->em = $container->get('doctrine.orm.default_entity_manager);
}
这与其说是答案,不如说是猜测,但很容易尝试。
更新:有效。酷
我只是想补充一点,在大多数情况下,应该将依赖项注入到构造函数中。这避免了部分初始化的服务。另一方面,只要您通过容器生成服务,那么这就不是真正的问题。
我发现它在特质方面非常有用。例如:
trait RouterTrait
{
private RouterInterface $router;
/** @required */
public function setRouter(RouterInterface $router)
{
$this->router = isset($this->router) ? $this->router: $router;
}
// These are just copied from AbstractController
protected function generateUrl(
return $this->router->generate(...
protected function redirectToRoute(
#
class SomeService
use RouterTrait;
public function someMethod()
$this->generateUrl('''
因此,如果一个服务需要做一些路由,那么他们只需要使用路由器特性,路由器就会自动注入。将样板代码添加到服务的构造函数中。我确实在 setRouter 周围设置了保护措施,因此它只能被有效地调用一次,这消除了对依赖项使用 setter 的另一个担忧。
更新 #2: 原始代码的问题是 MyService 是用 app.service.my_service 而不是 class 名称的服务定义的。 Autowire 实际上生成了第二个 MyService 并将其注入,因为 autowire 查找与 class 名称匹配的服务 ID。将服务定义更改为:
App\Services\MyService:
public: true
calls:
- [setContainer, ['@service_container']]
也可以。或者明确地将 app.service.my_service 注入控制器。