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.

PHP 5.3 namespaces & Symfony2

If you’re not used to namespaces, they can be easy to forget about. Essentially, if you’re going to use any class in your code, you need to ensure that the package or namespace it belongs to is imported at the top of your file, like so:

[php]
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends Controller
{
public function indexAction()
{
return new Response("Hello");
}
}[/php]

If you don’t import the class Response, you’ll get a class not found error when you try to instantiate it.

Read more:

Apache virtual hosts config

This approach uses the vhost_alias module combined with a local DNS to let you use any subfolder in your web directory as a virtual host without having to do any additional Apache configuration. This description is for Apache on Windows; the same Apache config will work on any system, but you’ll need to find and configure a local DNS service for your OS to get the full benefit.

1. Apache configuration

First, make sure mod_vhost_alias.so is enabled in httpd.conf.

LoadModule vhost_alias_module modules/mod_vhost_alias.so

Next, place the following code in your httpd-vhosts.conf file:

 ServerName normal.dev
 ServerAlias *.dev
 DirectoryIndex index.php index.html
 UseCanonicalName Off
 VirtualDocumentRoot D:\Jeremy\htdocs\%1\

If you now create a directory like so:

--- htdocs
  |
  --- testsite
    |
    --- index.html

This configuration will allow you to visit http://testsite.dev/index.html without having to add a new vhost in your apache config.

2. DNS configuration

However, before you can test this in your browser, you first need to map testsite.dev to localhost so that your browser can get to your Apache instance and pick up on that nice autoconfigured virtual host. There are two ways you can do this on Windows. One method is to edit the hosts file (type notepad c:\windows\system32\drivers\etc\hosts into the Run dialog) and add a line for each virtual host you want to use:

127.0.0.1        testsite.dev
127.0.0.1        testsite2.dev

Unfortunately, the windows hosts file doesn’t support the use of wildcards, so you can’t just define *.dev and be done with it. So, the preferred method is to install a DNS service like Acrylic. (Download page is here.) Once you’ve done that, you can add wildcards to the Acrylic custom hosts file, which looks just like the windows hosts file:

127.0.0.1        *.dev

3. Configuration for Symfony2

But what if your project, like Symfony2, requires a webroot within the project directory itself? Consider the following configuration:

--- htdocs
  |
  --- testsite
    |
    --- app
    |
    --- bin
    |
    --- src
    |
    --- vendor
    |
    --- web

You don’t want to have all your code in the web root, but you don’t want to have to store all your project files in htdocs either. So you need to define the VirtualDocumentRoot to point to a subdirectory of your project directory.

 ServerName symfony.sym
 ServerAlias *.sym
 DirectoryIndex index.php index.html
 UseCanonicalName Off
 VirtualDocumentRoot D:\Jeremy\htdocs\%1\web

Now, if you visit testsite.dev, Apache will set the document root to /testsite, according to the first set of rules we created; if you visit testsite.sym, Apache will look for a Symfony2-appropriate document root in /testsite/web. This lets you develop projects with various different directory structures without having to change your Apache config.

Don’t forget to create the appropriate rule in your Acrylic configuration too:

127.0.0.1        *.sym