Laravel’s IoC Container and Dependency Injection decoded
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.
- We have client object
RegisterController
RegisterController
is now ready to call theregister
method- But
RegisterController
in order to register the user, needs to create its dependencyCreateNewUserAction
CreateNewUserAction
will have to create the concrete dependencyPostgresUser
and also create instance of another dependency, the genericUser
model- In order to hash the chosen user password,
CreateNewUserAction
should also call static method fromBcryptHasher
, which also indicates thatCreateNewUserAction
depends on theBcryptHasher
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
- We have client object
RegisterController
RegisterController
hasCreateNewUserAction
as dependency so the IoC container will try to instantiate theCreateNewUserAction
- While instantiating the
CreateNewUserAction
, the IoC container will first need to instantiate thePostgresUser
andBcryptHasher
- After instantiating
PostgresUser
andBcryptHasher
is done,CreateNewUserAction
is ready to be injected with its dependencies into theregister
method inside theRegisterController
RegisterController
is now ready to callregister
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:
- Resolve the dependencies for
RegisterController
- Resolve the dependencies for
CreateNewUserAction
- Create new
CreateNewUserAction
instance with the resolved dependencies - Create new
RegisterController
instance with the resolved dependencies (in this caseCreateNewUserAction
) - Resolve dependencies for the
register
method fromRegisterController
- Call the
register
method fromRegisterController
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
- The container tried to resolve the
UserRepository
. - The
App\DAL\User\UserRepository
is the actual value, and that is string, so the check if that is closure will returnfalse
- The class already exists, so no exception is thrown
- But the check
isInstantiable
will returnfalse
, since we are trying to make instance of an interface - 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:
- Resolve the dependencies for
RegisterController
- Resolve the dependencies for
CreateNewUserAction
- Create new
CreateNewUserAction
instance with the resolved dependencies - Create new
RegisterController
instance with the resolved dependencies (in this caseCreateNewUserAction
) - Resolve dependencies for the
register
method fromRegisterController
- Call the
register
method fromRegisterController
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.