PHP: A noob explanation for Dependency Injection and DI Container
If you are probably developing something, you may have bumped on Dependency Injection somewhere along the lines of your code. This may help you alleviate that “And how it works?” questions crossing your mind. And, now, it is not magic.
Dependency Injection, as the name says, “injects” a dependency. So, what dependency? For who? And how it injects that “dependency”. Well, let’s have a short trip about the Design Patterns, shall we?
How to design something
In PHP, and in almost every development language, design patterns exists. There are always multiple ways to do something, from the very unorthodox and complex to simple “I didn’t know you could do that” forms.
Each way has its advantages and caveats, but more importantly, the better ones are those that allow your code to be understandable and maintainable.
There is a extensive list of patterns in this web page, which you can check out for simple explanations and simple code examples. For now, I will focus in the Dependency Injection and Dependency Injection Container.
Dependency Injection
The Dependency Injection pattern means to add classes that another class depends to work when we instantiate it.
For example, we need to store an image in the server. To do that, we can use a class called “Image”, which depends on one class, the Storage.
class ImageSaveHandler { protected $storage; public function __construct(Storage $storage)
{
$this->image = $image;
$this->storage = $storage return $this;
} public function save($image)
{
return $this->storage->store($image);
}}// Save the image
$storage = new FileStorage();$handler = new ImageSaveHandler($storage)$handler->save($image);
The Storage has a nice handler called store()
, which allows to persist the image in the Storage of our choice. In this example, we are using FileStorage
, which persists the image in the hard disk.
This allows also to change Storage implementation. For example, if we want to change from a FileStorage
to DatabaseStorage
, it won’t be a problem as long as these implements the Storage interface, or extends a Storage abstract class. That will depend on the framework or developer for which is.
Why an abstract class or interface? Because these acts like a constraint these classes must implement to keep working in our ImageSavelHandler
. As you can see, we use the $storage->save()
method. If we changed the storage for another that didn’t implemented that particular method, our ImageSaveHandler
won’t save the image, and hence an error will occur.
And that’s it. Dependency Injection means to add a class to another class that depends on it to work, and be able to change it without breaking up the final class.
Dependency Injection Container
The Dependency Injection Container elevates the approach to a whole new level. This is also called Service Container, because it holds all the services that usually are Singletons (classes that can only instantiated once with some tricks), like Cache, Storage, Database, Mail, Templating, etc, but also other classes, in an internal array.
Yes, all of your application, or at least the core of it, could be living in just a single Class. Some frameworks call it Service Container, other Dependency Container, etc. Like, for example the Symphony2 Dependency Injection Component.
I will take the liberty of embed a basic Container class from Dependency Injection (DI) Container in PHP article by Tech Tawatal. It a understandable blueprint of a Container.
This “Container” has four methods. Two of them are a get and set, while the other two do the magic, making instantiating objects much more easily because it injects automatically their dependencies when we need them.
In layman’s terms, the Container class will have an array of “Services”, which are basically other Classes that provide some kind of functionality, that you can register using set()
, but when constructed they expect other Classes because they depend on it. Like our ImageSaveHandler.
After registering every Service in the app, we will get them later in our code using the “get” method. With the help of the built-in class ReflectionClass, it will analyze what each Service needs when it’s constructed, like looking for its dependencies, and finally return it with the dependent classes already injected as long as they’re in the array.
This makes the app more modular, and less of a pain to meddle if you decide to change things. It also make testing better because you can change the real classes for mock classes, or change implementations for the speed of testing, like an SQLite in-memory database (which is miles faster).