Developer Blog

  • Blog
  • /
  • You don't need a fancy Dependency Injection Container
By Dracony on 6 February 2014

Looking around frameworks and libraries I see this strange notion of abstracting dependency injection into xml, yml, arrays, callbacks and a whole bunch of other things. And it’s very hard for me to find any legitimate reason for doing so, especially since this approach actually reduces the flexability of your dependency injection.

To illustrate my poin’t I’m going to create an example dependency injection factory which follows the Pure Fabrication principle of GRASP. It will represent a part of the application responsible for creating Fairies for protecting Trees in a Forest. There are a number of different types each fairy could be, and different types get different spells they can cast.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Fairies;
class Factory {

    public function fairy($fairy_type) {

        //Each fairy type has a corresponding spell ability
        //First we need to determine which magic type to use
        $spell_type = $this->get_spell_type($fairy_type)

        //Now build an instance of that magic
        $spell = $this->spell($spell_type);

        return new Fairy($fairy_type, $spell);
    }

    protected $fairy_spells = array(
        'pixie' => 'heal',
        'sylph' => 'wind'
    );  

    protected function get_spell_type($fairy_type) {
        return $this->fairy_spells[$fairy_type];
    }
    public function spell($spell_type) {
        //Determine class name based on spell type
        $class = 'Fairy\Spell\'.$spell_type;
        return new $class();
    }

}

Now we have a nice class for creating fairies. You can easily write tests for this class to ensure that all functions return correctly built instances. You could also define it as an interface and pass this factory around classes that would need to use it. All this while maintaining a nice API and having full IDE support with typehinting. Additionaly we expose only some logic like building actual fairies and spells, but we hide the logic behind determining which fairy gets which spell.

Now let’s suppose we need another factory for building trees. That new factory must now be able to build a fairy to assign it to protecting that tree. All we need to do is inject our Fairy factory as a dependency like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace Trees;

class Factory {
    protected $fairies_factory;

    //Forest is a service, so it's only going to be built once
    protected $forest;

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

    public function forest() {
        if($this->forest === null)
            $this->forest = $this->build_forest();
        return $this->forest;
    }

    protected function build_forest() {
        return new Forest;
    }

    public function tree($tree_type, $fairy_type) {
        $forest = $this->forest();
        $fairy = $this->fairy_factory->fairy($fairy_type);
        return new Tree($tree_type, $fairy, $forest);
    }

}

Using this strategy we can build factories on top of other factories, separating and layering them. This will give us easy testability and separation of concerns. If I pass a Tree Factory to a class I’m sure that that class will have no need in creating fairies or magic spells, by splitting factories I’m also splitting their interfaces, making it easier to see what the class is actually going to use.

Now let’s imagine we’re doing this using Pimple, it would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$container = new Pimple();

//Now isn't this convoluted?
$container['fairy'] = $container->protect(function($fairy_type) use($container){
    $spell_type = $container['spell_type']($fairy_type);
    $spell = $container['spell']($spell_type);
    return new Fairy($fairy_type, $spell);
});

$container['spell_type'] = $container->protect( function($fairy_type) {
    //As we don't have protected properties anymore
    //and we don't want to expose the fairy-spell map
    //we have to put it inside this method

    $fairy_spells = array(
        'pixie' => 'heal',
        'sylph' => 'wind'
    );
    return $this->fairy_spells[$fairy_type];
});

$container['spell'] = $container->protect( function($spell_type) {
    //Determine class name based on spell type
    $class = 'Fairy\Spell\'.$spell_type;
    return new $class();
});

//Good luck documenting these functions btw

This isn’t OOP at all, we had to resort to thing like use and sacrifice some flexability (since we had to move the spell map to the method). We also get no IDE completion, and have to pass a huge container around all classes if we ever need to build something.

Now someone is definitely going to say this: “You are using Pimple wrong, it should instead be used for holding services, like in the docs.”. And this is my reply: Yes, because Pimple is not a Dependency Injection container, it is a Service Locator. A true DI container should not only allow you to create services but also build instances of classes while automatically injecting required shared instances into those. To get this functionality out of Pimple you are still going to write factory classes, so what’s the point?

Approaches based on XML and YML are even worse, because now instead of using classes and interfaces to get dependencies I have to lookup how some other developer decided to name a particular instance, e.g. how in symfony2 to inject doctrine you need to add ‘@doctrine.orm.entity_manager’ to your YML file.

It seems that all of these approaches were created int he time when DI was something foreign to PHP and people needed things to ease them into it. Right now, when everybody is doing it let’s just ditch this thing ok? Can we just use proper classes and factories instead of some weird annotations and configurations please?

comments powered by Disqus