Understanding IoC Containers and Dependency Injection

Put simply (because it’s not a problem limited to OOP world only), a dependency is a situation where component A needs (depends on) component B to do the stuff it’s supposed to do. The word is also used to describe the depended-on component in this scenario. To put this in OOP/PHP terms, consider the following example with the obligatory car analogy:

class Car {

    public function start() {
        $engine = new Engine();
        $engine->vroom();
    }

}

Car depends on Engine. Engine is Car‘s dependency. This piece of code is pretty bad though, because:

  • the dependency is implicit; you don’t know it’s there until you inspect the Car‘s code
  • the classes are tightly coupled; you can’t substitute the Engine with MockEngine for testing purposes or TurboEngine that extends the original one without modifying the Car.
  • It looks kind of silly for a car to be able to build an engine for itself, doesn’t it?

Dependency injection is a way of solving all these problems by making the fact that Car needs Engine explicit and explicitly providing it with one:

class Car {

    protected $engine;

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

    public function start() {
        $this->engine->vroom();
    }

}

$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);

The above is an example of constructor injection, in which the dependency (the depended-on object) is provided to the dependent (consumer) through the class constructor. Another way would be exposing a setEngine method in the Car class and using it to inject an instance of Engine. This is known as setter injection and is useful mostly for dependencies that are supposed to be swapped at run-time.

Any non-trivial project consists of a bunch of interdependent components and it gets easy to lose track on what gets injected where pretty quickly. A dependency injection container is an object that knows how to instantiate and configure other objects, knows what their relationship with other objects in the project are and does the dependency injection for you. This lets you centralize the management of all your project’s (inter)dependencies and, more importantly, makes it possible to change/mock one or more of them without having to edit a bunch of places in your code.

Let’s ditch the car analogy and look at what OP’s trying to achieve as an example. Let’s say we have a Database object depending on mysqli object. Let’s say we want to use a really primitive dependency indection container class DIC that exposes two methods: register($name, $callback) to register a way of creating an object under the given name and resolve($name) to get the object from that name. Our container setup would look something like this:

$dic = new DIC();
$dic->register('mysqli', function() {
    return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
    return new Database($dic->resolve('mysqli'));
});

Notice we’re telling our container to grab an instance of mysqli from itself to assemble an instance of Database. Then to get a Database instance with its dependency automatically injected, we would simply:

$database = $dic->resolve('database');

That’s the gist of it. A somewhat more sophisticated but still relatively simple and easy to grasp PHP DI/IoC container is Pimple. Check its documentation for more examples.


Regarding OP’s code and questions:

  • Don’t use static class or a singleton for your container (or for anything else for that matter); they’re both evil. Check out Pimple instead.
  • Decide whether you want your mysqliWrapper class extend mysql or depend on it.
  • By calling IoC from within mysqliWrapper you’re swapping one dependency for another. Your objects shouldn’t be aware of or use the container; otherwise it’s not DIC anymore it’s Service Locator (anti)pattern.
  • You don’t need to require a class file before registering it in the container since you don’t know if you’re going to use an object of that class at all. Do all your container setup in one place. If you don’t use an autoloader, you can require inside the anonymous function you register with the container.

Additional resources:

  • Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
  • Don’t look for things — a Clean Code Talk about IoC/DI

Leave a Comment