In order to move MediaWiki towards the use of Dependency Injection (DI) as proposed in T384, it will be necessary to provide a //composition root// (aka application scope aka top level factory) that provides access to the top level services. This role should be taken by suitable //DI container//.
During the discussion of T384 as well as the relevant [[https://www.mediawiki.org/wiki/Wikimedia_Developer_Summit_2016/Software_engineering|discussions during the 2016 developer summit]], a rough consensus emerged that we should move towards DI //if it can be done nicely and without too much disruption//. This RFC aims to clarify the implementation details of the heart of the DI mechanism: the central service locator.
# we want to use service ”singletons” with narrow interfaces, and ”dumb” value objects for our domain model. For the storage layer, this means a [[https://en.wikipedia.org/wiki/Data_access_object|DAO]] approach instead of ”smart” records.
# we do not want to use [[http://symfony.com/blog/new-in-symfony-2-8-service-auto-wiring|auto-wiring]]. It's convenient for small systems, but confusing for large, complex systems like MediaWiki. We want to avoid magic, and do things explicitly.
# we want our DI container to be configurable, so that extensions can define their own service. Extensions should also be able to replace or wrap existing services.
# we do not want to use a 3rd party DI framework such as Pimple or PHP-DI. See below for the rationale.
# we want services to be injected as constructor argument to avoid strong coupling. We do not want to follow the service locator //pattern//. (We just call our top level DI container the service locator, because the name is more descriptive).
=== Proposal ===
This RFC proposes the implementation of a generic ServiceContainer class that instantiates services on demand based on a registry of callback functions. A top level instance of this ServiceContainer, namely MediaWikiServices, is used to access well known top level services. The top level services are initialized from a "wiring" file that defines a callback for creating each well known service.
An implementation of such a ServiceContainer mechanism is up for review on gerrit: [[https://gerrit.wikimedia.org/r/#/c/264403/|I3c25c0ac17300d3dd13e1cf5100558a605eee15f]]. Also see the follow-up for added functionality and examples of refactoring existing code to use DI.
=== Why not use a 3rd Party DI Framework ===
One of the recurring discussion topics in this context is whether we should use a 3rd party DI framework. A number of decent DI frameworks available for PHP, like [[http://symfony.com/doc/current/components/dependency_injection/introduction.html|Symfony DI]], [[http://php-di.org/|PHP-DI]], and the more light weight [[http://pimple.sensiolabs.org/|Pimple]]. So why not use them?
There are several reasons for using our own, instead of relying on a 3rd party framework:
* Overhead. Using a 3rd party library means overhead for maintaining the dependency. The overhead isn't huge, but it's clearly non-zero. It should only be done if the benefits are clear.
* Frameowrk isolation. We should avoid multiple components (and extensions) depending on a 3rd party framework. Instead, we should at least provide a thin layer to isulate the framework we use from MediaWiki code, so we can easily change the framework later. So even if we end up using a 3rd party lib, we still need to write and maintain the wrapper code anyway.
* The lib does too much. The basic functionality of the DI container is rather simple. Some of the available frameworks do way more than we need or want (auto-wiring, for instance). We would drag in weight, and would be open to new code using aspects of DI we don't want to use (auch as auto-wiring, or the framework's built-in configuration mechanism instead of MediaWiki's).
* The lib does too little. If we use a 3rd party framework but end up implementing half of the features we need on top of it, it may not be woth the extra dependency. For example, at least during the migration period, we need a mechanism to replace existing services, and run cleanup when an existign service is replaces. Also, we need integration with MediaWiki's configuration mechanism. Pimple allows us to do both somehow, but doesn't really help with doing it.
* Performance. MediaWiki has pretty unique requirements for scalability and performance, in particular for startup time. For MW, the tradeoff point between speed and memory consumption leans a lot further towards using more memory than is the case for most other projects. Having control over these aspects of the implementation of the DI container is helpful.
So, in essence, if we only save a few hundred lines of trivial code by using a 3rd party library, we probably shouldn't.