Dependency Injection: What and Why?
If you use Joomla, you may have heard the term “dependency injection”. But what is it and why is it important? I’ll explain it as clearly as possible, to make it understandable for non-coders too.
Modularity
When software grows, it often becomes more and more complex. In order to keep it maintainable, we divide a larger code-base into “bite-sized chunks”, manageable pieces.
It’s the same as for instance with a car, that you can see as a collection of parts: the engine, the wheels, the brakes, etc. The engine in turn consists of smaller parts. Parts are related, work together, but it is not the case that every part is related to every other part; that would be unworkable. What we try with a good modular structure is to divide the system as much as possible in a hierarchical way: the whole system first into coarse-grained parts, like the engine, the wheels, the lights, the seats, etc. And then each part into smaller parts, etcetera, until the smallest parts. You have different levels of how finely you divide the system. You try to make the parts as independent as possible from each other: when you have a flat tire, it would be nice if the lights still work, if you have a broken window, you ‘d hope the engine still starts. We try to limit the effects of a failing part as much as possible to the direct “parent” part it belongs to. That also makes it easier to find the cause of an error: when the engine doesn't start, you don’t first check if the tire pressure is high enough.
It is the same with software. The ideal is that when you change one part somewhere, it will not affect everything all over the place. We try to limit the effects of changes as much as possible to a well defined area. In other words: we try to reduce coupling.
Interface
If you change a bulb in the car, you can leave other parts where they are. Often, such a bulb doesn’t even have to be of the same brand as the original one. It is important that it fits and has the same characteristics, like voltage and light intensity. Another way to say that: has the same “interface”. If your phone needs an USB-C interface to be charged, then you can practically use any phone charger with USB-C. The same applies in software: if some part needs a specific interface, then any piece of software with that interface will fit there. The same interface can be used with different implementations.
Interfaces are standards, contracts between parts. Interfaces reduce tight coupling to a specific brand or implementation.
Instantiation
The parts of a car are manufactured from a blueprint. The same holds for “objects” in software when using Object Oriented Programming, like we do in PHP for instance. We use a “class” as a blueprint for a new object. Such an object is called an “instantiation” of a class. If we want to have an object that we give the name “$a”, made from a class named “ClassA” as blueprint, we simply use the code: $a = new ClassA();
A tightly coupled “IKEA” car
Suppose we have a class called “Car”, and in it there are an “Engine”, “Brakes” and more. In PHP-code it would look something like this:
class Car {
private Engine $engine;
private Brakes $brakes;
// More parts here
public function __construct() {
// We need an Engine and Brakes in this Car,
// so we instantiate them.
$this->engine = new Engine();
$this->brakes = new Brakes();
}
}
A new Car is made with:
$car = new Car();
And you start the engine with something like:
$car->engine->start();
In real life it would be quite awkward when you buy a new car, and inside it, there is no engine and no brakes assembled yet, but only the instructions on how to assemble a new engine and new brakes from the blueprint.

