Why use a Dependency Injection Container?

In this post I am going to look at the basics of Dependency Injection again and how they tie in with the move to using a Dependency Injection Container. The basics of why injecting an object's dependencies is a good idea are fairly easy to grasp, how this then leads to the use of a container is not so easily apparent.

Starting Point

So let's start by introducing the examples to be used. First up we have a controller that validates POST data and if valid sets up a Product object and passes it to a ProductSaver object to deal with persisting it. For the sake of simplicity the validation part is not actually shown:


class ProductController
{

    public function addAction()
    {
        //validate the POST variables

        $productSaver = new ProductSaver();
        $productSaver->save($product);
    }

    //--

}

The ProductSaver object persists the Product, again not actually shown, and then passes the product to an EmailNotifier object:


class ProductSaver
{

    public function save()
    {
        //save the product

        $emailNotifier = new EmailNotifier();
        $emailNotifier->notify($product);
    }

}

The EmailNotifier object creates a message notifying of the addition of the Product and sends it via a Mailer object:


class EmailNotifier
{

    public function notify($product)
    {
        $mail = new Mail();
        $mail->addTo('productmanager@example.com');
        $mail->addCC('deputyproductmanager@example.com');
        //create message body using $product
        $mailer = new Mailer();
        $mailer->send($mail);
    }

}

First Steps

Let's say we need to change the address we are sending the email notification to, to do this we will have to edit this code. If we wanted to use it in a different application where the email went to a different address then we would need to maintain two different versions. So let's move the address out of the class and inject it instead:


protected $toAddress;

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

public function notify($product)
{
    $mail = new Mail();
    $mail->addTo($this->toAddress);
    $mail->addCC('deputyproductmanager@example.com');

    //create message body using $product

    $mailer = new Mailer();
    $mailer->send($mail);
}

So by passing the email address in via the constructor we have now removed the need to have different versions of the class for different addresses. For the other application we want to CC the email to two different addresses, this means that the technique we used above would not work as we can only pass in one email address, instead what we can do is use setter injection and add the addresses to an array:


protected $toAddress;
protected $ccAddresses = array();

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

public function setCcAddress($ccAddress)
{
    $this->ccAddresses[] = $ccAdresss;
}

public function notify($product)
{
    $mail = new Mail();
    $mail->addTo($this->toAddress);
    foreach($this->ccAddresses as $ccAddress)
    {
        $mail->addCC($ccAddress);
    }
    
    //create message body using $product
    $mailer = new Mailer();
    $mailer->send($mail);
}

So now we can add as many CC addresses or even no CC addresses without having to change the class at all. To make it fully clear of any dependencies we could also inject the Mailer object, this is best suited to constructor injection again as it is a required dependency:


protected $toAddress;
protected $ccAddresses = array();
protected $mailer;

public function __construct($toAddress, MailerInterface $mailer)
{
    $this->toAddress = $toAddress;
    $this->mailer = $mailer;
}

public function setCcAddress($ccAddress)
{
    $this->ccAddresses[] = $ccAdresss;
}

public function notify($product)
{
    $mail = new Mail();
    $mail->addTo($this->toAddress);
    foreach($this->ccAddresses as $ccAddress)
    {
        $mail->addCC($ccAddress);
    }
    
    //create message body using $product
    $this->mailer->send($mail);
}

It looks like we can do the same with the Mail object but we need to be careful, whilst we can use the same Mailer object each time notify() is called, we actually want a fresh Mail object on each call. So, if we want to remove the Mail object, we would be better using a factory. Whilst many factory examples use a call to a static method to fetch the object we would then be stuck with a particular factory. Instead we can inject the factory into the class leaving us free to choose what factory to inject.


protected $toAddress;
protected $ccAddresses = array();
protected $mailer;
protected $mailFactory;

public function __construct($toAddress, 
                            MailerInterface $mailer, 
                            MailFactoryInterface $mailFactory
                           )
{
    $this->toAddress = $toAddress;
    $this->mailer = $mailer;
    $this->mailFactory = $mailFactory;
}

public function setCcAddress($ccAddress)
{
    $this->ccAddresses[] = $ccAdresss;
}

public function notify($product)
{
    $mail = $this->mailFactory->get();
    $mail->addTo($this->toAddress);
    foreach($this->ccAddresses as $ccAddress)
    {
        $mail->addCC($ccAddress);
    }
    
    //create message body using $product

    $this->mailer->send($mail);
}

