Contributed by
Nicolas Grekas
in #20973.
As part of our experimental features program, in Symfony 3.3 we've added a new feature called getter injection. This adds up to the usual mechanisms used for dependency injection and doesn't replace any of them. Instead, it provides an additional way that fits some specific use cases.
Getter injection allows the dependency injection container to leverage classes that provide inheritance-based extension points that matches the following requirements: public or protected methods with zero arguments and free of side-effects.
Some examples found while grepping Symfony and its vendors:
Kernel::getRootDir/CacheDir/LogDir()
in HttpKernelSessionListener::getSession()
in HttpKernel alsoAbstractBaseFactory::getGenerator()
in ProxyManagerThis is only a small subset of all the classes that apply this flavor of the open/closed principle in Symfony core and elsewhere. As shown in the examples, this applies both to objects injection (services) and to values injection (parameters).
Getter injection is a way to turn these classes into DI candidates via simple DI configuration. In Yaml, taking the SessionListener::getSession() example, this could look like:
1 2 3 4 | services:
SessionListener:
getters:
getSession: '@session'
|
In practice, this tells the Symfony Dependency Injection Container to create an anonymous inheritance-proxy class like this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $sessionListener = new class ($container) extends SessionListener {
private $container;
function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getSession()
{
return $this->container->get('session');
}
};
|
Classes designed with getter injection in mind have several advantages over the other IoC strategies:
null
;eval()
for runtime-based DICs;The introduction of "getter injection" was controversial and it generated both positive and negative feedbacks. I tried to sum up all rational arguments, pros and cons, in the lists above.
Having addressed them, I hope for the "surprise effect" to disappear over time and have getter injection be evaluated for its technical merits only. My personal point of view about it is that it fills a few holes with its unique pros/cons balance, and as such deserves being part of the developers' tool chain, with support from Symfony's container.
This feature is targeted at being used for framework-specific needs first. The target for now is to provide decoupled composable base controller trait(s). See pull request 18193 for what could be the next step on the topic.
By using for example anonymous classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $myDep = new DepClass();
$myConsumer = new class ($myDep) extends ConsumerClass {
private $dep;
public function __construct(DepClassInterface $dep)
{
$this->dep = $dep;
}
protected function getDep(): DepClassInterface
{
return $this->dep;
}
};
|
Yes, that's listed in the "cons". We use DICs to do the boring wiring - same here. Note also that with the help of the language, this could be reduced to the following (there is a discussion about that on php-internals, please support it):
1 2 3 4 5 6 7 8 9 | $myDep = new DepClass();
$myConsumer = new class () use ($myDep) extends ConsumerClass {
private $dep = $myDep;
protected function getDep(): DepClassInterface
{
return $this->dep;
}
};
|
By using e.g. anonymous classes (see above) - or a mock framework, e.g. PHPUnit's:
1 2 | $consumer = $this->createMock('ConsumerClass')->setMethods(['getDep'])->getMock();
$consumer->expects($this->any())->method('getDep')->will($this->returnValue($myDep));
|
People will always be creative to do things in bad ways. There is nothing specific about getter injection on that topic.
Container injection (or the "service locator" design pattern) is bad practice
when it hides the dependencies your classes are using: doing
$container->get('foo')->doFoo()
in your code relies on several loosely-
enforced assumptions, namely:
$container
has a "get" method, usually provided by your framework's
DI component (that's coupling to it);If any of those assumptions are not valid anymore, such code is fragile: any mistake can be undetected until the code is actually run (a refactoring's nightmare.)
Getter injection (as constructor/setter injection) makes your classes free from any such issues: the extension points are plain explicit and return-type hinted (you need PHP 7). By using a compatible DIC, the code is automatically generated, based on these explicit declarations in your code.
No problem! Remember that this feature is optional and your Symfony applications don't have to use it.
What a Symfony developer should know about the framework: News, Jobs, Tweets, Events, Videos,...