Requests for comment/Dependency injection/2014

This RFC proposes a lightweight mechanism for dependency injection. An implementation with tests and examples is provided. A facility like this could be combined with improved autoloading, and we could add it to core as a first step in an iterative development process. Other options are also discussed.

Problem statement
Dependency injection (DI) is a design pattern that can facilitate unit testing, loose coupling and architecture description. Although it's more useful in some languages than in others, it is a well-established pattern, and there is a solid ecosystem of DI libraries for PHP.

Mediawiki doesn't have a dedicated DI mechanism, though adding one has been discussed, and some code in core does DI by hand. Also, Wikibase has classes for DI.

Adding simple DI support to core would be a first step towards consistent, concise use of this pattern. Since we'll probably need at least a few iterations and use cases to get it right, this first step could be a kind of "internal API beta feature".

Previous discussions
Using DI in Mediawiki has been considered before. Here are some earlier conversations about it:
 * Discussion of changes to Architecture guidelines at Wikimania 2013
 * Section on DI for external resources in Talk:Architecture guidelines
 * Discussion of TitleValue at the Arcitecture Summit 2014
 * The TitleValue RFC and the ServiceRegistry section on that RFC's Talk page

Current functionality
The proposed implementation is minimalistic, though usage is not too different from some existing DI libraries. Here's how it works.

Let's say you have the following interfaces and classes:

Assuming you want only one instance of these classes per request, you can set up and use DI like this:

(Note: the global variable  and the class   are actually called   and   in the version of the implementation in Gerrit.)

First we register the types (in this case, interfaces) and their realization classes (the concrete classes). When a type is requested, an instance of the corresponding realization class will be provided.

The default scope (in fact, the only scope implemented) is. In this scope,  creates and caches a single instance of the realization class the first time it (or rather, its corresponding type) is requested. So the first time we request, an instance of   is created and cached. The next time  is requested, the same object will be returned.

To instantiate,   looks at the type hint in the constructor and notices that the type   is also registered and that its realization class is. So it creates or fetches from its cache the singleton instance of  and injects it.

Note that the type registered doesn't have to be an interface. It can also be a superclass of, or even the same class as, the realization class.

Factory example
The proposed implementation is much less featureful than most DI libraries. However, by creating factories, you can still use it to set up loose coupling for classes that you need many instances of. For example, suppose that we have the following interface-class pair for something we need to be able to create on demand:

In this case we can create a factory and inject that into the class that will be creating the s:

In this case, we isolate the call to  for the concrete class  in a very simple factory, and inject the factory into the. This way,  doesn't know about the implementation details of , and limits its knowledge to just the contracts it needs, that is,   and the very simple.

Possible combination with better autoloading
The verbosity of registrations is sadly reminiscent of Mediawiki's also-verbose autoloading registrations. However, better autoloading is certainly possible. If both DI and improved autoloading were added to core, it might make sense to combine them or link them up somehow. Convention-over-configuration could be used to reduce the verbosity of setup code for the most typical configurations.

Tests and example
Please see the Campaigns extension for examples and unit tests.

Other options
DI is already used to some extent in Mediawiki and sister projects. We could adopt one of those approaches, instead of using the implementation presented here. We could also use an external library. Following is a summary of some of these options.