This mini-article describes the methods of dependency injection, what each method implies in terms of both performance and simplicity.
- Constructor injection
This method is half manual and quite well known. Declare your classes' dependencies as constructor and pass the dependencies when you construct your instances. This approach completely skips the automation around detection of injections - but performs the best of all methods.
Can be combined with Singleton patterns, e.g. you can construct a Singleton using this method and this instance will be held in memory and returned when a fresh instance is requested.
Note that Extbase will create and pass constructor arguments when those constructor arguments are themselves Singletons (prototypes will cause an error), when you create the class without constructor arguments - however, this won't work if your "root" class is itself a Singleton which is why this article only mentions the manual passing of constructor arguments (as the only fully compatible, automation and reflection risk-free method, and coincidentally the best performing one too).
Addendum from @dfeyer: Constructor dependencies also have the benefit that you can actually access your dependencies in the constructor itself to initialize - and you will clearly see if you have architecture problems, e.g. when your constructor method grows too big - which are both great points!
- Injection methods
This method is the better of the two fully automatic dependency injections. It works by using native
PHP functions to read a list of methods on a class, parses the list of methods to detect ones with
inject prefix, reads the required type (without looking at PHPDoc!) and then attempts to build
the instance to inject (which then recurses down through the dependencies until all are injected).
The result of the analysis of method availability gets cached.
The benefit of this method is that the injection method can be immediately called after thawing the class reflection from cache - because all we need to know is that the method exists, is public and needs a particular instance.
- Injection annotations
This method is the worst performing of the three. It is the worst because of the following circumstances:
- The parsing requires PHPDoc parsing. Reflection is required to extract the PHPDoc and it must be parsed into a class name.
- The class name you provide must not be a short/imported class name because the reflection does not know about the namespace of your class.
- Every time a property needs to be set, Reflection must be used to force protected properties to become publicly settable (in other words: intentionally violates visibility).
Although the reflected information does get cached, the caching process is less efficient for all classes until cached and even when it is cached, violates visibility and uses Reflection which puts further pressure on the injection.
For your simple use cases and when this is appropriate, I recommend using constructor dependencies and handling those manually when you construct your instances. This method is the most explicit, least magic and more predictable of all of them. Your class can be, but is not required to be, a Singleton.
For use cases where your class is a Singleton and you need access to an instance but the context does not allow you to manually create instances, use injection methods - because they are the most efficient of the two automated dependency injection strategies and avoid the violation of visibility.
But please, don't use
@inject annotations. They are against PHP pragma, they perform worse than any of
the alternative methods, they have additional peculiarities like the demand for an FQN and they depend
on additional parsing of PHPDoc.
Thanks for reading!
You're welcome @mkrappitz - I don't have any numbers at hand, but I did run a test once a long, long time ago. If I remember it correctly it was in the low 10s of milliseconds for ten thousand classes being instanced, but I only tested Reflection-based forced public setting vs. injection method being called directly and only did so on a single fixture class; so no resolving of the method, class name, etc. So take it with a grain of salt, not least since it's several years old in my memory.
Therefore those numbers should be slightly higher for a real environment where resolving also must happen, and especially where doc-comment parsing must also happen (since this also involves Reflection to extract the comment). I would add that any optimisers which remove phpdoc comments also makes such class loading fail for the annotation case but not for the others (although, if you use such an optimiser, you face many equally severe problems with Extbase in many other places; but would likely see the class loading issues first of all).
Also: although you don't avoid it completely by switching injection methods away from annotations, it does put a bit of extra I/O pressure on the DB if you store reflection caches there - which default TYPO3 setups will do. The larger the reflection, the more data needs to transfer, compound that for remote SQL connections and so on.
Overall, the argument that "forcing protected properties public writable violates the core PHP language" should be the more important one. The performance gains are likely minimal, but they do increase in weight along with the number of classes (some sites easily reach several thousand unique classes on some requests) and things like expiration time on reflection cache which would cause re-reflection at unpredictable times.