在 Symfony2 中,为什么注入服务容器而不是单个服务是个坏主意? [英] In Symfony2, why is it a bad idea to inject the service container, rather than individual services?

查看:20
本文介绍了在 Symfony2 中,为什么注入服务容器而不是单个服务是个坏主意?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我找不到这个问题的答案...

如果我注入服务容器,例如:

//config.yml我的听众:类:MyListener参数:[@service_container]我的服务:类:我的服务//MyListener.php类 MyListener{受保护的 $container;公共函数 __construct(ContainerInterface $container){$this->container = $container;}公共函数 myFunction(){$my_service = $this->container->get('my_service');$my_service->doSomething();}}

然后它和我一样工作:

//config.yml我的听众:类:MyListener参数:[@my_service]我的服务:类:我的服务//MyListener.php类 MyListener{受保护的 $my_service;公共函数 __construct(MyService $my_service){$this->my_service = $my_service;}公共函数 myFunction(){$this->my_service->doSomething();}}

那么为什么我不应该只注入服务容器,然后从我的类中获取服务呢?

解决方案

我列出的您应该更喜欢注入服务的原因:

  1. 你的类只依赖于它需要的服务,而不是服务容器.这意味着该服务可以在不使用 Symfony 服务容器的环境中使用.例如,您可以将您的服务变成一个可以在 Laravel、Phalcon 等中使用的库——您的类不知道依赖项是如何被注入的.

  2. 通过在配置级别定义依赖关系,您可以使用配置转储器来了解哪些服务正在使用哪些其他服务.例如,通过注入@mailer,然后很容易从邮件程序被注入的服务容器中找出来.另一方面,如果您执行 $container->get('mailer'),那么几乎唯一找出邮件程序使用位置的方法就是执行 find.

  3. 在编译容器时,而不是在运行时,您会收到有关缺少依赖项的通知.例如,假设您定义了一个服务,并将其注入到侦听器中.几个月后,您不小心删除了服务配置.如果您正在注入该服务,则在您清除缓存后会立即通知您.如果你注入了服务容器,只有当因为容器无法获取服务而导致监听器失败时,你才会发现错误.当然,如果您进行了彻底的集成测试,您可以选择它,但是……您已经进行了彻底的集成测试,不是吗?;)

  4. 如果您注入了错误的服务,您会很快知道.例如,如果您有:

    公共函数__construct(MyService $my_service){$this->my_service = $my_service;}

    但您已将侦听器定义为:

    my_listener:班级:随便参数:[@my_other_service]

    当监听器接收到 MyOtherService 时,PHP 会抛出一个错误,告诉你它接收到了错误的类.如果您正在执行 $container->get('my_service') ,则您假设容器正在返回正确的类,并且可能需要很长时间才能确定它不是.

  5. 如果您使用的是 IDE,那么类型提示会增加一大堆额外的帮助.如果您使用 $service = $container->get('service'); 那么您的 IDE 不知道 $service 是什么.如果你注入

    公共函数__construct(MyService $my_service){$this->my_service = $my_service;}

    那么您的 IDE 就知道 $this->my_serviceMyService 的一个实例,并且可以提供方法名称、参数、文档等方面的帮助.p>

  6. 您的代码更易于阅读.您所有的依赖项都在类的顶部定义.如果它们用 $container->get('service') 分散在整个类中,那么就很难弄清楚了.

  7. 您的代码更容易进行单元测试.如果您要注入服务容器,则必须模拟服务容器,并配置模拟以返回相关服务的模拟.通过直接注入服务,您只需模拟服务并注入它们 - 您跳过了整个复杂层.

  8. 不要被它允许延迟加载"的谬论所迷惑.您可以在配置级别配置延迟加载,只需将服务标记为 lazy: true.

