Improving the readability of Symfony DI
Hi! I am developing Symfony applications and I want to share the problems I encountered when using Symfony DI, as well as some tips that I think will be useful when developing large applications. I briefly mentioned them in this article, and here I want to expand on the idea and talk in more detail.
There is a well-known IT wisdom that we read code much more often than we write it. This is especially relevant for large projects where more than one developer or even more than one team is working on it. The project changes quickly, it is impossible to keep track of all the changes, new classes, namespaces, solutions constantly appear, dependencies are added, bundles are created. In such an atmosphere, I put the readability and flexibility of the written code at the forefront of project development, and di is an important part of it.
Autowire
The first obstacle to this goal is autowire. Autowiring is useful for rapid prototyping, as it speeds up the writing of di, but in the long run it is rather harmful, I will give small examples:
services:
_defaults:
autowire: true
App\Service\Service: ~
App\Component\LockInterface:
class: App\Component\Lock
final class Service {
public function __construct(private readonly LockInterface $locker)
{
}
}
The implementation is successfully pulled into the service class. Then the service grows, service.yaml grows, possibly splits into smaller yaml, new implementations of LockInterface appear (otherwise why did we create the interface). And over time, looking at the class when reading the code, it becomes quite difficult to find, and which specific implementation is currently being used.
The situation is greatly simplified if we explicitly specify the implementation: by adding just one line di when writing, we make it easier for ourselves to read in the future.
App\Service\Service:
arguments:
$locker: '@App\Component\Lock'
Another danger is possible errors, since implementation changes can occur implicitly:
App\Component\LockInterface:
class: App\Component\MyCustomLock
So we change the implementation in all places of use in one fell swoop, convenient for a small application, where we keep all the places of use in mind. But in a large code base, such a change can affect places that we did not think about/forgot/did not know about. An explicit change to the di string in the class being used will at least highlight this. Yes, we will spend more time, but the changes will be obvious.
Do not use autowiring.
Classes instead of aliases
In di it is better to stick to one writing style, it simplifies reading and choosing between the two options:
App\Component\Consumer:
app.component.consumer:
class: App\Component\Consumer: ~
I prefer the second one, even though it is more verbose. The reason is that it allows you to reuse classes with different arguments, like this:
app.component.consumer.email_queue:
class: App\Component\Consumer:
arguments:
$queueName: 'mail'
app.component.consumer.sms_queue:
class: App\Component\Consumer:
arguments:
$queueName: 'sms'
This way you won't have to add new duplicate code or, worse, use inheritance. Using both naming conventions at the same time necessarily introduces unnecessary noise into di and makes it hard to read.
Use aliases.
Interface instead of implementation
Another strange way of specifying dependencies in di that I often see:
app.component.consumer:
class: App\Component\Consumer:
arguments:
$executor: '@App\Component\ExecutorInterface'
There is an explicit indication in the class arguments, but an indication of the interface. DI container is about the project assembly, the place where we specify specific implementations of interfaces described in the code and indicating the interface here simply makes the developer spend additional time reading di in search of the implementation, and such a line itself does not carry any useful information, the fact that $executor is an interface is clear even looking at the code.
Don't do that.
These rules are very subjective and possibly controversial, they force a more disciplined and verbose approach to writing di, but in my opinion they make it easier to read, which brings many benefits in the long run.