
Symfony setter injection
The Symfony documentation explains four ways to inject dependencies to your service. In short, you can use the constructor, inject directly to a public property or you can use a two flavours of injection via a method.
Short example:
# config/services.yaml
services:
App\Service\MyFancyService:
calls:
- setLogger: ['@logger']
class MyFancyService
{
private LoggerInterface|null $logger = null;
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
// ...
}
The documentation is doing a great job highlighting the advantages and disadvantages of each type of injection. But the Symfony documentation rarely give opinions, it is up for the developer to decide what is good or bad for them based on all the technical arguments made in the documentation. That is why I would like to share my opinion.
Setter injection is bad
Using setter injection or property injection is bad for a few reasons.
Your services becomes more complex
Because you define your dependencies as nullable, they are "optional". Ie, you say that your service may work without this dependency. So you always need to consider the case where the dependency exist and where the dependency do not exist. This can of course be achieved with [TODO]
$this->logger?->info('Example')
Sure, this example is very trivial and just increases the complexity with a tiny amount. Other examples could of course be much worse.
Doing this for the @logger
service is extra bad because the @logger
service
will always exist in a Symfony application.
In the simple case, just adding bunch of question marks in your service class might seams fine. But a question that I never heard a good answer to is: Why do we want to open the door to all this unnecessary complexity?
Road runner and reset
If you want to take your application to a platform like Road Runner, ReactPHP or FrankenPHP where you service multiple request before you kill the application, it gets very important to manage your memory correctly. You cannot have data leaking from one request to another.
An easy way to handle this is to make all your services readonly
. Alternatively
you can make make them implement the ResetInterface
. Now you know that no service
(the object that is being kept in memory between requests) does not share data.
With setter injection, you can not mark your services as readonly
and it is
more difficult to find what services that needs ResetInterface
.
Your services have state
The dependency injection container will use setter injection when the service is being instantiated. But there is nothing stopping other services to call your setter. This means you can get different behaviour from your service depending on when in the application life cycle you use the service.
This can be helped with immutable setter injection This means that the object will never have states which is good. If you really cannot use constructor injection, this is the only acceptable alternative. But it should be avoided because it is technically a small performance hit.
How to avoid using setter injection?
It is simple. Just use Constructor Injection. It might be a bit more code to write or you might need to override the parent constructor but it is always worth it.
In some rare cases you will get a circular reference exception, this is the time you use immutable setter injection. Read more in the documentation.