Entity view (Content)

Laravel IoC Container and Dependency Injection

By stran
Feb. 19, 2015

I recently had a chance to take a look at the Laravel framework and its IoC (inversion of control) container.  As a D7 developer, the concept of an IoC container was quite new to me.  If you're already familiar with D8 then this should be a walk in the park. If not, then I think this should be a good stepping stone on your way to D8.

So, after 24 hours and a healthy diet of ebooks and YouTube videos, things are starting to click for me.

At the core of Laravel is an IoC container, which really is just a convenient way of doing dependency injection. Dependency injection is just a fancy way of saying that a class should be called with all its dependencies passed in, rather than having the class initializing these dependencies by itself. For example, if you are a chair builder and you require a hammer, nails and wood to build a chair. As a client, if I want to hire you to build me a chair, then I should supply you with a hammer, nails and wood. I don't know why I would ever want to hire you, but that is the idea of dependency injection: A class should only do what it needs to do. Just like you only know how to build chairs, you don't need to know where to buy hammers, nails, etc.

Consider the following example:

class ChairBuilder {

    private $hammer;
    private $nail;
    private $wood;

    public function __construct()
    {
        $this->hammer = new Hammer();
        $this->nail = new Nail();
        $this->wood = new Wood();
    }

    public function buildChair()
    {
        // build the chair
    }
}

As you can see, the class instantiates the Hammer, Nail, Wood classes on its own, which is a violation of our principle that a class should only do what it's supposed to do, and that is to build a chair. It shouldn't need to know how to get the materials to build a chair. What if the Hammer class changes in the future? That would mean that you would need to update it here as well. What if you want to swap out Hammer for something different in the future?

So, how can we clean up this code? How about this?

class ChairBuilder {

    private $hammer;
    private $nail;
    private $wood;

    public function __construct(Hammer $hammer, Nail $nail, Wood $wood)
    {
        $this->hammer = $hammer;
        $this->nail = $nail;
        $this->wood = $wood;
    }

    public function buildChair()
    {
        // build the chair
    }
}

That looks a lot better. We are passing what the class needs as part of the constructor. Now whoever is calling this class is responsible for instantiating the necessary dependencies and if one of those dependencies changes, we don't care because we are not responsible for instantiating them. This makes our code less cluttered, more reusable, and more testable.

But let's take this a step further. Does our class really need to know about the specific things required for building a chair?

Can we build a chair out of metal instead of wood? Of course we can.
Can we use glue instead of nails? Of course we can.
Can we use a rubber hammer instead of a metal hammer? Of course we can.

So, how can we generalize this code even more? How about this?

interface StrikingToolInterface{}
interface JoiningToolInterface{}
interface BuildingMaterialInterface{}

class ChairBuilder {

    private $strikingTool;
    private $joiningTool;
    private $buildingMaterial;

    public function __construct(StrikingToolInterface $strikingTool, JoiningToolInterface $joiningTool, BuildingMaterialInterface $buildingMaterial)
    {
        $this->strikingTool = $strikingTool;
        $this->joiningTool = $joiningTool;
        $this->buildingMaterial = $buildingMaterial;
    }

    public function buildChair()
    {
        // build the chair
    }
}

Much better. Again, we are still asking the caller to instantiate the dependencies for us. But this time they can swap out different components if they want to.  For example, if they want to build a metal chair, then all they have to do is create a class that implements the BuildingMaterialInterface and just pop it in. Pretty flexible, no? That, is the benefit of dependency injection.

So what about the IoC container and how does it handle dependency injection?

Well Laravel does the same thing with dependency injection, but with an additional step.  Because Laravel development is instantiating the dependencies on our behalf, we need to tell Laravel which class to use for each interface since interfaces can't be instantiated and Laravel doesn' t know which classes we want to use. For example. If you have multiple classes that implement the StrikingToolInterface such as Hammer, Mallet, SledgeHammer, etc and you want Laravel to use the Mallet class, you'll need to bind that class to that interface, like so

App::bind('StrikingToolInterface', 'Mallet');

Now, the first time Laravel comes across this dependency, it will use instantiate the Mallet class and replace the StrikingToolInterface with it.

Post Tags: