Merge "Chinese Conversion Table Update 2016-2"
[lhc/web/wiklou.git] / includes / MediaWikiServices.php
1 <?php
2 namespace MediaWiki;
3
4 use Config;
5 use ConfigFactory;
6 use EventRelayerGroup;
7 use GlobalVarConfig;
8 use Hooks;
9 use LBFactory;
10 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
11 use LoadBalancer;
12 use MediaWiki\Services\ServiceContainer;
13 use MWException;
14 use ResourceLoader;
15 use SearchEngine;
16 use SearchEngineConfig;
17 use SearchEngineFactory;
18 use SiteLookup;
19 use SiteStore;
20 use SkinFactory;
21
22 /**
23 * Service locator for MediaWiki core services.
24 *
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 2 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License along
36 * with this program; if not, write to the Free Software Foundation, Inc.,
37 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
38 * http://www.gnu.org/copyleft/gpl.html
39 *
40 * @file
41 *
42 * @since 1.27
43 */
44
45 /**
46 * MediaWikiServices is the service locator for the application scope of MediaWiki.
47 * Its implemented as a simple configurable DI container.
48 * MediaWikiServices acts as a top level factory/registry for top level services, and builds
49 * the network of service objects that defines MediaWiki's application logic.
50 * It acts as an entry point to MediaWiki's dependency injection mechanism.
51 *
52 * Services are defined in the "wiring" array passed to the constructor,
53 * or by calling defineService().
54 *
55 * @see docs/injection.txt for an overview of using dependency injection in the
56 * MediaWiki code base.
57 */
58 class MediaWikiServices extends ServiceContainer {
59
60 /**
61 * @var MediaWikiServices|null
62 */
63 private static $instance = null;
64
65 /**
66 * Returns the global default instance of the top level service locator.
67 *
68 * The default instance is initialized using the service instantiator functions
69 * defined in ServiceWiring.php.
70 *
71 * @note This should only be called by static functions! The instance returned here
72 * should not be passed around! Objects that need access to a service should have
73 * that service injected into the constructor, never a service locator!
74 *
75 * @return MediaWikiServices
76 */
77 public static function getInstance() {
78 if ( self::$instance === null ) {
79 // NOTE: constructing GlobalVarConfig here is not particularly pretty,
80 // but some information from the global scope has to be injected here,
81 // even if it's just a file name or database credentials to load
82 // configuration from.
83 $bootstrapConfig = new GlobalVarConfig();
84 self::$instance = self::newInstance( $bootstrapConfig );
85 }
86
87 return self::$instance;
88 }
89
90 /**
91 * Replaces the global MediaWikiServices instance.
92 *
93 * @note This is for use in PHPUnit tests only!
94 *
95 * @throws MWException if called outside of PHPUnit tests.
96 *
97 * @param MediaWikiServices $services The new MediaWikiServices object.
98 *
99 * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later.
100 */
101 public static function forceGlobalInstance( MediaWikiServices $services ) {
102 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
103 throw new MWException( __METHOD__ . ' must not be used outside unit tests.' );
104 }
105
106 $old = self::getInstance();
107 self::$instance = $services;
108
109 return $old;
110 }
111
112 /**
113 * Creates a new instance of MediaWikiServices and sets it as the global default
114 * instance. getInstance() will return a different MediaWikiServices object
115 * after every call to resetGlobalServiceLocator().
116 *
117 * @warning This should not be used during normal operation. It is intended for use
118 * when the configuration has changed significantly since bootstrap time, e.g.
119 * during the installation process or during testing.
120 *
121 * @warning Calling resetGlobalServiceLocator() may leave the application in an inconsistent
122 * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
123 * any of the services managed by MediaWikiServices exist. If any service objects
124 * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
125 * with the operation of the services managed by the new MediaWikiServices.
126 * Operating with a mix of services created by the old and the new
127 * MediaWikiServices instance may lead to INCONSISTENCIES and even DATA LOSS!
128 * Any class implementing LAZY LOADING is especially prone to this problem,
129 * since instances would typically retain a reference to a storage layer service.
130 *
131 * @see forceGlobalInstance()
132 * @see resetGlobalInstance()
133 * @see resetBetweenTest()
134 *
135 * @param Config|null $bootstrapConfig The Config object to be registered as the
136 * 'BootstrapConfig' service. This has to contain at least the information
137 * needed to set up the 'ConfigFactory' service. If not given, the bootstrap
138 * config of the old instance of MediaWikiServices will be re-used. If there
139 * was no previous instance, a new GlobalVarConfig object will be used to
140 * bootstrap the services.
141 *
142 * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
143 * Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
144 * is defined).
145 */
146 public static function resetGlobalInstance( Config $bootstrapConfig = null ) {
147 if ( self::$instance === null ) {
148 // no global instance yet, nothing to reset
149 return;
150 }
151
152 self::failIfResetNotAllowed( __METHOD__ );
153
154 if ( $bootstrapConfig === null ) {
155 $bootstrapConfig = self::$instance->getBootstrapConfig();
156 }
157
158 self::$instance->destroy();
159
160 self::$instance = self::newInstance( $bootstrapConfig );
161 }
162
163 /**
164 * Creates a new MediaWikiServices instance and initializes it according to the
165 * given $bootstrapConfig. In particular, all wiring files defined in the
166 * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
167 *
168 * @param Config|null $bootstrapConfig The Config object to be registered as the
169 * 'BootstrapConfig' service. This has to contain at least the information
170 * needed to set up the 'ConfigFactory' service. If not provided, any call
171 * to getBootstrapConfig(), getConfigFactory, or getMainConfig will fail.
172 * A MediaWikiServices instance without access to configuration is called
173 * "primordial".
174 *
175 * @return MediaWikiServices
176 * @throws MWException
177 */
178 private static function newInstance( Config $bootstrapConfig ) {
179 $instance = new self( $bootstrapConfig );
180
181 // Load the default wiring from the specified files.
182 $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
183 $instance->loadWiringFiles( $wiringFiles );
184
185 // Provide a traditional hook point to allow extensions to configure services.
186 Hooks::run( 'MediaWikiServices', [ $instance ] );
187
188 return $instance;
189 }
190
191 /**
192 * Disables all storage layer services. After calling this, any attempt to access the
193 * storage layer will result in an error. Use resetGlobalInstance() to restore normal
194 * operation.
195 *
196 * @warning This is intended for extreme situations only and should never be used
197 * while serving normal web requests. Legitimate use cases for this method include
198 * the installation process. Test fixtures may also use this, if the fixture relies
199 * on globalState.
200 *
201 * @see resetGlobalInstance()
202 * @see resetChildProcessServices()
203 */
204 public static function disableStorageBackend() {
205 // TODO: also disable some Caches, JobQueues, etc
206 $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ];
207 $services = self::getInstance();
208
209 foreach ( $destroy as $name ) {
210 $services->disableService( $name );
211 }
212 }
213
214 /**
215 * Resets any services that may have become stale after a child process
216 * returns from after pcntl_fork(). It's also safe, but generally unnecessary,
217 * to call this method from the parent process.
218 *
219 * @note This is intended for use in the context of process forking only!
220 *
221 * @see resetGlobalInstance()
222 * @see disableStorageBackend()
223 */
224 public static function resetChildProcessServices() {
225 // NOTE: for now, just reset everything. Since we don't know the interdependencies
226 // between services, we can't do this more selectively at this time.
227 self::resetGlobalInstance();
228
229 // Child, reseed because there is no bug in PHP:
230 // http://bugs.php.net/bug.php?id=42465
231 mt_srand( getmypid() );
232 }
233
234 /**
235 * Resets the given service for testing purposes.
236 *
237 * @warning This is generally unsafe! Other services may still retain references
238 * to the stale service instance, leading to failures and inconsistencies. Subclasses
239 * may use this method to reset specific services under specific instances, but
240 * it should not be exposed to application logic.
241 *
242 * @note With proper dependency injection used throughout the codebase, this method
243 * should not be needed. It is provided to allow tests that pollute global service
244 * instances to clean up.
245 *
246 * @param string $name
247 * @param string $destroy Whether the service instance should be destroyed if it exists.
248 * When set to false, any existing service instance will effectively be detached
249 * from the container.
250 *
251 * @throws MWException if called outside of PHPUnit tests.
252 */
253 public function resetServiceForTesting( $name, $destroy = true ) {
254 if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
255 throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' );
256 }
257
258 $this->resetService( $name, $destroy );
259 }
260
261 /**
262 * Convenience method that throws an exception unless it is called during a phase in which
263 * resetting of global services is allowed. In general, services should not be reset
264 * individually, since that may introduce inconsistencies.
265 *
266 * This method will throw an exception if:
267 *
268 * - self::$resetInProgress is false (to allow all services to be reset together
269 * via resetGlobalInstance)
270 * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation)
271 * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing)
272 *
273 * This method is intended to be used to safeguard against accidentally resetting
274 * global service instances that are not yet managed by MediaWikiServices. It is
275 * defined here in the MediaWikiServices services class to have a central place
276 * for managing service bootstrapping and resetting.
277 *
278 * @param string $method the name of the caller method, as given by __METHOD__.
279 *
280 * @throws MWException if called outside bootstrap mode.
281 *
282 * @see resetGlobalInstance()
283 * @see forceGlobalInstance()
284 * @see disableStorageBackend()
285 */
286 public static function failIfResetNotAllowed( $method ) {
287 if ( !defined( 'MW_PHPUNIT_TEST' )
288 && !defined( 'MW_PARSER_TEST' )
289 && !defined( 'MEDIAWIKI_INSTALL' )
290 && !defined( 'RUN_MAINTENANCE_IF_MAIN' )
291 && defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' )
292 ) {
293 throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' );
294 }
295 }
296
297 /**
298 * @param Config $config The Config object to be registered as the 'BootstrapConfig' service.
299 * This has to contain at least the information needed to set up the 'ConfigFactory'
300 * service.
301 */
302 public function __construct( Config $config ) {
303 parent::__construct();
304
305 // Register the given Config object as the bootstrap config service.
306 $this->defineService( 'BootstrapConfig', function() use ( $config ) {
307 return $config;
308 } );
309 }
310
311 // CONVENIENCE GETTERS ////////////////////////////////////////////////////
312
313 /**
314 * Returns the Config object containing the bootstrap configuration.
315 * Bootstrap configuration would typically include database credentials
316 * and other information that may be needed before the ConfigFactory
317 * service can be instantiated.
318 *
319 * @note This should only be used during bootstrapping, in particular
320 * when creating the MainConfig service. Application logic should
321 * use getMainConfig() to get a Config instances.
322 *
323 * @return Config
324 */
325 public function getBootstrapConfig() {
326 return $this->getService( 'BootstrapConfig' );
327 }
328
329 /**
330 * @return ConfigFactory
331 */
332 public function getConfigFactory() {
333 return $this->getService( 'ConfigFactory' );
334 }
335
336 /**
337 * Returns the Config object that provides configuration for MediaWiki core.
338 * This may or may not be the same object that is returned by getBootstrapConfig().
339 *
340 * @return Config
341 */
342 public function getMainConfig() {
343 return $this->getService( 'MainConfig' );
344 }
345
346 /**
347 * @return SiteLookup
348 */
349 public function getSiteLookup() {
350 return $this->getService( 'SiteLookup' );
351 }
352
353 /**
354 * @return SiteStore
355 */
356 public function getSiteStore() {
357 return $this->getService( 'SiteStore' );
358 }
359
360 /**
361 * @return StatsdDataFactory
362 */
363 public function getStatsdDataFactory() {
364 return $this->getService( 'StatsdDataFactory' );
365 }
366
367 /**
368 * @return EventRelayerGroup
369 */
370 public function getEventRelayerGroup() {
371 return $this->getService( 'EventRelayerGroup' );
372 }
373
374 /**
375 * @return SearchEngine
376 */
377 public function newSearchEngine() {
378 // New engine object every time, since they keep state
379 return $this->getService( 'SearchEngineFactory' )->create();
380 }
381
382 /**
383 * @return SearchEngineFactory
384 */
385 public function getSearchEngineFactory() {
386 return $this->getService( 'SearchEngineFactory' );
387 }
388
389 /**
390 * @return SearchEngineConfig
391 */
392 public function getSearchEngineConfig() {
393 return $this->getService( 'SearchEngineConfig' );
394 }
395
396 /**
397 * @return SkinFactory
398 */
399 public function getSkinFactory() {
400 return $this->getService( 'SkinFactory' );
401 }
402
403 /**
404 * @return LBFactory
405 */
406 public function getDBLoadBalancerFactory() {
407 return $this->getService( 'DBLoadBalancerFactory' );
408 }
409
410 /**
411 * @return LoadBalancer The main DB load balancer for the local wiki.
412 */
413 public function getDBLoadBalancer() {
414 return $this->getService( 'DBLoadBalancer' );
415 }
416
417 ///////////////////////////////////////////////////////////////////////////
418 // NOTE: When adding a service getter here, don't forget to add a test
419 // case for it in MediaWikiServicesTest::provideGetters() and in
420 // MediaWikiServicesTest::provideGetService()!
421 ///////////////////////////////////////////////////////////////////////////
422
423 }