在控制器的构造函数中自动装配的 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 注入控制器。