Laravel’s IoC Container and Dependency Injection decoded

Stefan Brankovikj
15 min readOct 27, 2020

--

Are you getting started with Laravel, or any other modern PHP framework in general? Or you are already using a framework that already supports Dependency Injection and has a IoC container, but you want to understand how this magic works behind the scenes? Then you are at the right place, because I will try to break this down and explain how the IoC Container and Dependency Injection are bound together and working under the hood.

Definitions and background

Before we get into details and concrete examples, I think it is good idea first to explain the buzzwords (IoC Container and Dependency Injection) and give bit of a background.

So what is the IoC Container?

In my opinion, the IoC container definition and function is pretty straight forward. So I think the simplest definition for it would be:

The IoC container is piece of code that is aware of all application instances, knows their dependencies and how to resolve any of those instances.

But also, just to be more concrete and to provide more details, it is also worth mentioning that the acronym IoC is from Inversion of Control:

Inversion of Control is technique which, if followed properly, your code structure would be separated in “what to do” and “when to do” parts, where the “when to do” part will know as little as possible for the “what to do” part.

Simple Inversion of Control examples:

  • Dependency Injection.
    Injecting some class into constructor is the “when to do” part (since it is called by some client). The container that should instantiate and inject the object is “what to do” part.
  • Events handling.
    Dispatching new event is the “when to do” part (since it is triggered by some client). The event listener coresponds to the “what to do” part.

What about Dependency Injection?

As stated above, the Inversion of Control is the technique, and the Dependency Injection is one form on the Inversion of Control, where the logic on how the objects are instantiated and injected is stored on the IoC container.

Dependency Injection is removing the hard-coded instantiating of concrete classes from the client object. With Dependency Injection, the client objects will receive the other objects (services) that it depends on, and the client object will have no control of how these dependencies are instantiated.

Enough of the theory for now. Let’s see some code examples.

Inversion of control, what actually is inverted?

In order to get better understanding of the definitions and the theory from above, I think it was about time to show some code examples. I will do my best to be as close as possible to real case scenarios, since personally I sometimes find it hard to understand examples with Foo, Bar.. etc class names.

First, let’s see some simple PHP code, where we have a controller, that is calling some business logic layer in order to register a new user. Simple straightforward code, with no Dependency Injection.

  1. We have client object RegisterController
  2. RegisterController is now ready to call the register method
  3. But RegisterController in order to register the user, needs to create its dependency CreateNewUserAction
  4. CreateNewUserAction will have to create the concrete dependency PostgresUser and also create instance of another dependency, the generic User model
  5. In order to hash the chosen user password, CreateNewUserAction should also call static method from BcryptHasher, which also indicates that CreateNewUserAction depends on the BcryptHasher

As you can see, in this example, the client objects are responsible for instantiating concrete dependencies. In this case, we have concrete database repository and also a static call.

Let’s see the same example, but using Dependency Injection

  1. We have client object RegisterController
  2. RegisterController has CreateNewUserAction as dependency so the IoC container will try to instantiate the CreateNewUserAction
  3. While instantiating the CreateNewUserAction, the IoC container will first need to instantiate the PostgresUser and BcryptHasher
  4. After instantiating PostgresUser and BcryptHasher is done, CreateNewUserAction is ready to be injected with its dependencies into the register method inside the RegisterController
  5. RegisterController is now ready to call register method

You could spot the main difference now, and what is inverted with the IoC technique. Basically the method register is not able to be called until all of the dependencies are resolved, meaning that it will always first instantiate the dependencies. So if we see this example as a tree, where the RegisterController is the root, and the PostgresUser or BcryptHasher are the leafs, then the leafs are gonna be resolved and instantiated first, and the root last. That is the responsibility of the IoC container.

I want to point out, that you are probably more familiar with the terms Dependency Injection Container (DI Container), or Service Container (since it contains the services (dependencies) that can be injected) instead of the IoC container. As I said in the beginning, the Inversion of Control is very generic name, these are just forms of it. But these are all referring to the same thing, and the whole point is to separate the “what to do” and “when to do” parts.
So simple, the RegisterController doesn't know "what to do" in order to instantiate the CreateNewUserAction, but the IoC Container (Illuminate\Container\Container) does. The IoC Container doesn't know "when to do", since it is not aware when the CreateNewUserAction will need to be created for the RegisterController, but the RegisterController does.
That is how we have "what to do" and "when to do" separated with the IoC Container.

But then, okay, we are using Dependency Injection, but still using the concrete classes. In the next example, I will use the Laravel framework to provide more real life example.

