Software Alchemist

I am constantly seeking answers and ways of transforming desires into reality by coding

Interface Injection and Symfony2 DIC

| Comments

During the preparation for my recent talk, I had to refresh my knowledge of the theoretical side of Dependency Injection, since I was using it for a while, but found myself not knowing some of the terminology. I found "Dependency Injection" article on Wikipedia.org to very fulfilling. One thing that caught my attention specifically was the types of Dependency Injection, and Wikipedia defines three of them:

  1. Interface Injection (type 1)
  2. Setter Injection (type 2)
  3. Constructor Injection (type 3)

While the last two types are what I use in day-to-day coding, the first type was rather unfamiliar. So I looked it up, and Martin Fowler had a good definition.

Interface Injection - is a technique to define and use interfaces for the injection (Martin Fowler)

Examples on Martin Fowler's blog are quite complicated and would be more confusing to PHP developers than beneficial. But the general idea, which resonates with my point on Composition over Inheritance, is very simple - allow certain service to be injected into other services, that implement a common interface.

Example use case:

I have an interface ContainerAware, and I want all services that implement that interface to receive instance of Container through its ->setContainer() method. This would be very useful in Symfony2, if you were defining controllers as services and needed container. Right now there is a hard-coded instanceof check deep inside Symfony2's controller instantiation process, that sets the container on ContainterAware controllers. If Symfony2 DIC component supported the first type of Dependency Injection (Interface Injection), this would not be necessary, and many more interfaces could be defined, removing the verbosity from configuration.

Enough talking, show us some code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Company\ApplicationBundle\Controller;

use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;

class SimpleController implements ContainerAwareInterface
{
    /**
     * @var Symfony\Component\DependencyInjection\Container
     */
    private $container;

    /**
     * @param Symfony\Component\DependencyInjection\Container $container
     */
    public function setContainer(Container $container)
    {
        $this->container = $container;
    }
    //...
}

note Actually any controller that extends Symfony\Bundle\FrameworkBundle\Controller\Controller implements ContainerAwareInterface, as it inherits it from parent, but for the purposes of this example, I chose code that shows everything explicitly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" ?>

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

    <interfaces>
        <interface class="Symfony\Component\DependencyInjection\ContainerAwareInterface">
            <call method="setContainer">
                <argument type="service" id="container" />
            </call>
        </interface>
    </interfaces>
</container>

note There is no way to define Interface Injectors in Symfony2 yet, as it doesn't support the first type of Dependency Injection. This is my proposed way of configuring interface injectors in xml.

All we would need to do now is register our controller within the Symfony2's DIC component without defining the method call explicitly, since the DIC would know to apply interface injectors to all services, requiring them:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" ?>

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

    <services>
        <service id="index_controller" class="Company\ApplicationBundle\Controller\SimpleController" />
    </services>
</container>

The described approach makes DIC configurations take less space to achieve the same result. Because if you're implementing a certain interface that defines rules for injection of other services, you usually need to re-use the same services across all instances of the interface, but have to explicitly define the injections every time.

I can see some of the Symfony2s built-in services benefiting from Interface Injection:

I definitely have a lot more classes, that can benefit from it in my problem domain.

So with all that in mind I spent last evening putting together my own implementation of Interface Injection in Symfony2's DIC component, and you can find it in my Symfony2 for on github.

Please comment with suggestions and/or feedback, very interested in what you think.

Happy Coding!

Comments