A Tale about Dependency Injection Containers

Amy Chen
4 min readJul 24, 2015

--

The Problem

Note: The description of the problem will be long. But I think its important to thoroughly understand what problem you are trying to solve before seeing a random design pattern and implementing it for the wrong use case.

So you finished creating a library or framework, and being the good programmer that you are, these libraries and frameworks never instantiate their dependencies and are instead injected into the constructor or method parameters.

Library Code

class YayAClass 
{
public $param1;
public $param2;
.
.
public $param9;
public function __construct (
Object1 $param1,
Object2 $param2,
.
.
Object9 $param9

) {

$this->param1 = $param1;
$this->param2 = $param2;
.
.
$this->param9 = $param3;
}}

Above, you’ll see that YayAClass depends on Object1, Object 2, … , and Object9. From this, you can create a dependency graph.

All of a sudden, you step back from all these libraries you created — that push the responsibility of providing the dependencies to a 3rd party — and ask…

“Now what?”

Outside of the library level, you have reached the application level. This is where we implement the Composition Root pattern and compose the actual objects.

Composition Root

This is ideally the one time you are allowed to say the keyword new. This is also the part where you trigger the objects to actually do things to make your application work.

Ideally, you always want the composition of your super clean, dependency injected objects to occur as close to the application entry point as possible in one location.

Application Code

$object1 = new Object1( 'specific', 'configuration' );
$object2 = new Object2( 'another', 'specific', 'configuration' );
.
.
$object9 = new Object9( 'more', 'specific', 'configuration' );
$yayAClass = new YayAClass(
$object1,
$object2,
.
.
$object9
);

And here, is where we finally see the long awaited PROBLEM.

The Problem

Imagine that the dependency graph is infinitely more complicated than what is shown above. For instance, maybe Object1 depends on a bunch of things, and so does Object9. Also imagine that you need a new instance of YayAClass multiple times.

Currently, every single time you need YayAClass, you need to do that huge setup you see above. What a hassle!

Also, what happens if the constructor signature changes for YayAClass? You would have to change every new instantiation of YayAClass to reflect that change.

The Solution

Dependency Injection Container to the Rescue!

Now we can talk about Dependency Injection Containers. On an application level, they compose your objects for you.

So when you need to use YayAClass, it will set up YayAClass’s dependencies and configuration for you. The mess above will happen like this:

// produces an instance of YayAClass
$yayAClass = $container['YayAClass'];

You will never have to change this line of code. Every new instantiation of YayAClass is taken care of by this magical container. When YayAClass changes, you will only have to change your code once within the container to reflect this change.

What is a Dependency Injection Container?

A DI container, again, composes and holds your objects for you. They provide your objects with all the dependencies it needs, instantiates them, then returns a put together instance of that object.

In this example, we will be using Pimple, a php dependency injection container.

Pimple Implementation

use Pimple\Container;/**
* Create a Container using Pimple
*/

$container = new Container();
/**
* Here, the array keys are defined by an anonymous function. Every
* time you call $container['someParam'], it will find that array
* key and return an instantiation of whatever you defined.
*/
$container['object1'] = function () { return new Object1( 'specific', 'configuration' );} .
.
.
$container['object9'] = function () { return new Object9( 'more', 'specific', 'configuration' );}$container['YayAClass'] = function (Container $container) { return new YayAClass(
$container['object1']
.
.
.
$container['object9']
);
}/**
* Now, you can clearly see that when this line is called, a
* "put-together" (aka composed) instance of YayAClass is returned.
*
* Specifically for Pimple containers, each time you access an
* object from the container, it will be lazily called. This means
* that you will be returned the same instance of the object.
*
* If you're more interested in a factory style access. Meaning,
* you want a new instance of an object when you access the
* container. Check out the Pimple documentation:
* (
http://pimple.sensiolabs.org/)
*/
$yayAClass1 = $container['YayAClass'];/**
* Look! I don't have to set up YayAClass again!
*/
$yayAClass2 = $container['YayAClass'];

But Amy, you’re hardcoding the dependencies, doesn’t this violate some design rule?

You’ll notice that none of the Library code is changed. YayAClass still understands its exact dependencies and none of that logic is hidden upon the instantiation of YayAClass.

To further illustrate this point, I’ll paste the constructor signature of YayAClass:

public function __construct ( 
Object1 $param1,
Object2 $param2,
.
.
Object9 $param9
) {

Here, all of the params are still type hinted. In the Pimple Implementation, we still grab instances, of Object1 to construct YayAClass.

This solution also allows you to keep your library code clean and your application code manageable.

(If you enjoyed this post, please hit the recommend button below!)

--

--