Dependency Injection

This page describes the use of the en:Dependency Injection (DI) facility provided by the MediaWikiServices class.

Best Practices
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.

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".

This RFC proposes adopting the following as best practices:
 * 1) When a service (or application logic) needs access to another a service, it asks for it in the constructor. This is the actual injection of dependencies.
 * 2) Objects that need access to services can only be constructed via factories (not directly using the   operator).
 * 3) Services are constructed by other services (factories and registries being special types of service). At the top of this chain of registries/factories there is the application-scope service locator which acts as the top-level service registry.
 * 4) Access to global default instances ("singletons") should be restricted to static entry points (e.g. hook handlers and callbacks in bootstrap code). Ideally, there is only one such global default instance, namely the service locator.
 * 5) Injecting/passing around factories, registries, and especially "kitchen sinks" like RequestContext should be avoided. The service locator should never be passed as a parameter.
 * 6) Mutable global state should especially be avoided.
 * 7) Services should be represented by narrow interfaces (e.g. UserLookup).
 * 8) Registries use instatiator functions (aka factory functions) to instantiate services.
 * 9)  should avoid instantiating services, but define instatiator function instead.

These principles effectively promote dependency injection, without binding to a particular DI framework. In fact, it allows DI to be implemented entirely "on foot", without any configurable DI container - instantiation and decisions regarding the life cycle of service objects (lazy initialization, etc) can be left to plain old PHP code.


 * DI vs Service Locator
 * DI vs Container injection (context objects)

Service Locator vs. RequestContext
In the past, RequestContext was sometimes proposed as a vehicle to make services available to application logic. This is an example of the kitchen sink anti-pattern: An object with many dependencies is passed to many classes, causing all code to (indirectly) depend on everything. The distinction may seem cosmetic:

$context->getFoo;

versus

MediaWikiServices::getInstance->getFoo

And the former actually looks better on a first glance - after all, it does not use any global state, the RequestContext is properly injected (though its current implementation heavily relies on global state internally). The important distinction is when and where this would be called: MediaWikiServices::getInstance is supposed to be called only by static code, never in application logic. Code that uses MediaWikiServices::getInstance can, by definition, not be tested by unit tests, and should thus be minimized.

By contrast, RequestContext is injected to provide information about the current request, that is, the requested page, the logged in user, requested output language, etc. Having a (value) object to represent the request in such a way is quite useful. But that value object should not depend on any services, to avoid circular (or rather, knotted) dependencies of everything on everything.

Static entry points
A static entry point is code in a static context (directly in clobal scope, or in a global function, or in a static method) that gets called by a framework (e.g. the PHP runtime, or MediaWiki's Hooks mechanism). In MediaWiki, typical static entry points are:


 * 1) Global scope code in web entry points like index.php, load.php, thumb.php, etc.
 * 2) Global scope code in maintenance scripts.
 * 3) Extension bootstrap code (see )
 * 4) Hook handler functions (see )
 * 5) Instatiator functions

Service locator
The application-scope service locator is the top-level registry for the services that the application's logic needs to operate (see e.g. ). Extensions can define their own service locators (which may depend on MediaWiki's service locator), see e.g..

Access to the service locator should be restricted to static entry points. This way, it acts as a DI container. A simple implementation of such a DI container is described in http://fabien.potencier.org/do-you-need-a-dependency-injection-container.html

See also en:Dependency_injection for a discussion of service locator vs. DI container logic.

Bootstrap code
Bootstrap code refers to code that is executed at the beginning of every request. Bootstrap code creates the initial scaffolding for initializing the application by loading configuration and instantiating the most basic services. In MediaWiki, bootstrap code is typically:
 * 1) global scope code in a web entry point (or maintenance script).
 * 2) extension entry points (see )

Code inside hook handler functions or instatiator functions is not bootstrap code, since it is not executed during the initialization process.

Factory
A factory is a service that instantiates objects. These objects can be services, or data objects. Factory methods that are guaranteed to create a new instance should have names starting with "new". Other factory methods should have names starting with "get", and may or may not return singletons.

Factories are used to inject the ability to instantiate certain kinds of objects. They can be understood as partial applications of constructors. Factory methods typically, but not necessarily, take parameters.

A "factory" in the more narrow sense would typically have only one factory method, and create only one kind of object.

Registry
Registries are factories for services. Factory methods in a registry typically do not take any parameters. Registries can be used to
 * 1) provide access to instances of a variety of services, e.g. various storage level services.
 * 2) provide access to specialized instances of a single service interface implemented for different "targets", e.g. different MediaHandler instances for each type of media.
 * 3) provide lazy instantiation of services, to avoid overhead at startup time.

The top-level registry (the service locator) provides access to all services known to the application, for use in.

A registry may be implemented by hardcoding the logic for instantiating the services (typical especially for the top-level registry), or by bootstrap code defining instatiator functions (aka factory functions). See the example. Note that registering class names should be avoided, since that prevents injection of services via constructor arguments (because the constructor's signature is prescribed by the registry).

Instatiator function
A instatiator function or factory function is a callable that returns a new instance of some class.

UserLookup interface
Note that this is an interface for looking up users. It does not contain methods for updating user records, nor for creating new users.

MediaWikiServices
To avoid boilerplate code for lazy instantiation, this can be generalized a bit:

Hook handler injection
The handler function is then hooked up as usual:

The static logic can also be moved into an anonymous function, if preferred:

This is somewhat cleaner, since spurious dependencies in the handler class are avoided. But keeping the static code in the handler class provides better knowledge locality, and avoids clutter in the bootstrap file.