/usr/portage

Dependency Injection Container Refactorings, Part One 15

This is part of a mini-series about typical refactorings when using DI containers. Read part two.


(c) Jil A. Brown

Working heavily with the Symfony2 Dependency Injection Container, I feel that we found some typical refactorings towards a DI container that emerge during the introduction of such a component. I want to write down the preliminary results of trying to systematize more or less as a draft. I will use the Symfony2 DI container configuration as an example but most of the refactorings should be applicable to other containers as well, some of them even to dependency injection without a container.

Make Dependency Explicit

This is typically the first step towards Dependency Injection: make a dependency explicit. There are three typical ways to do so, first is constructor injection, second is setter injection and third and less preferred is property injection. I roughly prefer constructor injection for invariant dependency in my domain and setter injection for infrastructure (setNotifier e.g.). Consider this example:

<?php
namespace Example;
class Client
{
    public function execute()
    {
        $dependency = new Dependency();
        $dependency->execute();
    }
}

Client creates a new instance of Dependency and call execute(). Bad for testing and for configuration, Dependency will always be hard coded there. To make it easier manageable we refactor towards setter injection:

<?php
namespace Example;
class Client
{
    public function setDependency(Dependency $dependency)
    {
        $this->_dependency = $dependency;
    }
 
    public function execute()
    {
        $this->_dependency->execute();
    }
}

Now we can manage Client in the DI container like this:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

We see that the dependency is explicit: we specifically configure Example\Client and pass a specific Example\Dependency object.

Introduce Interface Injection

After a number of Explicit Dependency refactorings our configuration file for the service container will become huge. We will notice that we have common dependencies that are used at various places, an event manager for example. To fix that rapid growth we choose to utilize Interface Injection to ease configuration.

This is the configuration starting point:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
        <service name="example.anotherClient" class="Example\AnotherClient">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
            <call method="setOtherDependency">
                <argument type="service">
                    <service class="Example\OtherDependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

We notice that both Example\Client and Example\AnotherClient depend on Example\Dependency. First of all we need an interface contracting setDependency. This is basically the Extract Interface refactoring. We call the newly extracted interface Example\DependencyAware.

The interface:

<?php
namespace Example;
interface DependencyAware
{
    public function setDependency(Dependency $dependency);
}

And we refactor both Example\Client and Example\AnotherClient to implement Example\DependencyAware.

Now we change our configuration to call setDependency no longer explicitly for Example\Client and Example\AnotherClient but for every object implementing Example\DependencyAware.

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client"/>
        <service name="example.anotherClient" class="Example\AnotherClient">
            <call method="setOtherDependency">
                <argument type="service">
                    <service class="Example\OtherDependency"/>
                </argument>
            </call>
        </service>
    </services>
    <interfaces>
        <interface class="Example\DependencyAware">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </interface>
    </interfaces>
</container>

Expose Service

Really simple, but …
Expose Service is applied when a service has been only a dependency but should be used as a top level service. We start with the well known example:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

Consider we want to expose Example\Dependency as a service directly, we need to change from the configuration above to
reference the service by ID.

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.dependency" class="Example\Dependency"/>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service" id="example.dependency"/>
            </call>
        </service>
    </services>
</container>

Simple.

Next topics would be: Introduce Parameter, Parametererize Service and Allow Environment Specific configuration

Filed on 19-04-2011, 21:09 under , , , & 15 comments & one trackback

Trackbacks

Trackback specific URI for this entry

  1. Trackback from WEBLOG (Brian Swan)
    posted on April 22nd 2011, 07:17:20 pm This Week’s Link List (April 22, 2011)

    There was a lot of good content this week. Highlights for me included the release of the Windows Azure