But fortunately, this isn’t real life, this is just a code example, showing a problem you can see very often in code: we bind the implementation of the engine to a specific class, to a specific implementation of an engine. The same holds for the brakes. If instead we can bind it to an interface, we could have used many other implementations of an engine or brakes. Thus, our car would be much more loosely coupled to a specific implementation of an engine or brakes.
When you buy a new car, the assembled parts that it is dependent upon, are generally already mounted in the car. We are used to that. But in code, that is something special, with the fancy name “dependency injection”.
Dependency Injection
Instead of instantiating the parts that it is dependent upon, an object can also get those dependencies ready-made. They are put into the object during its creation. The dependencies are “injected” into the object. The Car class would then look like this:
class Car {
// We need an Engine and Brakes in this Car,
// so we inject them.
public function __construct(
private Engine $engine;
private Brakes $brakes;
){}
}
The Car doesn’t create its own Engine and Brakes anymore, but gets them from the outside. When we instantiate a new Car, we first have to instantiate a new Engine and Brakes:
$engine = new Engine();
$brakes = new Brakes();
$car = new Car(
engine: $engine,
brakes: $brakes
);
By the way, in the examples above I used “constructor property promotion” and “named parameters”, both available since PHP 8.0.
Don't use the new keyword inside your custom classes
(except for classes that are part of PHP, like DateTimeImmutable etc.).
Inject the dependencies when creating your object.
More decoupling using interfaces
So, instead of having a class create its own dependencies, we have injected those dependent objects into the constructor. But we still tied the dependencies to a specific class. We can improve this by injecting interfaces. This way, we can inject any implementation with that interface in the main object. In software an interface is just a list of methods that must be in the class that implements the interface. We can therefore define an Engine interface, with for example a start() method. If we then implement a class that implements the Engine interface, we know it has a start() method. The Car class would look the same as in the example above, but now the Engine and Brakes are interface-names, not class-names. Any object that implements these interfaces can be injected in the Car class.
In Joomla we do that for instance with the database. We don’t specify in a class which type of database we use (MySQL or PostgreSQL) and the class doesn’t know anything about how to connect to that database. We just provide (“inject”) a database object that implements a DatabaseInterface.
Decouple from a specific implementation by using interfaces.
Constructor and Setter Injection
Above you saw how to inject a dependency via a constructor. Another way to inject dependencies into an already instantiated object is via setter methods. Our Car example would then look like this:
class Car {
private Engine $engine;
private Brakes $brakes;
// more parts here
// We need an Engine in this Car, so we inject it.
public function setEngine(Engine $engine) {
$this->engine = $engine;
}
// We need Brakes in this Car, so we inject them.
public function setBrakes(Brakes $brakes) {
$this->brakes = $brakes;
}
}
In general it is best to use constructor injection for all dependencies that are required for the created object to function. In that way you cannot instantiate an object without the required dependencies. For all optional dependencies you can use setter injection. In the example above you can now instantiate a Car without an Engine or Brakes …
In Joomla, however, we mainly use setter injection. That was mainly done, because maintainers wanted to keep backwards incompatible changes in the signature of our MVC-classes as small as possible.
Dependency Injection Container
In short: the DI-container. In the above we didn’t use any “container” to do dependency injection. So, what is its purpose then? Well, if you always, when instantiating a class, have to inject a bunch of dependent objects, then you can use such a container to do that automatically for you. Joomla's DI-container has a buildObject() method that automatically fills in all the parameters in the constructor. And it can do that in a recursive way, so also resolve the dependencies of the dependencies.
However, in current Joomla we don't use this method because of the use of non-objects such as the config array as constructor-parameters, and because maintainers didn't want to change the signature of existing classes by adding the dependencies to the constructor parameters. In Joomla mainly setter-injection is used and we use a so-called "service provider" to fill in those dependencies. Within this service provider we use a DI-container to solve the dependencies. The standard core DI-container is already filled with the most needed objects, like the DatabaseDriver.
You don’t need a Dependency Injection Container to inject dependencies.
The purpose of the Joomla DI-container is to provide the setter-parameters,
used to inject the dependencies.
Misuse of the DI-container
Say you rent an apartment. You need your front door key. Instead, someone hands you a janitor's keyring with 100 keys for the entire building. Now you have to find the right key, and you have access to rooms you shouldn't enter.
Using Joomla’s DI-container outside the service providers is an alarm bell that you are coupling too much. Conjuring up the DI-container using Facory::getContainer() provides access to information in that container at places where it doesn't belong. This misuse of a DI-container is called a "service locator"; it is an anti-pattern that should be avoided as much as possible. It is a global object in disguise. The worst is that you have everything in your container available at a place which should be as decoupled as possible from the rest of your application. Best practice is to use the DI-container only high in your hierarchy of objects. In Joomla that is the Application object.
Avoid using the DI-container in your MVC classes.
Service provider
A service provider is an automated recipe to instantiate an object and resolve its dependencies. It can also be used to resolve setter-injected dependencies and non-objects such as the config array. A service provider can be registered in the DI-container. When called, it returns the instantiated object with its dependencies resolved.
If you want to use a service provider recursively, the dependencies of the objects on a lower level are not automatically resolved, as with the buildObject() method of the DI-container The most commonly used objects, like the database, are set in the DI-container in an early stage of the application, either directly or by registering a service provider that provides the instantiated object. On a higher level you then get the dependent objects from the DI-container, or have other service providers resolve those “nested” dependencies.
In Joomla each extension has a service provider to resolve the dependencies of that extension. If you look, for instance, at the service provider of com_content (in /administrator/components/com_content/services/provider.php), you can see that it has an MVCFactory as dependency. Because that MVCFactory in turn also has dependencies, we don’t directly instantiate the MVCFactory, but refer to the MVCFactory service provider in /libraries/src/Extension/Service/Provider/MVCFactory.php. There, you can see that for instance, the dependence on the database is resolved from the DI-container.
Don’t confuse these three terms that look very similar:
- Service container: another word for Dependency Injection Container, or DI-container.
- Service provider: a recipe to get an instantiated object with all dependencies resolved.
- Service locator: an anti-pattern, injecting (or conjuring up) the whole DI-container in places where it doesn’t belong. To be avoided.
MVCFactory
Yes, but… yes, but… what if I don’t know yet if I need a dependent object? Or when I don’t know yet what kind of dependent object I would need exactly? How can I then create that dependent object without instantiating it on the spot by using the new keyword? The solution in those cases is to delegate the creation of the dependent object to a so-called “factory method", dedicated to creating the type of dependent objects you need.
In Joomla we have several of those “factories” to create dependent objects. The MVCFactory, for instance, has methods to create a Model, a View, a Controller, or a Table. This MVCFactory is injected into objects that need those dependent MVC-objects, but don’t know beforehand what they will need exactly. These factories are OK, contrary to the old \Joomla\CMS\Factory, which should be avoided as much as possible.
Key takeaways
- Don’t use the
newkeyword to instantiate dependent objects. - Instead, inject dependent objects, preferably using an interface.
- If you cannot yet instantiate a dependent object to inject it, inject a “factory” to create it later.
- You can use the principles of dependency injection without a DI-container.
- Avoid using the DI-container on the level of MVC-classes.
- Dependency injection promotes a decoupled modular structure.
Some articles published on the Joomla Community Magazine represent the personal opinion or experience of the Author on the specific topic and might not be aligned to the official position of the Joomla Project
By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/
Comments