Right now, we can see that we rely on interfaces as well, which is always a good idea. It helps unit testing, replacing implementations is easy as well. But how are the interfaces actually instantiated? How the IoC container knows what instance should be returned instead of the interface?

Replacing abstracts (interface, custom abstracts) with concrete classes

Based on the last example, seems like the interfaces are instantiable, but we all know that is incorrect. So before I start explaining the interfaces that are from our source code (not from the Laravel), let’s see for example how Laravel is making instance of the Illuminate\Contracts\Routing\ResponseFactory which is the one we have injected inside RegisterController.

So, if we check the usages of the ResponseFactory we will see that there are a lot of usages, and that is expected, since it is an interface, but I would really like to pay attention on the usages that are inside any ServiceProvider class. These are basically set of classes that are called when the framework is booting up in order to set all the requirements to run properly. You can read more about Service Providers in Laravel.

To continue with our investigation. The piece of code that we are interested in here is in Illuminate\Routing\RoutingServiceProvider@registerResponseFactory

So what is going on here? We can translate the registerResponseFactory method into human understandable sentence:
- When the application ($this->app) needs to inject the interface Illuminate\Contracts\Routing\ResponseFactory somewhere, return a new singleton instance of Illuminate\Routing\ResponseFactory. Don't let the name confuse you, these are two different classes in two different namespaces.

So it is not magical at all. Somewhere in the framework there is some piece of code that, when asked, knows that when some interface should be injected, it needs to return concrete implementation. And yes, if you double check the Illuminate\Routing\ResponseFactory you will see that it implements the Illuminate\Contracts\Routing\ResponseFactory interface.

You are probably thinking, okay, Laravel’s interfaces are handled by the framework, so we don’t have to worry about them. But we still have the UserRepository on our end, which is interface.. and also the CreateNewUserAction which is concrete class. We need to resolve them somehow?

That is true, since now if we expose our code as endpoint, and try to execute it, we are gonna see error message stating: Target [App\DAL\User\UserRepository] is not instantiable while building [App\Actions\User\CreateNewUserAction]. And if we go step deeper, we will just confirm that the register method is not even called. It is not yet executed according to the error stack trace. So the Laravel's IoC Container checked what are the requirements in order to call the register method, tried to instantiate the dependencies, and while instantiating CreateNewUserAction it failed, since interfaces (In this case UserRepository) are not instantiable.

How we can solve this? Just like the Laravel’s setup, we need to have a place where we define what should be returned when some client object (RegisterController in our example) asks for some interface to be injected. Best place for that is the default AppServiceProvider.

To be honest, in actual project, I would have dedicated DalServiceProvider which is responsible only for this repository bindings. Then register the DalServiceProvider into the AppServiceProvider. But that is besides the point of the example.

And the concrete implementation hold the actual logic

Okay so, now we saw two different things. In the example where we looked at the ResponseFactory it was clear that new instance was created, but in our case, we are just giving the full name of a concrete class (basically string). So what is really going on back there?

The magic behind IoC containers

We saw two different implementations and rules on how interfaces are replaced and used. First we saw the Laravel’s ResponseFactory interface, and then another one where we needed to define implementation for our UserRepository. As we remember, two different ways and both of them work fine. So finally, let's see what is happening behind the scenes.

Please note that I will use the Laravel framework as example. I would say that most of the modern framework containers are build using the same principle, but just different implementation. The goal is the same across frameworks.

If we compare both examples mentioned above, we are gonna see one thing that they have in common. Both of them are calling some methods on some application ($this->app), and we can follow and track down from our DalServiceProvider what type of variable is this.

We can see that our DalServiceProvider does not have this variable defined anywhere, so must be coming from the parent class Illuminate\Support\ServiceProvider. If we open this class, at the very top as first defined property we are gonna see that the $app variable is defined, and it must be an interface of type Illuminate\Contracts\Foundation\Application.

But then, we are still looking at interfaces, we will not find any logic there. That means this application is some concrete instance that is being passed through the construct method of the ServiceProvider and that instance should hold all our answers. So let's check how this service provider is instantiated. Since this is an abstract class, we are gonna find usages of classes that inherit this one. After checking the usages, you will probably be pointed out to the Laravel's Illuminate\Foundation\Application class, and if you check the interfaces, the Illuminate\Contracts\Foundation\Application is implemented on this class.

The Application class in Laravel is literally the Laravel itself (in a way). It have all the logic on how the application should be booted, configurations, which service providers to be started, what paths to be loaded, basically all settings for the framework it self. Since the Application hold all this logic, means that this class should always be instantiated first, since it should know everything there is. And if you check the Laravel entry point file public/index.php, you are gonna see that there is a line where the application is defined: $app = require_once __DIR__.'/../bootstrap/app.php';

