Please help review and edit this page.
This page describes the use of the Dependency Injection (DI) facility provided by the MediaWikiServices class.
This section provides a quick start to those who are already familiar with DI.
Using services:
MediaWikiServices::getInstance()
. This should only be done in bootstrap code and static entry points. Accessing the service container say in application logic (as per the Service Locator pattern) or passing the service container as a parameter or keeping a reference to the service container in a member variable (as per the Context Object pattern) is strongly discouraged.getMainConfig()
or getDBLoadBalancer()
.getService( $name )
method. Extensions may provide static convenience functions of the form MyExtensionServices::getFooService()
implemented to return MediaWikiServices::getInstance()->getService( 'MyExtensionFoo' );
setService( $name, $service )
method of MediaWikiIntegrationTestCase
.Defining services:
includes/ServiceWiring.php
that returns a new instance of the desired service class.$this->getService( 'SomeServiceName' )
.defineService()
, providing an instantiator callback for the custom service which returns a new instance of the desired service class.src/ServiceWiring.php
, in the extension directory. The wiring file should consist of a single return statement, returning an associative array that maps service names to instantiator functions. To avoid name conflicts, the service names should be prefixed with the extension's name.$wgServiceWiringFiles
in the extension's bootstrap code.getService()
with the internal service name.Best practices for using Dependency Injection in MediaWiki:
new
operator).RequestContext
should be avoided. The service container should never be passed as a parameter.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.
TBD: DI vs Service Locator
TBD: DI vs Container injection (context objects)
Static entry points[edit]A static entry point is code in a static context (directly in global 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:
index.php
, load.php
, thumb.php
, etc.The application-scope service container is the top-level registry for the services that the application's logic needs to operate. Extensions can define their own service containers (which may depend on MediaWiki's service container).
Access to the service container should be restricted to static entry points. Direct access to the global service container instance from application logic following the service locator pattern is discouraged. See Assembly comparison for a discussion of service locator vs. DI container logic.
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:
Code inside hook handler functions or instatiator functions is not bootstrap code, since it is not executed during the initialization process.
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.
Registries are factories for services. Factory methods in a registry typically do not take any parameters. Registries can be used to
The top-level registry (the service container) provides access to all services known to the application, for use in Static entry points.
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 #Registry bootstrap 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).
Instantiator function[edit]A instantiator function (or factory callback) is a callable that returns a new instance of some class.
A service wiring file is a PHP file returning an array of instantiator functions by service name. For extensions, the service wiring files are listed in ServiceWiringFiles , and it is advisable to include the extension name in the service name, to avoid collisions with other extensions. As of October 2020 (see service wiring code search), the most common patterns for this are ExtensionNameServiceName
(no delimiter) and ExtensionName.ServiceName
(dot-separated), but other delimiters are also sometimes used, and some extensions don’t include the name at all.
The order of services in the wiring file is not significant. The MediaWiki core file is sorted alphabetically, to avoid Git merge conflicts when multiple changes add a new service (without sorting, each change would try to add the service to the end of the list). The same approach is recommended for extensions as well. (Specifically, MediaWiki uses natcasesort(), i.e. NamespaceInfo
comes before NameTableStoreFactory
.)
Extensions define their services in a service wiring file (sometimes more than one), typically includes/ServiceWiring.php
, which looks like this:
<?php use MediaWiki\MediaWikiServices; // ... /** @phpcs-require-sorted-array */ return [ 'Extension.Service1' => function ( MediaWikiServices $services ) : Service1 { return new Service1(); }, 'Extension.Service2' => function ( MediaWikiServices $services ) : Service2 { return new Service2( $services->getDBLoadBalancer() ); }, ];
The wiring file is listed in the extension.json ServiceWiringFiles .
It is also often useful to include a utility class that can be used to access the services type-safely, similar to MediaWikiServices
for MediaWiki core.
class ExtensionServices { public static function getService1( ?ContainerInterface $services = null ) : Service1 { return ( $services ?? MediaWikiServices::getInstance() ) ->get( 'Extension.Service1' ); } public static function getService2( ?ContainerInterface $services = null ) : Service2 { return ( $services ?? MediaWikiServices::getInstance() ) ->get( 'Extension.Service2' ); } }
This also makes it easier to work with dependencies between the extension’s services; if Service2 needs Service1, the wiring file from earlier could include something like this:
'Extension.Service2' => function ( MediaWikiServices $services ) : Service2 { return new Service2( $services->getDBLoadBalancer(), ExtensionServices::getService1( $services ) ); },
The ExtensionServices
class can be tested with a custom subclass of ExtensionServicesTestBase. A more-or-less complete example of this can be found in Extension:Cognate : src/ServiceWiring.php, src/CognateServices.php, tests/phpunit/CognateServicesTest.php
TBD
TBD
TBD
TBD
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4