For more information on the types of dependency injection see my post Symfony2: Dependency Injection Types.

What have we achieved? Well we have made our class look much more complicated, is this really a good thing? Whilst it looks more difficult to maintain and possibly harder to follow, we can now reuse it in different applications with different email addresses and even a different Mailer. This means that we now only need to maintain one version of it and not one for each combination of addresses and Mailer. This means that we have reduced the overall potential maintenance. We have also simplified the role of this class, it now only needs to concern itself with creating the email to send and no longer with whom to send it to and which Mailer to use.

Pushing Creation Upwards

Great then, but where does all the injection of these dependencies go? If we just now tried to use this from our ProductSaver class, we would have problems, as the EmailNotifier is not set up with all its dependencies. To set it up in the ProductSaver we would need to make these changes:


public function save()
{
    //save the product

    $emailNotifier = new EmailNotifier('productmanager@example.com', 
                                       new Mailer, 
                                       new MailFactory
                                      );
    $emailNotifier->setCcAddress('deputyproductmanager@example.com');
    $emailNotifier->setCcAddress('salesteam@example.com');
    $emailNotifier->notify($product);
}

This no longer looks so good, by shifting the set up of the EmailNotifier out of the class itself to the point of creation we have ended up giving the responsibility of setting it up to the ProductSaver. If we didn't want the EmailNotifer to be concerned with where the email is sent then we definitely don't want the ProductSaver to be responsible for it. So what do we do? The same as we did in the EmailNotifier, remove the point of creation of its dependencies out of the class:


protected $emailNotifier;

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

public function save($product)
{
    $mapper = new DataMapper();
    $mapper->save($product);
    $this->emailNotifier->notify($product);
}

Whilst we are at it lets move the DataMapper out as well:


protected $emailNotifier;
protected $mapper;

public function __construct($emailNotifier, $mapper)
{
    $this->emailNotifier = $emailNotifier;
    $this->mapper = $mapper;
}

public function save($product)
{
    $mapper->save($product);
    $this->emailNotifier->notify($product);
}

Ok problem solved, well sort of. We have pushed the creation up to our controller so that now looks like this:


public function addAction()
{
    //validate the POST variables

    $emailNotifier = new EmailNotifier('productmanager@example.com', 
                                       new Mailer, 
                                       new MailFactory
                                      );
    $emailNotifier->setCcAddress('deputyproductmanager@example.com');
    $emailNotifier->setCcAddress('salesteam@example.com');

    $mapper = new DataMapper();

    $productSaver = new ProductSaver($emailNotifier, $mapper);
    $productSaver->save($product);
}

So I guess we had better repeat the process and push it up out of our controller and into a bootstrap file that sets up our controller. So the controller now looks like this:


protected $productSaver;

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

public function addAction()
{
    //validate the POST variables

    $this->productSaver->save($product);
}

With a bootstrap file to set it up:


$emailNotifier = new EmailNotifier('productmanager@example.com', 
                                    new Mailer, 
                                    new MailFactory
                                  );
$emailNotifier->setCcAddress('depuftyproductmanager@example.com');
$emailNotifier->setCcAddress('salesteam@example.com');

$mapper = new DataMapper();

$productSaver = new ProductSaver($emailNotifier, $mapper);
$controller   = new ProductController($productSaver);

Great now we can just have a different bootstrap file for each application and set up the configuration of which services we want to use and how to configure them without needing to touch any of the other code.

Managing the Configuration

At the moment this still looks fairly manageable in this way but this is of course a huge simplification. In a real world app, in which we had performed this process of moving the dependencies up through out, the configuration would be much more complicated. For a start we would need to set up the configuration and dependencies of our DataMapper and EmailNotifier. There is also the code that was not shown for the validation of the POST request, we would also need to set up a response to send back to the browser. So the configuration will grow and grow and start to become difficult to maintain itself. That is only for a single action taking place in our application, for the other actions of the controller we may well need to set up other dependencies and configuration. Then there are all the other Controllers and their set ups.

At this point you will probably also have noticed that all the code in the set up is essentially repetitive instantiation of objects and calling of setter methods. Of course, we will want to do something about all this repetition. This is essentially what a Dependency Injection Container (DIC) is, it deals with the instantiation and set up of our dependencies for us and allows us to just specify them in a configuration file.

