With the move from a mostly procedural to a mostly OOP based architecture in Drupal 8, many Drupal developers have been introduced to new concepts we don't fully understand. I myself was terrified of notions such as dependency injection and service container, making me worry about the future of my Drupal development.
So I took it to Symfony (from where many components have been borrowed) and then Drupal 8 alpha releases and turns out it's not a big deal. All you need in order to understand them is to know basic OOP principles. Check out Larry Garfield's timeless introductory article to Object Oriented Programming on Drupal Watchdog for a good start.
In this article I am going to talk a bit about what dependency injection is and why one would use a container for managing these dependencies. In Symfony and Drupal 8 this is called a service container (because we refer to these global objects as services). Then, we will take a look at how these are applied in Drupal 8. Briefly, because you don't need much to understand them.
So what is dependency injection?
Take the following simple class:
class Car {
protected $engine;
public function __construct() {
$this->engine = new Engine();
}
/* ... */
}
When you instantiate a new class Car
you go like this:
$car = new Car();
And now you have an object handler ($car
) that has an $engine
property containing the handler of another object. But what if this car class needs to work with another engine? You'd have to extend the class and overwrite its constructor for each new car with a different engine. Does that make sense? No.
Now consider the following:
class Car {
protected $engine;
public function __construct($engine) {
$this->engine = $engine;
}
/* ... */
}
To instantiate an object of this class, you go like this:
$engine = new Engine();
$car = new Car($engine);
Much better. So now if you need to create another car using another engine, you can do so easily without caring about the Car
class too much since it is supposedly equipped to work with all the engines in your application.
$turbo = new TurboEngine();
$car2 = new Car($turbo);
And that is dependency injection. The Car
class depends on an engine to run (dooh), so we inject one into its constructor which then does what it needs to do. Rather than hardcoding the engine into the Car class which would not make the engine swappable. Such constructor injections are the most common but you'll also find other types such as the setter injection by which we would pass in the engine through a setter method.
So what is this container business?
So far we've seen a very simple class example. But imagine (rightfully) that the Car
has many other potentially swappable components (dependencies), like a type of gear shift, breaks or wheels. You have to manually instantiate all these dependent objects just so you can pass them to the one you actually need. This is what the container is for, to do all that for you.
Basically it works like this. You first register with the container your classes and their dependencies. And then at various points of your application, you can access the container and request an instance of a particular class (or service as we call them in Symfony and Drupal 8). The container instantiates an object of that class as well as one of each of its dependencies, then returns you that service object. But what is the difference between services that we usually access through the container and other PHP classes?
A very good definition that makes this distinction comes from the Symfony book:
As a rule, a PHP object is a service if it is used globally in your application. A single Mailer service is used globally to send email messages whereas the many Message objects that it delivers are not services. Similarly, a Product object is not a service, but an object that persists Product objects to a database is a service.
Understanding how the container works under the hood is I believe not crucial for using it. It's enough to know how to register classes and how to access them later. There are multiple ways to register services but in Drupal 8 we use YAML files. In Symfony, you can use directly PHP, YAML or even XML. To know more about how to do this in Drupal 8, check out this documentation page. Accessing the services in Drupal 8, on the other hand, is done in one of two ways: statically and using, yet again, dependency injection.
Statically, it's very simple. We use the global \Drupal
namespace to access its service()
method that returns the service with the name we pass to it.
$service = \Drupal::service('my service');
This approach is mostly used for when we need a service in our .module
files where we are still working with procedural code. If we are in a class (such as a form, controller, entity, etc), we should always inject the service as a dependency to the class. Since I covered it elsewhere and the Drupal documentation mentioned provides a good starting point, I won't go into the exact steps you need to take in order to inject dependent services into your Drupal 8 classes. However, you can check out my introductory series on Drupal 8 module development, on Sitepoint.com, where I covered the process of creating services and injecting them as dependencies (in the third part, published soon if not already).
Conclusion
So there you go. Dependency injection is a very simple concept that has to do with the practice of decoupling functionality between classes. By passing dependencies to objects we can isolate their purpose and easily swap them with others. Additionally, it make is much easier to unit test the classes individually by passing mock objects.
The service container is basically there to manage some classes when things get overwhelming. That is, when the number grows and the number of their dependencies also increases. It keeps track of what a certain service needs before getting instantiated, does it for you and all you have to do is access the container to request that service.
Hope its clear.
Daniel Sipos
Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.
Comments
excellent!
What a wonderfully clear description! Bookmarking this gem for sure.
One question though:
There's only 2 articles listed there... "Building a Drupal 8 Module: Blocks and Forms" and "Build a Drupal 8 Module: Routing, Controllers and Menu Links", to which 'third' do you refer? ;-)
In reply to excellent! by just-passin-thru (not verified)
Hey there,
Hey there,
Thanks! Sorry about that, the third part will be published in 2 days.
D
In reply to excellent! by just-passin-thru (not verified)
Here is the third part.
Here is the third part.
Good luck!
In reply to Here is the third part. by admin
brilliant...
thanks!
Great Explanation
Dependency injection is a great approach towards programming and it can also be applied into other programming languages.
Thanks for very well-explained article.
Clear and concice. Thanks!
Clear and concice. Thanks!
getContainer()
D8 says that getContainer() is deprecated. Where do I get the container now? Should I use static::$container?
Thanks,
Rainer
A very good birds-eye view of
A very good birds-eye view of DI and Services. Thanks for taking the time to write and publish.
Very good explanation of Depenency Injection
Very well explained. Thank you very much!!!
Nice explanation!
I was referred some of D8's core module for dependency injection. But no luck due to lack of core concept.
This post is awesome :)
unclear
"You should use injection but i won't tell you how" is not a particularly useful article...
Most results on using dependency injection in drupal are for when you define other services, and then it looks really clean. But what about when the class that needs the dependencies is not itself registered as a service, like some form class? Or even worse, when it's not even instantiated, because just a static method of it is used for a menu router item somewhere?
In reply to unclear by v (not verified)
This is an introductory
This is an introductory article to the concept of Dependency Injection and a bit of how it's used in Drupal. For more in depth "how to", check out this article:
https://code.tutsplus.com/tutorials/drupal-8-properly-injecting-dependencies-using-di--cms-26314
And it's true. Not all cases can use DI in Drupal 8. Nobody is expecting you to when it's not possible.
Excellent?
Add new comment