21

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?

Dan Blows
  • 20,846
  • 10
  • 65
  • 96
Jarek Jakubowski
  • 948
  • 10
  • 22
  • possible duplicate of [Is is an anti-pattern to inject DI container to (almost) each class?](http://stackoverflow.com/questions/10356497/is-is-an-anti-pattern-to-inject-di-container-to-almost-each-class) – Tomasz Madeyski May 29 '14 at 10:46

4 Answers4

58

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.

Dan Blows
  • 20,846
  • 10
  • 65
  • 96
  • Hi, Do you think Role based injection is a bad idea? For example, an admin who wants to perform the same actions but slightly differently? I have a situation where the authorization for an action is becoming increasingly complex because depending on the user, and depending on who owns the Entity, the permission has become like a matrix (Role x ObjectOwner). – Worthy7 Jul 26 '17 at 08:50
6

Besides all disadvantages explained by others (no control over used services, run time compilation, missing dependencies, etc.)

There is one main reason, which breaks the main advantage of using DIC - Dependencies replacement.

If service is defined in library, you wont be able to replace it dependencies with local ones filling your needs.

Only this reason is strong enough, to not inject whole DIC. You just break whole idea of replacing dependencies since they are HARDCODED! in service;)

BTW. Don't forget to require interfaces in service constructor instead of specific classes as much as you can - again nice dependencies replacement.

EDIT: Dependencies replacement example

Service definition in some vendor:

<service id='vendor_service' class="My\VendorBundle\SomeClass" />
    <argument type="service" id="vendor_dependency" />
</service>

Replacement in your app:

<service id='vendor_service' class="My\VendorBundle\SomeClass" />
     <argument type="service" id="app_dependency" />
</service>

This allows you to replace vendor logic with your customized one, but don't forget to implement required class interface. With hardcoded dependencies you're not able to replace dependency in one place.

You can also override vendor_dependency service, but this will replace it in all places not only in vendor_service.

Maciej Pyszyński
  • 9,266
  • 3
  • 25
  • 28
  • I'm not exactly sure what you mean by 'replacing dependencies with local ones'. Could you clarify in the answer? – Dan Blows Oct 07 '14 at 10:06
  • Also, you should never inject business objects, only cross cutting concerns, and external component dependencies. By external I dont mean third party, although they count too. – samazi Nov 06 '17 at 05:37
5

It is not a good idea because you're making your class dependent on the DI. What happens when some day you decide to pull out your class and use it on an entirely different project? Now I'm not talking about Symfony or even PHP, I'm talking generally. So in that case you have to make sure the new project uses the same kind of DI mechanism with the same methods supported or you get exceptions. And what happens if the project does not use DI at all, or uses some cool new DI implementation? You have to go through your whole codebase and change things to support the new DI. In large projects this can be problematic and costly, especially when you're pulling more than just one class.

It is best to make your classes as independent as possible. This means keeping the DI out of your usual code, something like a 3rd person who decides what goes where, points where the stuff should go, but doesn't go there and do it himself. This is how I understand it.

Although, as tomazahlin said, I agree that in Symfony projects in rare occasion it helps prevent circular dependencies. That's the only example where I'd use it and I'd make damn sure that's the only option.

Andrej Mohar
  • 966
  • 8
  • 20
  • I get the general idea, but what if I don't expect to move my class anywhere else? Does injecting DI have any performance/security impact? – Jarek Jakubowski May 29 '14 at 12:12
  • 1
    Not as far as I know (anyone feel free to correct me). As long as you're having a single instance (like a singleton, another antipattern) you should be fine. I know of some quite large and expensive projects using these antipatterns quite extensively, and it works for them quite well. If you look at it, the same thing happens in the Symfony base controller. It has the container injected and is quite dependent on it (the use of `get()` and `has()` methods throughout the actions). – Andrej Mohar May 29 '14 at 12:19
  • The answer is completely valid, but in reality you're not likely to come across a situation like this. So it's more of a theory thing. Good answer for an interview question though ;) – jmoz Jun 05 '14 at 15:31
  • The 'theory' has nothing to do with possibly rewriting your app blah blah... it's a very simple one. Testing. You cant unit test your code without the framework, and everything that relates to it, running. If you don't care about unit tests, and are happy to write integration tests exclusively, then don't worry. Otherwise, you need to isolate all your business code from whatever framework you are using. – samazi Nov 06 '17 at 05:35
0

Injecting the whole container is not a good idea in general. Well, it works, but why injecting the whole container, while you only need a few other services or parameters.

Sometimes you want to inject the whole container to avoid circular references, because if you inject the whole container, you get "lazy loading" of the services you require. An example would be doctrine entity listeners.

You can get the container from every class that is "Container Aware" or has access to the kernel.

tomazahlin
  • 2,137
  • 1
  • 24
  • 29