February 23, 2017 Nicolas Grekas

WARNING: This feature was reverted before the release of Symfony 3.3, so no stable Symfony version ever supported it.

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 HttpKernel

in HttpKernel SessionListener::getSession() in HttpKernel also

in HttpKernel also AbstractBaseFactory::getGenerator() in ProxyManager

This 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' ); } };

Pros of using getter injection¶ Classes designed with getter injection in mind have several advantages over the other IoC strategies: Using inheritance makes them free from any coupling with any framework/DIC (as opposed to injecting the container); It makes injected dependencies immutable (as opposed to setter/property injection); By requiring to always use the getter to fetch some dependencies internally, it allows for lazy instantiation of said dependencies (as opposed to constructor/setter/property injection); It doesn't pollute the constructor, which is an advantage for classes designed for extensibility (same as setter/property injection - and as opposed to constructor injection); It plays well with traits-based composition, each trait providing a new set of open getters (same pro for setter/property injection - and as opposed to constructor injection); It makes dependencies explicit (as opposed to injecting the container); It allows to declare mandatory dependencies by using abstract getters; It allows to have optional dependencies by providing default getters that return null or a default implementation (e.g. a NullLogger ); It allows to have and report missing dependencies that are conditionally required when using some subset of the features provided by the base class (typically a controller or a command) - by throwing a useful exception message when that happens; The proxies required to leverage the injection points are easy to write (see example) or generate; The proxies do not need to change when the injected dependencies change themselves (as opposed to using decorators when dealing with laziness); It adds no performance overhead when using the injected dependencies (as opposed to using decorators when dealing with laziness); When using anonymous or generated proxy classes, it doesn't create any new type to hint for, thus is free from inheritance hell.

Cons of using getter injection¶ It requires to write an inheritance proxy, thus adds more boilerplate than the other injection strategies when wiring or testing these classes; By design, it doesn't work with final classes nor with private methods; Since PHP (unlike other languages) doesn't provide any way to create proxies at runtime, it requires either hand written code, a dumped container, or using eval() for runtime-based DICs; When laziness is a target, it gives the laziness responsibility to the caller-side of dependencies (as opposed to callee-side when using decoration) - thus it should be used only when laziness is part of the core business of the class open to getter injection; It adds indirection, thus can make debugging harder (Why is this class throwing?, "Oh, initialization of this lazy dependency fails at that time"); When the inheritance proxy provides laziness, it can create circular dependencies in the PHP object graph, making garbage collection required at some point; If the side-effect-free requirement is not met, it will lead to bugs - when not using abstract getters, this requirement is technically hard to enforce (requires static code analyzes).

Discussion¶ 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.

Envisioned use case in Symfony core¶ 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.