Dependency Injection pattern in Symfony
Dependency injection is one of the most useful and used oriented objects pattern. Martin Fowler was the first author who spoke about this term more than ten years ago.
In this article, we will speak deeper on how to use this pattern in Symfony and its advantages, but as a first approach we can say that dependency injection is about extracting the responsibility of creating objects inside a class in order to provide these objects when we build the class.
We can understand it better through a simple example. Example class needs to use another class and it has the responsibility of creating it within its own constructor:
Class Example{
private $dependency;
__construct() {
$this->dependency = new DependencyClass();
}
//…
}
We can transform this example by using dependency injection. In this case, the Example class will be receiving its dependency as a parameter, respecting the Single Responsibility Principle:
Class Example{
private $dependency;
__construct(DependencyClass $dependencyClass) {
$this->dependency = $dependencyClass;
}
//…
}
Related concepts
In order to better understand the dependency injection idea, we will clarify some of the terms which always come up whenever this topic is discussed:
-
Inversion of Control (IoC): It is a programming style at which the framework is the one which controls the flow of the program, instead of being controlled by the developer.
-
Dependency Inversion Principle (DIP): Is one of the SOLID principles and speaks about decoupling software modules, which must be independent and not reliant of the implementation details. Dependency injection is based in this concept.
-
Service: It is an object which makes some kind of global task and can be used in any part of your application.
-
Service Container: It is an object that manages the creation of the available services instances.
-
Dependency Injection Container: It is an utility that makes the implementation of the the dependency injection easier. Using the container as a service locator is an anti-pattern.
The Symfony component
Symfony framework is built around this pattern, and this is because the framework has to take care of many tasks related with security, HTTP requests and responses, persistence, validations, cache, routing… and each one of them are delegated to different objects according to the separation of concern principle, so at the end we have a framework composed by a lot of objects which has dependencies between them and services which depend of different configuration parameters.
Working with dependency injection in Symfony is not complex because the framework counts with the DependencyInjection component, which makes the work much easier and helps the developer to abstract from the implementation details, providing a clear interface easy to work with.
Anyway, it is highly recommended to be familiar with the code, which you can see in the official Git repository. Reading a well structured and clean code and following good practices is an excellent way of learning, so you should take a look of it, although it is not necessary to go through it in a deeper way.
Using it is quite easy, and surely you have already used dependency injection without actually knowing you were. Surely you have written something like this inside a controller many times:
$this->container->get('doctrine');
Or directly:
$this->get('doctrine');
Dependency Injection in Symfony step by step
Now we are seeing all the steps you need to follow in order to use dependency injection in Symfony within a simple example: Imagine we have a class called MyService with a dependency of an object and, at the moment, the class is responsible for the creation of that object:
<?php
namespace AppBundle\Services;
use AppBundle\Services\DependencyClass;
class MyService
{
protected $dependency;
public function __construct()
{
$this->dependency = new DependencyClass();
}
}
First step, will be transforming this class in order to remove that responsibility, so from now this class will receive the service thad depends as a parameter already built.
<?php
namespace AppBundle\Services
use AppBundle\Services\DependencyClass;
class MyService
{
protected $dependency;
public function __construct(DependencyClass $dependency)
{
$this->dependency = $dependency;
}
}
At this point, we can create the first class which receives its dependency as a parameter:
$dependency = new DependencyClass();
$myService = new MyService($dependency);
At this step we come across an issue, because we need to know all the dependencies of each and every service, so we have to move forward in order to take advantages of the decoupled services we have created. At this time, the service container comes into play as the only object which knows all the dependencies between services and which helps us creating all one of them.
So now Symfony has its own container implemented and using it is as simple as it follows:
$myService = $this->container->get(my.service’);
To make this easier, Symfony provides us with a shortcut which allows us to call this service in the following way:
$myService = $this->get(‘my.service’);
But there is one more step missing before we finish finish our example: How does this container know the right way to create the service within the correct dependencies? It is, actually, very simple: we start defining all the services and its dependencies into a YAML file. This config file is usually placed in Resources/config/services.yml
services:
dependency.service:
class: AppBundle\Services\DependencyClass
arguments: [‘@swiftmailer.mailer’]
my.service:
class: AppBundle\Services\MyService
arguments: [‘@dependency.service’]
In this particular case, we are declaring two different services: the first one, called dependency.service and which has other dependencies too, includes swiftmailer -the service which allows sending emails - in this case. When we call the service my.service, thanks to the service container and the dependency injection, we can abstract from how that service is created and what are its dependencies, and with only one line of code we have both the service and its methods available.
Now we know the advantages of using dependency injection, and even more when we use it into a framework like Symfony, which saves you a lot of time while creating services while reducing the number of bugs just because there is no need to take the dependencies into account when instantantiate is needed.
Moreover, the possibility of decoupling all the services and knowing all the configuration of each and everyone of them by looking only one file it is a great advantage which saves a lot of development time and helps creating a better, cleaner and maintainable code.