For the examples, I am going to look at the Symfony2 Dependency Injection Container or Service container as it is also known in the documentation. This is because Symfony2 is the next generation PHP framework which is closest to release and which has a working DIC. In Symfony2 we can set up this configuration using YAML, XML or PHP, I cam going to look at the XML version here. So our set up above could now be done with the following XML file:


<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>       
        
        <service id="productController" 
                 class="NameSpace\Of\ProductController">
            <argument type="service" id="productSaver" />
        </service>
        
        <service id="productSaver" 
                 class="NameSpace\Of\ProductSaver">
            <argument type="service" id="emailNotifer" />
            <argument type="service" id="mapper" />
        </service>

        <service id="mapper" 
                 class="NameSpace\Of\DataMapper"/>        

        <service id="emailNotifer" 
                 class="NameSpace\Of\EmailNotifer">
            <argument>productmanager@example.com</argument>
            <argument type="service" id="mailer" />
            <argument type="service" id="mailFactory" />
            <call method="setCcAddress">
                <argument>deputyproductmanager@example.com</argument>                
	    </call>
            <call method="setCcAddress">
                <argument>salesteam@example.com</argument>
	    </call>
        </service>
        
        <service id="mailer" 
                 class="NameSpace\Of\Mailer"/>
        
        
        <service id="mailFactory" 
                 class="NameSpace\Of\MailFactory"/>        

    </services>

</container>

We can now get the ProductController from the DIC which will set it up with all of its dependencies:


$productController = $container->get('productController');

So the DIC has helped us to clean up the repetition in our setting up of our dependencies. We can now just maintain config files for the DIC for each application.

So having seen loomed at the low level advantages of using DI, we can start to see some of the the application level benefits of using a DIC for DI. We can now avoid any configuration taking place in our code. It is no longer the responsibility or concern of the code but instead a configuration issue. This allows for rapid application development as we can easily reconfigure the framework to suit the current application.

When we do actually want to write some code rather than config files because we want to add a new feature, then we can write a new version of the class that needs the new functionality and just drop it in using the DIC config. Rather than repeat the existing functionality we could decorate the existing class to add this functionality:


class ProductSaverLoggerDecorator implements ProductSaverInterface
{
    protected $productSaver;
    protected $logger;

    public function __construct(ProductSaverInterface $productSaver,
                                LoggerInterface $logger)
    {
        $this->productSaver = $productSaver;
        $this->logger       = $logger;
    }


    public function save($product)
    {
        $this->productSaver->save($product);
        
        //create log message using product

        $this->logger->log($message);
    }

}

We would then not need to change any existing code just the config, these are the changes to the relevant parts:


<service id="productController" 
         class="NameSpace\Of\ProductController">
    <argument type="service" id="decoratedProductSaver" />
</service>
        
<service id="productSaver" 
         class="NameSpace\Of\ProductSaver">
    <argument type="service" id="emailNotifer" />
    <argument type="service" id="mapper" />
</service>

<service id="logger" 
         class="NameSpace\Of\FileLogger"/>
          
<service id="decoratedProductSaver" 
         class="NameSpace\Of\ProductSaverLoggerDecorator">
    <argument type="service" id="productSaver" />
    <argument type="service" id="logger" />
</service>

Other Advantages

Another advantage of using a DIC over writing the object creation code ourselves is that if we were to write the creation of all our controllers then we would be creating a lot of objects that are not used in a request. This is a problem in PHP where everything is created on each request, using a DIC if we request the controller from it then only that controller and its dependencies will be created and not the other controllers. In fact better than this, a good container will use proxies for dependencies and only create them as needed. This means that any objects that are not used due to the execution path of the code are not created, so the cost of creating unused dependencies is avoided.

Other performance measures taken by a DIC such as Symfony2's include the compilation and caching of the config files so that this is avoided on each request and caching of the actual classes as well. I do not know the internals of Symfony2's DIC to go into any real details about this though.

Conclusion

To get the benefits of Dependency Injection you need to keep pushing the creation of dependencies up through the code until it reaches the top. Once this is done a Dependency Injection Container helps to manage the resulting complicated configuration code as well providing performance benefits over directly creating the classes.

Disclaimer: As well as the usual issues of error checking and what have you in the code examples, there is the specific issue that having the ProductSaver directly invoke the email sending and logging is not really the best approach. It would instead be preferable to do something such as making the ProductSaver observable and registering logging and email notification as observers or using Symfony2's event dispatcher.