How to use the Dependency Injection Container in Symfony2

When you first start using Symfony2, you’re struck immediately with the concept of Dependency Injection, how wonderful it is, and how easy Symfony2 makes it to use this pattern.

The trouble is, there’s a very easy misunderstanding to make which can really confuse you. There are a fair few good articles about this, but it took me a while to understand what was going on, so I am writing this to make the basic difference clear.

In a nutshell:

Defining dependency relationships between services in a config file, and passing them to the service’s constructor, IS dependency injection.

Grabbing services from the DIC by name inside your code IS NOT dependency injection.

Functions of the dependency injection container

The dependency injection container has two functions.

1) The DIC stores all services and their details (class name, arguments, etc) outside of the location where they’re used.

When services are registered, you can pass other services to them by reference; this ensures that all dependencies are loosely coupled and not hard coded.

services:
    page_manager:
        class: MyCompany\MyBundle\Entity\PageManager
        arguments:
            [@doctrine.orm.entity_manager]
    page_controller:
        class: MyCompany\MyBundle\Controller\PageController
        arguments:
            [ @templating, @page_manager ]

The @ symbol tells Symfony2 to interpret this argument as a service name.

Once you have set up a service, you can pass it to other services. This is better than having code inside PageController like this:

    function __construct() {
        $this->em = $this->getEntityManager();
        $this->page_manager = new PageManager($this->em);
    }

Not only does this tie you to the class PageManager, but it requires you to pass the dependencies directly. If PageManager’s arguments change, you need to change every instance in your code where you create one.

So, defining your services outside the code and letting the DIC take care of instantiating them is good.

2) The DIC can be used to retrieve by name any service defined inside it.

This is the kind of example you see in the documentation:

$session = $this->container->get('session');
$session->set('foo', $bar);

Or:

$em = $this->container->get('doctrine')->getEntityManager();
$users = $em->getRepository('AcmeHelloBundle:User')
    ->findAllOrderedByName();

This is the part which is important to understand, but which isn’t mentioned in the official documents: You need to think twice before using $container->get()

What’s wrong with $container->get()?

This approach should only be used sparingly, in certain places where it does not matter that you are hard coding references to services. There are two reasons:

  1. Everywhere you call $container->get('service_name'), you’re setting up a reference to the same service, with the same name. If you later decide you want to use service_name configured in two different ways depending on the situation, you can’t do it, because if you make a change to the way the service is configured, all the places that call for that service will be updated, not just the ones you want.
  2. It’s harder to test. If the services you’re using were injected into your constructors, or via setter methods, you could replace the real thing with a mock object easily. If the service is being fetched from the container, then you need to replace the container with a mock object, load it up with your mock service objects, and set it up to return them.

It would be possible to mitigate these two concerns by just being willing to search/replace all your calls to the service you need to change, and by having a way to inject your container and a few helpers in your tests to make a mock container with the mock  services you want, and I’m sure that will appeal to some people; there are plenty of examples of people injecting the entire container into their services. But it’s important to realise that this part of the DIC is NOT dependency injection — it’s using it as a Service Locator, which is a related concept. (See this much-linked article by Martin Fowler for a discussion of the two).

It doesn’t make one wrong and the other right, but it made it much easier to understand dependency injection when I realised that despite the name, that’s not exactly always what the DIC is used for.

Discussion

Often learning about DI with reference to a container is part of the problem as it makes you think DI is about getting things from a DIC when it is not at all.

— Richard Miller

http://miller.limethinking.co.uk/2011/05/19/when-dependency-injection-goes-wrong/

Controllers are just a way to wire everything together with no logic. I don’t see why it would be a bad practice to inject the container into the controller, even if it makes the container a service locator in this case. Pragmatism over purity is always better.

— Fabien Potencier

https://github.com/FriendsOfSymfony/UserBundle/issues/154

Hopefully this clears up a little bit of confusion.

Leave a Reply

Your email address will not be published. Required fields are marked *