Comments

  1. Lukas replys:
    published on April 19th 2011, 11:52:26 pm *

    Interface injection is very problematic imho, because the class definition shouldn’t have control over what it gets injected. Well I guess per container one has the ability to once define what to inject, but its not on a per service basis anymore.

    See http://groups.google.com/group/symfony-devs/browse_thread/thread/efb1b1588f05f255

    Reply

  2. Lars Strojny answers:
    published on April 20th 2011, 08:05:56 am *

    I would describe it that way: the interface describes what abstract class of service ist injected, which is kind of what it does even without the interface.

    Reply

  3. Lukas supposes:
    published on April 20th 2011, 02:09:42 pm *

    No it doesnt describe, it hardcodes it. I think the only workable way for interface injection is if the interface just gives a "default" that for all i can be used automatically, but that must be possible to disable explicitly. otherwise you end up with a one size must fit all scenario.

    Then again I just realized that in your setup you reference services by class name, which is odd, why aren’t you injecting by service name? i guess by class works "fine" as long as the injecting service doesn’t have dependencies of his own.

    Anyway, it looks like interface injection will be removed from the Symfony2 dependency injection container because of the issues I mentioned.

    Reply

  4. Lars Strojny replys:
    published on April 21st 2011, 10:35:02 am *

    Thanks for your comment. Nevertheless I think that’s bad news, as it is really handy. What would achieve both of our targets is to allow filtering and expressions like "use this for interface a unless instanceof DifferentDepReq". This shouldn’t be too hard to implement in the current code base.

    Reply

  5. Lukas answers:
    published on April 21st 2011, 10:38:07 am *

    No I think the only viable solution to keep interface injection is to add a per service flag to disable interface injection entirely. This way the person defining the service is in control. Having to extend the code just to modify the dependencies is just not what DI is about imho.

    Reply

  6. Lars Strojny returns:
    published on April 21st 2011, 10:42:37 am *

    Hi Lukas, I think we misunderstand each other here. I was not talking about code, but about configuration. Something like this maybe:

    interface class=Example\Service
       exceptions
             exception class=Example\DontInjectHere
    

    Reply

  7. Lukas reckons:
    published on April 21st 2011, 10:48:00 am *

    The code got kind of lost there. Anyway you cannot do this by checking for a specific classes. This doesnt solve the issue, because you might want to have different services with different dependencies for the same class in one DI container.

    But sure you can "invent" some syntax to disable interface injection entirely, on specific methods, on specific interfaces on a per service basis. But then you are adding a lot of syntax for what essentially is syntax sugar.

    So like I said, if at all I think a simple flag to disable is viable. Alternatively one can use abstract services to get around having to define the same setter calls over and over again, however that is of course not as "automatic" as just looking at interfaces.

    Reply

  8. Lars Strojny means:
    published on April 21st 2011, 10:39:38 am *

    Hi Lukas, one question I forgot to answer was why we refer to the explicit class instead of a service. We want to keep our interface the container provides as narrow as possible so we try to keep the number of concrete services exposed very small. That’s why we start referring to the concrete class and if we need that dependency somewhere else, apply Expose Service.

    Reply

  9. Stephan Hochdoerfer states:
    published on April 20th 2011, 02:00:15 pm *

    Why is there the need to refer to the setter in the xml config again when the method name is already exposed via the interface definition? Looks wired to me.

    Reply

  10. Lars Strojny responses:
    published on April 21st 2011, 10:36:36 am *

    Hi Stephan, thanks for you question. As an interface might provide a contract for more than one method, you need to specify which method exactly is used for dependency injection. That’s why the setter is referred to explicitly.

    Reply

  11. Stephan Hochdoerfer replys:
    published on April 24th 2011, 01:00:49 pm *

    Well I would simply not allow that and just force the devs to define one interface for one dependency. IMHO you should not mix dependency injection with other interface methods.

    Reply

  12. Lars Strojny supposes:
    published on May 3rd 2011, 07:01:39 pm *

    Hi Stephan,

    I totally agree for my own code. The only thing I could think of would be defining an additional getter but I haven’t hat a case like that yet. What’s more interesting are other packages I want to reuse which do not adhere to the same design principles. As our profession became more and more being about "glueing components together", this is an important feature to have.

    Reply

  13. Adrian reckons:
    published on April 20th 2011, 05:59:22 pm *

    @Stephan
    I’m not a big fan of xml writing either. Luckily there are dependency injection frameworks which don’t use xml configuration files. For a small applications a DI framework like bucket (https://github.com/troelskn/bucket) does the job pretty well. For Java applications I recommend Guice (http://code.google.com/p/google-guice/).

    Reply

  14. Loïc Frering states:
    published on April 20th 2011, 06:15:42 pm *

    With Symfony2 DI Container you can use either XML, YAML, INI, Closure or Plain PHP to define your services.

    By the way I also developed an annotation loader to load the container by directly annotating your classes, like with Guice. It comes with LosoBundle: http://goo.gl/4udcD.

    Reply

  15. Stephan Hochdoerfer responses:
    published on April 20th 2011, 09:12:40 pm *

    First of all I did not want to blame xml, in fact I like xml. It just looks wired to me to define the same information twice.
    Second, simple DI containers are nice to play with but as far as I can tell a full stack DI framework plays much nicer (but I am biased).

    Reply

Add a Comment & let me know what you think