If you read the first comment you basically already noticed that the Application is referred to be the IoC container for Laravel. And that is correct in a way, since by having instance of Illuminate\Foundation\Application you will be able to register new bindings, singletons, instances (you can find more about the Laravel's Container features), but the real magic is in the parent class Illuminate\Container\Container. So finally we found the magician that is responsible for the magic behind the scenes.

If you take a quick look of the Container you will notice that there are many arrays as properties at first glance. And that is where the power of the container starts. In this case, the Laravel's container has a lot of features supported, so it has kinda one property array for each feature. But for the sake of this example, let's try and decode the bind method.

What will happen behind the scenes, it is really simple, basically the Container will just add new value to it's $bindings property, and next time when the client objects needs the interface, the IoC container will check the $bindings property and build the concrete implementation for it. Let's see the examples

Okay so, basically there will be an array, where the key is the actual class name including the namespace of the class, and as values for that key is going to be the concrete class. So it doesn’t matter if we provide a Closure (the example with the response factory), or we provide concrete class namespace, the point is to give some information about our concrete implementation for this interface to the IoC Container.

Right now, the IoC container, does know what to do when any client object asks for the UserRepository interface. The only thing that is left explaining is how the concrete implementation is built.

Please note that, I am trying to not go too much into details on this, since Laravel’s container is, again, really advanced and has a lot of features. I just want to explain the main points what is making one class an IoC container.

Reflection Classes. The real magic

If you already have seen the Illuminate\Container\Container class, you probably noticed a lot of stuff going on there. I will try to break it down using simple concrete examples, that are still from this IoC container.

Let’s continue with the example that we already have, using the RegisterController. So, in real world scenario, we are gonna have some routing file, where we define the route path and which action should be called based on the route path. That means we are gonna probably define the route something like this

This means, in order the router to call the register method from the App\Http\Controllers\RegisterController class, it needs to somehow:

  1. Resolve the dependencies for RegisterController
  2. Resolve the dependencies for CreateNewUserAction
  3. Create new CreateNewUserAction instance with the resolved dependencies
  4. Create new RegisterController instance with the resolved dependencies (in this case CreateNewUserAction)
  5. Resolve dependencies for the register method from RegisterController
  6. Call the register method from RegisterController with the resoled dependencies

Looks familiar right? Inversion of Control, first we resolve the leafs, then we create the root and finally we call the needed method.
Let’s start with resolving the constructor dependencies for RegisterController

All this magic, that allows the IoC Container to have information on how to resolve the class, if only full class name is provided, is based on usage of ReflectionClass.

The ReflectionClass has all the information for the given full class name. Please note that this needs to be an existing class, otherwise it will throw an exception. There are a lot of methods and options available on a ReflectionClass instance, but let's keep focus only those that are in the scope of this post

First step: Check if the given class is instantiable

The ReflectionClass object of any class, can give us information if we can make instance of this class or not. Just like we had the Target [App\DAL\User\UserRepository] is not instantiable while building [App\Actions\User\CreateNewUserAction] error, when we tried to execute the code, without telling the IoC Container what to do when we ask for UserRepository.

Let’s see how Laravel’s Container is handling this, and how we got the error message

So initially, what is going on is, the IoC container will try to find any alias, instances definition or bindings of some sort to locate the concrete implementation that should be instantiated. Since we never registered this interface, the getConcrete method will return the same UserRepository namespace, resulting the method isBuildable to return true since the $concrete is literally same value as the $abstract

  1. The container tried to resolve the UserRepository.
  2. The App\DAL\User\UserRepository is the actual value, and that is string, so the check if that is closure will return false
  3. The class already exists, so no exception is thrown
  4. But the check isInstantiable will return false, since we are trying to make instance of an interface
  5. You can see the format of the error on line :49 on the code example

But after we fix this issue, and bind the UserRepository with the PostgresUser class inside the AppServiceProvider, the $concrete variable will be PostgresUser, and also this will try to build the PostgresUser resulting the isInstaintable to return true. So, since we have instantiable class name, the container will try now to make instance of it, and resolve it.

Second step: Find constructor dependencies

As mentioned above, we have the App\DAL\User\PostgresUser to be resolved and instantiated. The IoC Container now knows that this class can be instantiated, but it yet doesn't know what are the dependencies for it.

The ReflectionClass also can give information on that matter using the getContructor() method. This is going to return an instance of ReflectionMethod, but it will contain all of the information related to the PostgresUser's __construct method.

From the example above it is clear that we can see the required parameters for instantiating the concrete PostgresUser class.

So let’s see how the Laravel’s Container will handle this

They use the same method to get all the information about the concrete class that should be instantiated (in our example PostgresUser), and extracting the defined dependencies. Since this is Inversion of Control, first it is required the dependencies to be resolved in order to have everything needed to make instance of PostgresUser. I wanted to make this shorter, since Laravel's Container is really advanced, so how they are resolving the dependencies is basically with iteration through all of the $dependencies (which is array of ReflectionParameter). Each dependency has the typeHint which can also be passed through the same flow that we are explaining now (basically loop that is going through each dependencies and repeating everything that we are explaining now, check if is interface, get the concrete class, try to build it).

Step three: Making instance of the concrete class

After all of the $dependencies are resolved, we have new variable called $instances which is array of instances taken from the $dependencies. In our case, it is an array containing instance of the App\Models\User class.

Now we are ready to make new instance of the class. The newInstanceArgs method accepts array as argument and it will use this array to make new instance of the reflected class. This is blackbox for us, since it is native part of PHP, but I would assume that behind the scenes it is happening something like new PostgresUser(...$instances); if we translate that into PHP example (of course I oversimplified this, there must be more than just one line). You can read more about the ... splat operator here.

After this step, we have created the PostgresUser using the UserRepository interface. This means if we at some point of time, need to change the implementation for UserRepository and we start to use MongoDbUser instead of PostgresUser, we can do it by not changing the CreateNewUserAction class at all, since it is using an abstraction for dependency, not an concrete class. The only changes that have to be done are to replace the binding in the AppServiceProvider, and of course, create new MongoDbUser class which implements the UserRepository interface.

At this point we know how interfaces and concrete classes are resolved through the constructor. So just to make quick summary, in order the router to call the register method from the App\Http\Controllers\RegisterController class, the IoC container, as explained above, will:

  1. Resolve the dependencies for RegisterController
  2. Resolve the dependencies for CreateNewUserAction
  3. Create new CreateNewUserAction instance with the resolved dependencies
  4. Create new RegisterController instance with the resolved dependencies (in this case CreateNewUserAction)
  5. Resolve dependencies for the register method from RegisterController
  6. Call the register method from RegisterController with the resoled dependencies

Please note that I am keeping the example to two nested levels with resolving. This can go for more than two nesting levels, of course, if the classes are dependent on multiple classes, that are also dependent on multiple classes.. and so on, this will be just bigger flow, but the order is the same. The dependencies are always instantiated first.

Step four: Calling the register method

Since we already know that the IoC Container just needs a full class name, than doing Dependency Injection for the method shouldn’t be any problem.

Just like for a class we can get all the information we need, we can do the same for the methods. There are two ways of doing this, and both of them return instance of ReflectionMethod

At this point we have all information that we need. If we stick with the example with router calling controller action, then the router now can easily call the controller action method (in this case the register method). We have instance of controller, and we have the arguments of the register method.

PSR-11: Container interface

It is also worth mentioning that in this example I used the Laravel’s Service Container just to give the example, since I have the most experience using the Laravel framework (since 4.1 actually), so I feel comfortable explaining this. Since having IoC is really common in most of the frameworks these days, the PHP Framework Interop Group (PHP-FIG) are trying to create common standards between frameworks and libraries in order to provide reusabilities across different frameworks and libraries. For the IoC Container, one of the PHP Standards Recommendations is dedicated on the ContainerInterface (you can check the PSR-11: Container Interface).

The main purpose of this PSR is to allow one framework to take advantages of IoC Container of personal choice and references. Even the Laravel’s Service Container is implementing this standard, where they have their own Container Interface (Illuminate\Contracts\Container\Container) which is extending the PSR-11 defined interface.

And the implemented methods in the actual concrete Laravel’s implementation Illuminate\Container\Container

Let’s also take a look at another popular IoC Container PHP-DI and their implementation on the methods

There are a lot of other IoC Containers that are popular and highly used. These are just two examples that I used to explain the usage of a IoC Containers. Their purpose is the same, prepare, manage, and inject application dependencies. the implementations vary.

Summary

IoC Container, DI Container or Service Container — however you wanna call them, they all refer the same. In my opinion the most precise term is DI Container since it is referring to what exactly is the purpose of the container. There is no magic into any of these container (or any other feature in general, in my opinion), only a lot of hours and effort to give that feel since is really simple and easy to use. And all these implementations are mostly based on the Reflection* PHP classes, which contain all information about classes, interfaces, methods etc.

--

--

Stefan Brankovikj

PHP Developer since 2013. Spent most of my career working as full stack web developer, with a small turns to GoLang and Unity in my spare time