Today I will write about this topic because I realize that some software developers have difficulties using dependency injection concepts in real applications.
What is Dependency Injection?
First, look at this code:
This code works, but it also generates a serious problem! When we create an instance of Logger inside FirstExample’s constructor, we create a tight coupling between these two classes.
Imagine if we need to implement this pattern in several classes of our application, or worse, and if at some point it is necessary to change the way Logger should be instantiated.
It makes me nauseous just thinking about it!
To resolve this, we can transfer the creation of the Logger instance (dependency) outside the FirstExample class and pass it in its construction, for example:
This is dependency injection, that is when we “inject” a class instance into another, where the “injected” class is the dependency.
Golden tip
There is a very cool principle in SOLID called Dependency Inversion principle which, among other things, suggests that our classes depend on abstractions and not on concrete classes.
As abstractions (read: interfaces) expose a usage contract “obliging” the classes that implement it to follow it, we can use any class that establishes this contract as a dependency, making implementation more flexible and reducing coupling.
Knowing this, we can refactor the example class by replacing Logger with LoggerInterface, since the component we are using (monolog) implements the PSR3 that defines the LoggerInterface as the default log implementation for PHP.
With refactoring, the code looks like this:
How and why to use a dependency injection container?
A container what!?
In summary, a dependency injection container serves, among other things, to abstract control over which instances should be created, how they will be done (configuring definitions, for example), and when this is necessary.
This is our example code refactored to use a dependency injection container:
On line 7, we declare the use of PHPDI, a dependency injection container widely used in PHP projects.
The code is the same as that used in the last examples, and the changes occur from line 32, where we create an instance of the container. On line 34, through the addDefinitions() method, we define the injections that will be used in our application.
On line 40, through the build() method, we create our container. On line 42, through the get() method, we create an instance of FourthExample assigned to the $exampleController variable, and through it, we can invoke any FourthExample class method without problems.
Note:
Note that on line 48, I declared an alternative way of instantiating a class through the container using the call() method, that way, we pass the class name that we want to instantiate and the method that should be invoked followed by an array with the parameters that the method expects to receive.
What’s changed?
Realize that at no time do we need to instantiate and/or “inject” the necessary dependency(s) for the FourthExample class to work correctly, as the container is responsible for carrying out this task, and this is one of the great insights in if using a dependency injection container.
Note:
In some cases, it will not be necessary to configure the injections for our applications, and for these cases, we can use a feature that is already enabled by default with PHPDI, auto wiring.
As a complement, I suggest you read about the autowiring feature and how to define injections manually with the dependency injection component we are using.
I created a project that runs all the examples used in this article through the terminal. This is a basic application that generates logs (proposed in the above codes), with a log for each executed example.
The repository link on GitHub its here.
We have reached the end of another article. I really hope this content was useful for you.
See you next time!
Leave a comment