就我个人而言,唯一一次注入服务容器是最好的解决方案是当我尝试将安全上下文注入到原则侦听器中时.这会引发循环引用异常,因为用户存储在数据库中.结果是原则和安全上下文在编译时相互依赖.通过注入服务容器,我能够绕过循环依赖.然而,这可能是一种代码异味,有很多方法可以解决它(例如,通过 使用事件调度器),但我承认增加的复杂性可能超过好处.

I can't find the answer to this...

If I inject the service container, like:

// config.yml
my_listener:
   class: MyListener
   arguments: [@service_container]

my_service:
   class: MyService

// MyListener.php
class MyListener
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function myFunction()
    {
        $my_service = $this->container->get('my_service');
        $my_service->doSomething();
    }
}

then it works just as well as if I do:

// config.yml
my_listener:
   class: MyListener
   arguments: [@my_service]

my_service:
   class: MyService

// MyListener.php    
class MyListener
{
    protected $my_service;

    public function __construct(MyService $my_service)
    {
        $this->my_service = $my_service;
    }

    public function myFunction()
    {
        $this->my_service->doSomething();
    }
}

So why shouldn't I just inject the service container, and get the services from that inside my class?

解决方案

My list of reasons why you should prefer injecting services:

  1. Your class is dependent only on the services it needs, not the service container. This means the service can be used in an environment which is not using the Symfony service container. For example, you can turn your service into a library that can be used in Laravel, Phalcon, etc - your class has no idea how the dependencies are being injected.

  2. By defining dependencies at the configuration level, you can use the configuration dumper to know which services are using which other services. For example, by injecting @mailer, then it's quite easy to work out from the service container where the mailer has been injected. On the other hand, if you do $container->get('mailer'), then pretty much the only way to find out where the mailer is being used is to do a find.

  3. You'll be notified about missing dependencies when the container is compiled, instead of at runtime. For example, imagine you have defined a service, which you are injecting into a listener. A few months later, you accidentally delete the service configuration. If you are injecting the service, you'll be notified as soon as you clear the cache. If you inject the service container, you'll only discover the error when the listener fails because of the container cannot get the service. Sure, you could pick this up if you have thorough integration testing, but ... you have got thorough integration testing, haven't you? ;)

  4. You'll know sooner if you are injecting the wrong service. For example, if you have:

    public function __construct(MyService $my_service)
    {
       $this->my_service = $my_service;
    }
    

    But you've defined the listener as:

    my_listener:
        class: Whatever
        arguments: [@my_other_service]
    

    When the listener receives MyOtherService, then PHP will throw an error, telling you that it's receiving the wrong class. If you're doing $container->get('my_service') you are assuming that the container is returning the right class, and it can take a long time to figure out that its' not.

  5. If you're using an IDE, then type hinting adds a whole load of extra help. If you're using $service = $container->get('service'); then your IDE has no idea what $service is. If you inject with

    public function __construct(MyService $my_service)
    {
       $this->my_service = $my_service;
    }
    

    then your IDE knows that $this->my_service is an instance of MyService, and can offer help with method names, parameters, documentation, etc.

  6. Your code is easier to read. All your dependencies are defined right there at the top of the class. If they are scattered throughout the class with $container->get('service') then it can be a lot harder to figure out.

  7. Your code is easier to unit test. If you're injecting the service container, you've got to mock the service container, and configure the mock to return mocks of the relevant services. By injecting the services directly, you just mock the services and inject them - you skip a whole layer of complication.

  8. Don't be fooled by the "it allows lazy loading" fallacy. You can configure lazy loading at configuration level, just by marking the service as lazy: true.

Personally, the only time injecting the service container was the best possible solution was when I was trying to inject the security context into a doctrine listener. This was throwing a circular reference exception, because the users were stored in the database. The result was that doctrine and the security context were dependent on each other at compile time. By injecting the service container, I was able to get round the circular dependency. However, this can be a code smell, and there are ways round it (for example, by using the event dispatcher), but I admit the added complication can outweigh the benefits.

这篇关于在 Symfony2 中,为什么注入服务容器而不是单个服务是个坏主意?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