Fix MediaWiki.WhiteSpace.EmptyLinesBetweenUse.Found
[mediawiki.git] / docs / Injection.md
blob850a96ce6b7a00c11a62b34c16da478aee9ecfe0
1 Dependency Injection {#dependencyinjection}
2 =======
4 This is an overview of how MediaWiki uses of dependency injection.
5 The design originates from [RFC T384](https://phabricator.wikimedia.org/T384).
7 The term "dependency injection" (DI) refers to a pattern in object oriented
8 programming. DI tries to improve modularity by reducing strong coupling
9 between classes. In practical terms, this means that anything an object needs
10 to operate should be injected from the outside. The object itself should only
11 know narrow interfaces, no concrete implementation of the logic it relies on.
13 The requirement to inject everything typically results in an architecture based
14 on two main kinds of objects: simple "value" objects with no business logic
15 (and often immutable), and essentially stateless "service" objects that use
16 other service objects to operate on value objects.
18 As of 2022 (MediaWiki 1.39), MediaWiki has adopted dependency injection in much
19 of its code. However, some operations still require the use of singletons or
20 otherwise involve global state.
22 ## Overview
24 The heart of the DI in MediaWiki is the central service locator,
25 MediaWikiServices, which acts as the top-level factory (or registry) for
26 services. MediaWikiServices represents the tree (or network) of service objects
27 that define MediaWiki's application logic. It acts as an entry point to all
28 dependency injection for MediaWiki core.
30 When `MediaWikiServices::getInstance()` is first called, it will create an
31 instance of MediaWikiServices and populate it with the services defined by
32 MediaWiki core in `includes/ServiceWiring.php`, as well as any additional
33 bootstrapping files specified in  `$wgServiceWiringFiles`. The service
34 wiring files define the (default) service implementations to use, and
35 specifies how they depend on each other ("wiring").
37 Extensions can add their own wiring files to `$wgServiceWiringFiles`, in order
38 to define their own service. Extensions may also use the `MediaWikiServices`
39 hook to replace ("redefine") a core service, by calling methods on the
40 MediaWikiServices instance.
42 It should be noted that the term "service locator" is often used to refer to a
43 top-level factory that is accessed directly, throughout the code, to avoid
44 explicit dependency injection. In contrast, the term "DI container" is often
45 used to describe a top-level factory that is only accessed only inside service
46 wiring code when instantiating service classes. We use the term "service locator"
47 because it is more descriptive than "DI container", even though application
48 logic is strongly discouraged from accessing MediaWikiServices directly.
50 `MediaWikiServices::getInstance()` should ideally be accessed only in "static
51 entry points" such as hook handler functions. See "Migration" below.
53 ## Principles {#di-principles}
55 Service classes generally only vary on site configuration and are
56 deterministic and agnostic of global state. It is the responsibility of
57 callers to a service object to obtain and derive information from a
58 web request (such as title, user, language, WebRequest, RequestContext),
59 and pass this to specific methods of a service class as-needed. See
60 [T218555](https://phabricator.wikimedia.org/T218555) for related discussion.
62 Consider using the factory pattern if your service would otherwise be
63 unergonomic or slow, e.g. due to passing many parameters and/or recomputing
64 the same derived information. This keeps the global state out of the
65 service class, by having the service be a factory from which the caller
66 can obtain a (re-usable) object for its specific context.
68 This design ensures service classes are safe to use in both user-facing
69 contexts on the web (e.g. index.php page views and special pages), as
70 well as in an API, job, or maintenance script. It also ensures that
71 within a web-facing context the same service can be safely used
72 multiple times to perform different operations, without incorrectly
73 implying certain commonalities between these calls. Lastly, this
74 restriction allows services to be instantiated across wikis in the
75 future.
77 If a feature is not ready to meet these requirements, keep it outside
78 the service container. This avoids false confidence in the safety of an
79 injected service, and its ripple effect on other services.
81 ### Principle exemption
83 There is a limited exemption to the above principles for "inconsequential
84 state". That is, global state may be used directly if and only if used
85 for diagnostics or to optimise performance, so long as they do not
86 change the observed functional outcome of a called method.
88 Examples of safe and inconsequential state:
91 * Use `$_SERVER['REQUEST_TIME_FLOAT']` or `ConvertibleTimestamp::now`
92   to help compute a time measure that is sent to a metric service.
94 * Use `wfHostname()`, `PHP_SAPI`, or `WikiMap::getCurrentWikiId()`
95   to describe where, how, or for which wiki the overall process was
96   created and send it as message context to a logging service.
98 * Use `WebRequest::getRequestId()` to automatically inject a
99   header into HTTP requests to other services. These are for tracking
100   purposes only.
102 * Use `function_exists('apcu_fetch')` to automatically enable use
103   of caching.
105 Examples of unsafe state in a service class:
107 * Do not use `WikiMap::getCurrentWikiId()` as the default value
108   to obtain a database connection.
110 * Do not use `$_SERVER['SERVER_NAME']` to inject a header into
111   HTTP requests to other services to control which wiki to operate on.
113 ## Create a new service
115 To create a new service in MediaWiki core, write a function that will return
116 the appropriate class instantiation for that service in ServiceWiring.php. This
117 makes the service available through the generic `getService()` method on the
118 `MediaWikiServices` class. We then also add a wrapper method to
119 MediaWikiServices.php with a discoverable method named and strictly typed
120 return value to reduce mistakes and improve static analysis.
122 ## Service Reset
124 Services get their configuration injected, and changes to global
125 configuration variables will not have any effect on services that were already
126 instantiated. This would typically be the case for low level services like
127 the ConfigFactory or the ObjectCacheManager, which are used during extension
128 registration. To address this issue, Setup.php resets the global service
129 locator instance by calling `MediaWikiServices::resetGlobalInstance()` once
130 configuration and extension registration is complete.
132 Note that "unmanaged" legacy services services that manage their own singleton
133 must not keep references to services managed by MediaWikiServices, to allow a
134 clean reset. After the global MediaWikiServices instance got reset, any such
135 references would be stale, and using a stale service will result in an error.
137 Services should either have all dependencies injected and be themselves managed
138 by MediaWikiServices, or they should use the Service Locator pattern, accessing
139 service instances via the global MediaWikiServices instance state when needed.
140 This ensures that no stale service references remain after a reset.
142 ## Configuration
144 When the default MediaWikiServices instance is created, a Config object is
145 provided to the constructor. This Config object represents the "bootstrap"
146 configuration which will become available as the 'BootstrapConfig' service.
147 As of MW 1.27, the bootstrap config is a GlobalVarConfig object providing
148 access to the $wgXxx configuration variables.
150 The bootstrap config is then used to construct a 'ConfigFactory' service,
151 which in turn is used to construct the 'MainConfig' service. Application
152 logic should use the 'MainConfig' service (or a more specific configuration
153 object). 'BootstrapConfig' should only be used for bootstrapping basic
154 services that are needed to load the 'MainConfig'.
156 Note: Several well known services in MediaWiki core act as factories
157 themselves, e.g. ApiModuleManager, ObjectCache, SpecialPageFactory, etc.
158 The registries these factories are based on are currently managed as part of
159 the configuration. This may however change in the future.
161 ## Migration
163 This section provides some recipes for improving code modularity by reducing
164 strong coupling. The dependency injection mechanism described above is an
165 essential tool in this effort.
167 ### Migrate access to global service instances and config variables
168 Assume `Foo` is a class that uses the `$wgScriptPath` global and calls
169 `wfGetDB()` to get a database connection, in non-static methods.
170 * Add `$scriptPath` as a constructor parameter and use `$this->scriptPath`
171   instead of `$wgScriptPath`.
172 * Add LoadBalancer `$dbLoadBalancer` as a constructor parameter. Use
173   `$this->dbLoadBalancer->getConnection()` instead of `wfGetDB()`.
174 * Any code that calls `Foo`'s constructor would now need to provide the
175   `$scriptPath` and `$dbLoadBalancer`. To avoid this, avoid direct instantiation
176   of services all together - see below.
178 ### Migrate services with multiple configuration variables
179 When a service needs multiple configuration globals injected, a ServiceOptions
180 object is commonly used with the service class defining a public constant
181 (usually `CONSTRUCTOR_OPTIONS`) with an array of settings that the class needs
182 access to.
184 ```php
185 <?php
187 class DemoService {
189         public const CONSTRUCTOR_OPTIONS = [
190                 'Foo',
191                 'Bar'
192         ];
194         private $options;
196         public function __construct( ServiceOptions $options ) {
197                 // ServiceOptions::assertRequiredOptions ensures that all of the
198                 // settings listed in CONSTRUCTOR_OPTIONS are available
199                 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
200                 $this->options = $options;
201                 // $wgFoo is now available with $this->options->get( 'Foo' )
202                 // $wgBar is now available with $this->options->get( 'Bar' )
203         }
208 ServiceOptions objects are constructed within ServiceWiring.php and can also
209 be created in tests.
210 ```php
211 'DemoService' => function ( MediaWikiServices $services ) : DemoService {
212         return new DemoService(
213                 new ServiceOptions(
214                         DemoService::CONSTRUCTOR_OPTIONS,
215                         $services->getMainConfig()
216                 )
217         );
221 ### Migrate class-level singleton getters
222 Assume class `Foo` has mostly non-static methods, and provides a static
223 `getInstance()` method that returns a singleton (or default instance).
224 * Add an instantiator function for `Foo` into ServiceWiring.php. The
225   instantiator would do exactly what `Foo::getInstance()` did. However, it
226   should replace any access to global state with calls to `$services->getXxx()`
227   to get a service, or `$services->getMainConfig()->get()` to get a
228   configuration setting.
229 * Add a `getFoo()` method to MediaWikiServices. Don't forget to add the
230   appropriate test cases in MediaWikiServicesTest.
231 * Turn `Foo::getInstance()` into a deprecated alias for
232   `MediaWikiServices::getInstance()->getFoo()`. Change all calls to
233   `Foo::getInstance()` to use injection (see above).
235 ### Migrate direct service instantiation
236 Assume class `Bar` calls `new Foo()`.
237 * Add an instantiator function for `Foo` into ServiceWiring.php and add a
238   `getFoo()` method to MediaWikiServices. Don't forget to add the appropriate
239   test cases in MediaWikiServicesTest.
240 * In the instantiator, replace any access to global state with calls
241   to `$services->getXxx()` to get a service, or
242   `$services->getMainConfig()->get()` to get a configuration setting.
243 * The code in `Bar` that calls `Foo`'s constructor should be changed to have a
244   `Foo` instance injected; Eventually, the only code that instantiates `Foo` is
245   the instantiator in ServiceWiring.php.
246 * As an intermediate step, `Bar`'s constructor could initialize the `$foo`
247   member variable by calling `MediaWikiServices::getInstance()->getFoo()`. This
248   is acceptable as a stepping stone, but should be replaced by proper injection
249   via a constructor argument. Do not however inject the MediaWikiServices
250   object!
252 ### Migrate parameterized helper instantiation
253 Assume class `Bar` creates some helper object by calling `new Foo( $x )`,
254 and `Foo` uses a global singleton of the `Xyzzy` service.
255 * Define a `FooFactory` class (or a `FooFactory` interface along with a
256   `MyFooFactory` implementation). `FooFactory` defines the method
257   `newFoo( $x )` or `getFoo( $x )`, depending on the desired semantics (`newFoo`
258   would guarantee a fresh instance). When Foo gets refactored to have `Xyzzy`
259   injected, `FooFactory` will need a `Xyzzy` instance, so `newFoo()` can pass it
260   to `new Foo()`.
261 * Add an instantiator function for FooFactory into ServiceWiring.php and add a
262   getFooFactory() method to MediaWikiServices. Don't forget to add the
263   appropriate test cases in MediaWikiServicesTest.
264 * The code in Bar that calls Foo's constructor should be changed to have a
265   FooFactory instance injected; Eventually, the only code that instantiates
266   Foo are implementations of FooFactory, and the only code that instantiates
267   FooFactory is the instantiator in ServiceWiring.php.
268 * As an intermediate step, Bar's constructor could initialize the $fooFactory
269   member variable by calling `MediaWikiServices::getInstance()->getFooFactory()`.
270   This is acceptable as a stepping stone, but should be replaced by proper
271   injection via a constructor argument. Do not however inject the
272   MediaWikiServices object!
274 ### Migrate a handler registry
275 Assume class `Bar` calls `FooRegistry::getFoo( $x )` to get a specialized `Foo`
276 instance for handling `$x`.
277 * Turn `getFoo` into a non-static method.
278 * Add an instantiator function for `FooRegistry` into ServiceWiring.php and add
279   a `getFooRegistry()` method to MediaWikiServices. Don't forget to add the
280   appropriate test cases in MediaWikiServicesTest.
281 * Change all code that calls `FooRegistry::getFoo()` statically to call this
282   method on a `FooRegistry` instance. That is, `Bar` would have a `$fooRegistry`
283   member, initialized from a constructor parameter.
284 * As an intermediate step, Bar's constructor could initialize the `$fooRegistry`
285   member variable by calling
286   `MediaWikiServices::getInstance()->getFooRegistry()`. This is acceptable as a
287   stepping stone, but should be replaced by proper injection via a constructor
288   argument. Do not however inject the MediaWikiServices object!
290 ### Migrate deferred service instantiation
291 Assume class `Bar` calls `new Foo()`, but only when needed, to avoid the cost of
292 instantiating Foo().
293 * Define a `FooFactory` interface and a `MyFooFactory` implementation of that
294   interface. `FooFactory` defines the method `getFoo()` with no parameters.
295 * Precede as for the "parameterized helper instantiation" case described above.
297 ### Migrate a class with only static methods
298 Assume `Foo` is a class with only static methods, such as `frob()`, which
299 interacts with global state or system resources.
300 * Introduce a `FooService` interface and a `DefaultFoo` implementation of that
301   interface. `FooService` contains the public methods defined by Foo.
302 * Add an instantiator function for `FooService` into ServiceWiring.php and
303   add a `getFooService()` method to MediaWikiServices. Don't forget to
304   add the appropriate test cases in MediaWikiServicesTest.
305 * Add a private static `getFooService()` method to `Foo`. That method just
306   calls `MediaWikiServices::getInstance()->getFooService()`.
307 * Make all methods in `Foo` delegate to the `FooService` returned by
308   `getFooService()`. That is, `Foo::frob()` would do
309   `self::getFooService()->frob()`.
310 * Deprecate `Foo`. Inject a `FooService` into all code that calls methods
311   on `Foo`, and change any calls to static methods in foo to the methods
312   provided by the `FooService` interface.
314 ### Migrate static hook handler functions (to allow unit testing)
315 Assume `MyExtHooks::onFoo` is a static hook handler function that is called with
316 the parameter `$x`; Further assume `MyExt::onFoo` needs service `Bar`, which is
317 already known to MediaWikiServices (if not, see above).
318 * Create a non-static `doFoo( $x )` method in `MyExtHooks` that has the same
319   signature as `onFoo( $x )`. Move the code from `onFoo()` into `doFoo()`,
320   replacing any access to global or static variables with access to instance
321   member variables.
322 * Add a constructor to `MyExtHooks` that takes a Bar service as a parameter.
323 * Add a static method called `newFromGlobalState()` with no parameters. It
324   should just return
325   `new MyExtHooks( MediaWikiServices::getInstance()->getBar() )`.
326 * The original static handler method `onFoo( $x )` is then implemented as
327   `self::newFromGlobalState()->doFoo( $x )`.
329 ### Migrate a "smart record"
330 Assume `Thingy` is a "smart record" that "knows" how to load and store itself.
331 For this purpose, `Thingy` uses wfGetDB().
332 * Create a "dumb" value class `ThingyRecord` that contains all the information
333   that `Thingy` represents (e.g. the information from a database row). The value
334   object should not know about any service.
335 * Create a DAO-style service for loading and storing `ThingyRecord`s, called
336   `ThingyStore`. It may be useful to split the interfaces for reading and
337   writing, with a single class implementing both interfaces, so we in the
338   end have the `ThingyLookup` and `ThingyStore` interfaces, and a SqlThingyStore
339   implementation.
340 * Add instantiator functions for `ThingyLookup` and `ThingyStore` in
341   ServiceWiring.php. Since we want to use the same instance for both service
342   interfaces, the instantiator for `ThingyLookup` would return
343   `$services->getThingyStore()`.
344 * Add `getThingyLookup()` and `getThingyStore()` methods to MediaWikiServices.
345   Don't forget to add the appropriate test cases in MediaWikiServicesTest.
346 * In the old `Thingy` class, replace all member variables that represent the
347   record's data with a single `ThingyRecord` object.
348 * In the old Thingy class, replace all calls to static methods or functions,
349   such as wfGetDB(), with calls to the appropriate services, such as
350   `LoadBalancer::getConnection()`.
351 * In Thingy's constructor, pull in any services needed, such as the
352   LoadBalancer, by using `MediaWikiServices::getInstance()`. These services
353   cannot be injected without changing the constructor signature, which
354   is often impractical for "smart records" that get instantiated directly
355   in many places in the code base.
356 * Deprecate the old `Thingy` class. Replace all usages of it with one of the
357   three new classes: loading needs a `ThingyLookup`, storing needs a
358   `ThingyStore`, and reading data needs a `ThingyRecord`.
360 ### Migrate lazy loading
361 Assume `Thingy` is a "smart record" as described above, but requires lazy
362 loading of some or all the data it represents.
363 * Instead of a plain object, define `ThingyRecord` to be an interface. Provide a
364   "simple" and "lazy" implementations, called `SimpleThingyRecord` and
365   `LazyThingyRecord`. `LazyThingyRecord` knows about some lower level storage
366   interface, like a LoadBalancer, and uses it to load information on demand.
367 * Any direct instantiation of a `ThingyRecord` would use the
368   `SimpleThingyRecord` implementation.
369 * `SqlThingyStore` however creates instances of `LazyThingyRecord`, and injects
370   whatever storage layer service `LazyThingyRecord` needs to perform lazy
371   loading.