在Symfony2中,为什么注入服务容器而不是单个服务是个坏主意?

Jar*_*ski 21 dependency-injection symfony

我找不到这个答案......

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

// 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后它的工作方式和我做的一样:

// 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*ows 54

我列出了您希望注入服务的原因列表:

  1. 您的类仅依赖于它所需的服务,而不依赖于服务容器.这意味着该服务可以在不使用Symfony服务容器的环境中使用.例如,您可以将服务转换为可以在Laravel,Phalcon等中使用的库 - 您的类不知道如何注入依赖项.

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

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

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

    public function __construct(MyService $my_service)
    {
       $this->my_service = $my_service;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但是你已经将监听器定义为:

    my_listener:
        class: Whatever
        arguments: [@my_other_service]
    
    Run Code Online (Sandbox Code Playgroud)

    当侦听器收到时MyOtherService,PHP将抛出一个错误,告诉你它正在接收错误的类.如果你正在做,$container->get('my_service')你假设容器正在返回正确的类,并且可能需要很长时间来弄清楚它的'不是.

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

    public function __construct(MyService $my_service)
    {
       $this->my_service = $my_service;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后你的IDE知道它$this->my_service是一个实例MyService,并且可以提供方法名称,参数,文档等方面的帮助.

  6. 您的代码更易于阅读.所有依赖项都在类的顶部定义.如果他们分散在整个班级中,$container->get('service')那么要弄清楚可能要困难得多.

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

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

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


And*_*har 5

这不是一个好主意,因为你让你的班级依赖于DI.有一天你决定退出课程并在一个完全不同的项目中使用它会发生什么?现在我不是在谈论Symfony甚至是PHP,我一般都在谈论.因此,在这种情况下,您必须确保新项目使用相同类型的DI机制,并支持相同的方法,否则您将获得异常.如果项目根本不使用DI,或者使用一些很酷的新DI实现,会发生什么?您必须完成整个代码库并更改内容以支持新的DI.在大型项目中,这可能是有问题且成本高昂的,尤其是当您拉动的不仅仅是一个班级时.

最好让您的课程尽可能独立.这意味着将DI保留在通常的代码之外,就像第三个人决定什么在哪里,指出应该去哪里,但是不去那里自己做.这就是我理解它的方式.

虽然,正如tomazahlin所说,我同意在Symfony项目中,在极少数情况下它有助于防止循环依赖.这是我使用它的唯一例子,我确信这是唯一的选择.


Mac*_*ski 5

除了其他人解释的所有缺点(无法控制使用的服务,运行时编译,缺少依赖性等)

有一个主要原因,它打破了使用DIC - Dependencies替换的主要优势.

如果在库中定义了服务,那么您将无法将其依赖性替换为满足您需求的本地依赖项.

只有这个原因足够强大,不能注入整个DIC.你只是打破了更换依赖关系的全部想法,因为它们是HARDCODED!在服务;)

BTW.不要忘记interfaces尽可能多地使用服务构造函数而不是特定的类 - 再次使用良好的依赖项替换.

编辑:依赖替换示例

某些供应商的服务定义:

<service id='vendor_service' class="My\VendorBundle\SomeClass" />
    <argument type="service" id="vendor_dependency" />
</service>
Run Code Online (Sandbox Code Playgroud)

您的应用中的替换:

<service id='vendor_service' class="My\VendorBundle\SomeClass" />
     <argument type="service" id="app_dependency" />
</service>
Run Code Online (Sandbox Code Playgroud)

这允许您使用自定义的逻辑替换供应商逻辑,但不要忘记实现所需的类接口.使用硬编码的依赖项,您无法在一个地方替换依赖项.

您也可以覆盖vendor_dependency服务,但这不仅会在所有地方替换它vendor_service.