Completed
Branch master (e9bc15)
by
unknown
32:19
created

MediaWikiServices::getLocalServerObjectCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace MediaWiki;
3
4
use Config;
5
use ConfigFactory;
6
use CryptRand;
7
use EventRelayerGroup;
8
use GenderCache;
9
use GlobalVarConfig;
10
use Hooks;
11
use LBFactory;
12
use LinkCache;
13
use Liuggio\StatsdClient\Factory\StatsdDataFactory;
14
use LoadBalancer;
15
use MediaHandlerFactory;
16
use MediaWiki\Linker\LinkRenderer;
17
use MediaWiki\Linker\LinkRendererFactory;
18
use MediaWiki\Services\SalvageableService;
19
use MediaWiki\Services\ServiceContainer;
20
use MediaWiki\Services\NoSuchServiceException;
21
use MWException;
22
use ObjectCache;
23
use ProxyLookup;
24
use SearchEngine;
25
use SearchEngineConfig;
26
use SearchEngineFactory;
27
use SiteLookup;
28
use SiteStore;
29
use WatchedItemStore;
30
use WatchedItemQueryService;
31
use SkinFactory;
32
use TitleFormatter;
33
use TitleParser;
34
use VirtualRESTServiceClient;
35
use MediaWiki\Interwiki\InterwikiLookup;
36
37
/**
38
 * Service locator for MediaWiki core services.
39
 *
40
 * This program is free software; you can redistribute it and/or modify
41
 * it under the terms of the GNU General Public License as published by
42
 * the Free Software Foundation; either version 2 of the License, or
43
 * (at your option) any later version.
44
 *
45
 * This program is distributed in the hope that it will be useful,
46
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
47
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48
 * GNU General Public License for more details.
49
 *
50
 * You should have received a copy of the GNU General Public License along
51
 * with this program; if not, write to the Free Software Foundation, Inc.,
52
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
53
 * http://www.gnu.org/copyleft/gpl.html
54
 *
55
 * @file
56
 *
57
 * @since 1.27
58
 */
59
60
/**
61
 * MediaWikiServices is the service locator for the application scope of MediaWiki.
62
 * Its implemented as a simple configurable DI container.
63
 * MediaWikiServices acts as a top level factory/registry for top level services, and builds
64
 * the network of service objects that defines MediaWiki's application logic.
65
 * It acts as an entry point to MediaWiki's dependency injection mechanism.
66
 *
67
 * Services are defined in the "wiring" array passed to the constructor,
68
 * or by calling defineService().
69
 *
70
 * @see docs/injection.txt for an overview of using dependency injection in the
71
 *      MediaWiki code base.
72
 */
73
class MediaWikiServices extends ServiceContainer {
74
75
	/**
76
	 * @var MediaWikiServices|null
77
	 */
78
	private static $instance = null;
79
80
	/**
81
	 * Returns the global default instance of the top level service locator.
82
	 *
83
	 * @since 1.27
84
	 *
85
	 * The default instance is initialized using the service instantiator functions
86
	 * defined in ServiceWiring.php.
87
	 *
88
	 * @note This should only be called by static functions! The instance returned here
89
	 * should not be passed around! Objects that need access to a service should have
90
	 * that service injected into the constructor, never a service locator!
91
	 *
92
	 * @return MediaWikiServices
93
	 */
94
	public static function getInstance() {
95
		if ( self::$instance === null ) {
96
			// NOTE: constructing GlobalVarConfig here is not particularly pretty,
97
			// but some information from the global scope has to be injected here,
98
			// even if it's just a file name or database credentials to load
99
			// configuration from.
100
			$bootstrapConfig = new GlobalVarConfig();
101
			self::$instance = self::newInstance( $bootstrapConfig, 'load' );
102
		}
103
104
		return self::$instance;
105
	}
106
107
	/**
108
	 * Replaces the global MediaWikiServices instance.
109
	 *
110
	 * @since 1.28
111
	 *
112
	 * @note This is for use in PHPUnit tests only!
113
	 *
114
	 * @throws MWException if called outside of PHPUnit tests.
115
	 *
116
	 * @param MediaWikiServices $services The new MediaWikiServices object.
117
	 *
118
	 * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later.
119
	 */
120
	public static function forceGlobalInstance( MediaWikiServices $services ) {
121
		if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
122
			throw new MWException( __METHOD__ . ' must not be used outside unit tests.' );
123
		}
124
125
		$old = self::getInstance();
126
		self::$instance = $services;
127
128
		return $old;
129
	}
130
131
	/**
132
	 * Creates a new instance of MediaWikiServices and sets it as the global default
133
	 * instance. getInstance() will return a different MediaWikiServices object
134
	 * after every call to resetGlobalInstance().
135
	 *
136
	 * @since 1.28
137
	 *
138
	 * @warning This should not be used during normal operation. It is intended for use
139
	 * when the configuration has changed significantly since bootstrap time, e.g.
140
	 * during the installation process or during testing.
141
	 *
142
	 * @warning Calling resetGlobalInstance() may leave the application in an inconsistent
143
	 * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
144
	 * any of the services managed by MediaWikiServices exist. If any service objects
145
	 * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
146
	 * with the operation of the services managed by the new MediaWikiServices.
147
	 * Operating with a mix of services created by the old and the new
148
	 * MediaWikiServices instance may lead to INCONSISTENCIES and even DATA LOSS!
149
	 * Any class implementing LAZY LOADING is especially prone to this problem,
150
	 * since instances would typically retain a reference to a storage layer service.
151
	 *
152
	 * @see forceGlobalInstance()
153
	 * @see resetGlobalInstance()
154
	 * @see resetBetweenTest()
155
	 *
156
	 * @param Config|null $bootstrapConfig The Config object to be registered as the
157
	 *        'BootstrapConfig' service. This has to contain at least the information
158
	 *        needed to set up the 'ConfigFactory' service. If not given, the bootstrap
159
	 *        config of the old instance of MediaWikiServices will be re-used. If there
160
	 *        was no previous instance, a new GlobalVarConfig object will be used to
161
	 *        bootstrap the services.
162
	 *
163
	 * @param string $quick Set this to "quick" to allow expensive resources to be re-used.
164
	 * See SalvageableService for details.
165
	 *
166
	 * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
167
	 *         Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
168
	 *          is defined).
169
	 */
170
	public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) {
171
		if ( self::$instance === null ) {
172
			// no global instance yet, nothing to reset
173
			return;
174
		}
175
176
		self::failIfResetNotAllowed( __METHOD__ );
177
178
		if ( $bootstrapConfig === null ) {
179
			$bootstrapConfig = self::$instance->getBootstrapConfig();
180
		}
181
182
		$oldInstance = self::$instance;
183
184
		self::$instance = self::newInstance( $bootstrapConfig );
185
		self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] );
186
187
		if ( $quick === 'quick' ) {
188
			self::$instance->salvage( $oldInstance );
189
		} else {
190
			$oldInstance->destroy();
191
		}
192
193
	}
194
195
	/**
196
	 * Salvages the state of any salvageable service instances in $other.
197
	 *
198
	 * @note $other will have been destroyed when salvage() returns.
199
	 *
200
	 * @param MediaWikiServices $other
201
	 */
202
	private function salvage( self $other ) {
203
		foreach ( $this->getServiceNames() as $name ) {
204
			// The service could be new in the new instance and not registered in the
205
			// other instance (e.g. an extension that was loaded after the instantiation of
206
			// the other instance. Skip this service in this case. See T143974
207
			try {
208
				$oldService = $other->peekService( $name );
209
			} catch ( NoSuchServiceException $e ) {
210
				continue;
211
			}
212
213
			if ( $oldService instanceof SalvageableService ) {
214
				/** @var SalvageableService $newService */
215
				$newService = $this->getService( $name );
216
				$newService->salvage( $oldService );
217
			}
218
		}
219
220
		$other->destroy();
221
	}
222
223
	/**
224
	 * Creates a new MediaWikiServices instance and initializes it according to the
225
	 * given $bootstrapConfig. In particular, all wiring files defined in the
226
	 * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
227
	 *
228
	 * @param Config|null $bootstrapConfig The Config object to be registered as the
229
	 *        'BootstrapConfig' service.
230
	 *
231
	 * @param string $loadWiring set this to 'load' to load the wiring files specified
232
	 *        in the 'ServiceWiringFiles' setting in $bootstrapConfig.
233
	 *
234
	 * @return MediaWikiServices
235
	 * @throws MWException
236
	 * @throws \FatalError
237
	 */
238
	private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) {
239
		$instance = new self( $bootstrapConfig );
240
241
		// Load the default wiring from the specified files.
242
		if ( $loadWiring === 'load' ) {
243
			$wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
244
			$instance->loadWiringFiles( $wiringFiles );
245
		}
246
247
		// Provide a traditional hook point to allow extensions to configure services.
248
		Hooks::run( 'MediaWikiServices', [ $instance ] );
249
250
		return $instance;
251
	}
252
253
	/**
254
	 * Disables all storage layer services. After calling this, any attempt to access the
255
	 * storage layer will result in an error. Use resetGlobalInstance() to restore normal
256
	 * operation.
257
	 *
258
	 * @since 1.28
259
	 *
260
	 * @warning This is intended for extreme situations only and should never be used
261
	 * while serving normal web requests. Legitimate use cases for this method include
262
	 * the installation process. Test fixtures may also use this, if the fixture relies
263
	 * on globalState.
264
	 *
265
	 * @see resetGlobalInstance()
266
	 * @see resetChildProcessServices()
267
	 */
268
	public static function disableStorageBackend() {
269
		// TODO: also disable some Caches, JobQueues, etc
270
		$destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ];
271
		$services = self::getInstance();
272
273
		foreach ( $destroy as $name ) {
274
			$services->disableService( $name );
275
		}
276
277
		ObjectCache::clear();
278
	}
279
280
	/**
281
	 * Resets any services that may have become stale after a child process
282
	 * returns from after pcntl_fork(). It's also safe, but generally unnecessary,
283
	 * to call this method from the parent process.
284
	 *
285
	 * @since 1.28
286
	 *
287
	 * @note This is intended for use in the context of process forking only!
288
	 *
289
	 * @see resetGlobalInstance()
290
	 * @see disableStorageBackend()
291
	 */
292
	public static function resetChildProcessServices() {
293
		// NOTE: for now, just reset everything. Since we don't know the interdependencies
294
		// between services, we can't do this more selectively at this time.
295
		self::resetGlobalInstance();
296
297
		// Child, reseed because there is no bug in PHP:
298
		// http://bugs.php.net/bug.php?id=42465
299
		mt_srand( getmypid() );
300
	}
301
302
	/**
303
	 * Resets the given service for testing purposes.
304
	 *
305
	 * @since 1.28
306
	 *
307
	 * @warning This is generally unsafe! Other services may still retain references
308
	 * to the stale service instance, leading to failures and inconsistencies. Subclasses
309
	 * may use this method to reset specific services under specific instances, but
310
	 * it should not be exposed to application logic.
311
	 *
312
	 * @note With proper dependency injection used throughout the codebase, this method
313
	 * should not be needed. It is provided to allow tests that pollute global service
314
	 * instances to clean up.
315
	 *
316
	 * @param string $name
317
	 * @param bool $destroy Whether the service instance should be destroyed if it exists.
318
	 *        When set to false, any existing service instance will effectively be detached
319
	 *        from the container.
320
	 *
321
	 * @throws MWException if called outside of PHPUnit tests.
322
	 */
323 View Code Duplication
	public function resetServiceForTesting( $name, $destroy = true ) {
324
		if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
325
			throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' );
326
		}
327
328
		$this->resetService( $name, $destroy );
329
	}
330
331
	/**
332
	 * Convenience method that throws an exception unless it is called during a phase in which
333
	 * resetting of global services is allowed. In general, services should not be reset
334
	 * individually, since that may introduce inconsistencies.
335
	 *
336
	 * @since 1.28
337
	 *
338
	 * This method will throw an exception if:
339
	 *
340
	 * - self::$resetInProgress is false (to allow all services to be reset together
341
	 *   via resetGlobalInstance)
342
	 * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation)
343
	 * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing)
344
	 *
345
	 * This method is intended to be used to safeguard against accidentally resetting
346
	 * global service instances that are not yet managed by MediaWikiServices. It is
347
	 * defined here in the MediaWikiServices services class to have a central place
348
	 * for managing service bootstrapping and resetting.
349
	 *
350
	 * @param string $method the name of the caller method, as given by __METHOD__.
351
	 *
352
	 * @throws MWException if called outside bootstrap mode.
353
	 *
354
	 * @see resetGlobalInstance()
355
	 * @see forceGlobalInstance()
356
	 * @see disableStorageBackend()
357
	 */
358
	public static function failIfResetNotAllowed( $method ) {
359
		if ( !defined( 'MW_PHPUNIT_TEST' )
360
			&& !defined( 'MW_PARSER_TEST' )
361
			&& !defined( 'MEDIAWIKI_INSTALL' )
362
			&& !defined( 'RUN_MAINTENANCE_IF_MAIN' )
363
			&& defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' )
364
		) {
365
			throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' );
366
		}
367
	}
368
369
	/**
370
	 * @param Config $config The Config object to be registered as the 'BootstrapConfig' service.
371
	 *        This has to contain at least the information needed to set up the 'ConfigFactory'
372
	 *        service.
373
	 */
374
	public function __construct( Config $config ) {
375
		parent::__construct();
376
377
		// Register the given Config object as the bootstrap config service.
378
		$this->defineService( 'BootstrapConfig', function() use ( $config ) {
379
			return $config;
380
		} );
381
	}
382
383
	// CONVENIENCE GETTERS ////////////////////////////////////////////////////
384
385
	/**
386
	 * Returns the Config object containing the bootstrap configuration.
387
	 * Bootstrap configuration would typically include database credentials
388
	 * and other information that may be needed before the ConfigFactory
389
	 * service can be instantiated.
390
	 *
391
	 * @note This should only be used during bootstrapping, in particular
392
	 * when creating the MainConfig service. Application logic should
393
	 * use getMainConfig() to get a Config instances.
394
	 *
395
	 * @since 1.27
396
	 * @return Config
397
	 */
398
	public function getBootstrapConfig() {
399
		return $this->getService( 'BootstrapConfig' );
400
	}
401
402
	/**
403
	 * @since 1.27
404
	 * @return ConfigFactory
405
	 */
406
	public function getConfigFactory() {
407
		return $this->getService( 'ConfigFactory' );
408
	}
409
410
	/**
411
	 * Returns the Config object that provides configuration for MediaWiki core.
412
	 * This may or may not be the same object that is returned by getBootstrapConfig().
413
	 *
414
	 * @since 1.27
415
	 * @return Config
416
	 */
417
	public function getMainConfig() {
418
		return $this->getService( 'MainConfig' );
419
	}
420
421
	/**
422
	 * @since 1.27
423
	 * @return SiteLookup
424
	 */
425
	public function getSiteLookup() {
426
		return $this->getService( 'SiteLookup' );
427
	}
428
429
	/**
430
	 * @since 1.27
431
	 * @return SiteStore
432
	 */
433
	public function getSiteStore() {
434
		return $this->getService( 'SiteStore' );
435
	}
436
437
	/**
438
	 * @since 1.28
439
	 * @return InterwikiLookup
440
	 */
441
	public function getInterwikiLookup() {
442
		return $this->getService( 'InterwikiLookup' );
443
	}
444
445
	/**
446
	 * @since 1.27
447
	 * @return StatsdDataFactory
448
	 */
449
	public function getStatsdDataFactory() {
450
		return $this->getService( 'StatsdDataFactory' );
451
	}
452
453
	/**
454
	 * @since 1.27
455
	 * @return EventRelayerGroup
456
	 */
457
	public function getEventRelayerGroup() {
458
		return $this->getService( 'EventRelayerGroup' );
459
	}
460
461
	/**
462
	 * @since 1.27
463
	 * @return SearchEngine
464
	 */
465
	public function newSearchEngine() {
466
		// New engine object every time, since they keep state
467
		return $this->getService( 'SearchEngineFactory' )->create();
468
	}
469
470
	/**
471
	 * @since 1.27
472
	 * @return SearchEngineFactory
473
	 */
474
	public function getSearchEngineFactory() {
475
		return $this->getService( 'SearchEngineFactory' );
476
	}
477
478
	/**
479
	 * @since 1.27
480
	 * @return SearchEngineConfig
481
	 */
482
	public function getSearchEngineConfig() {
483
		return $this->getService( 'SearchEngineConfig' );
484
	}
485
486
	/**
487
	 * @since 1.27
488
	 * @return SkinFactory
489
	 */
490
	public function getSkinFactory() {
491
		return $this->getService( 'SkinFactory' );
492
	}
493
494
	/**
495
	 * @since 1.28
496
	 * @return LBFactory
497
	 */
498
	public function getDBLoadBalancerFactory() {
499
		return $this->getService( 'DBLoadBalancerFactory' );
500
	}
501
502
	/**
503
	 * @since 1.28
504
	 * @return LoadBalancer The main DB load balancer for the local wiki.
505
	 */
506
	public function getDBLoadBalancer() {
507
		return $this->getService( 'DBLoadBalancer' );
508
	}
509
510
	/**
511
	 * @since 1.28
512
	 * @return WatchedItemStore
513
	 */
514
	public function getWatchedItemStore() {
515
		return $this->getService( 'WatchedItemStore' );
516
	}
517
518
	/**
519
	 * @since 1.28
520
	 * @return WatchedItemQueryService
521
	 */
522
	public function getWatchedItemQueryService() {
523
		return $this->getService( 'WatchedItemQueryService' );
524
	}
525
526
	/**
527
	 * @since 1.28
528
	 * @return CryptRand
529
	 */
530
	public function getCryptRand() {
531
		return $this->getService( 'CryptRand' );
532
	}
533
534
	/**
535
	 * @since 1.28
536
	 * @return MediaHandlerFactory
537
	 */
538
	public function getMediaHandlerFactory() {
539
		return $this->getService( 'MediaHandlerFactory' );
540
	}
541
542
	/**
543
	 * @since 1.28
544
	 * @return ProxyLookup
545
	 */
546
	public function getProxyLookup() {
547
		return $this->getService( 'ProxyLookup' );
548
	}
549
550
	/**
551
	 * @since 1.28
552
	 * @return GenderCache
553
	 */
554
	public function getGenderCache() {
555
		return $this->getService( 'GenderCache' );
556
	}
557
558
	/**
559
	 * @since 1.28
560
	 * @return LinkCache
561
	 */
562
	public function getLinkCache() {
563
		return $this->getService( 'LinkCache' );
564
	}
565
566
	/**
567
	 * @since 1.28
568
	 * @return LinkRendererFactory
569
	 */
570
	public function getLinkRendererFactory() {
571
		return $this->getService( 'LinkRendererFactory' );
572
	}
573
574
	/**
575
	 * LinkRenderer instance that can be used
576
	 * if no custom options are needed
577
	 *
578
	 * @since 1.28
579
	 * @return LinkRenderer
580
	 */
581
	public function getLinkRenderer() {
582
		return $this->getService( 'LinkRenderer' );
583
	}
584
585
	/**
586
	 * @since 1.28
587
	 * @return TitleFormatter
588
	 */
589
	public function getTitleFormatter() {
590
		return $this->getService( 'TitleFormatter' );
591
	}
592
593
	/**
594
	 * @since 1.28
595
	 * @return TitleParser
596
	 */
597
	public function getTitleParser() {
598
		return $this->getService( 'TitleParser' );
599
	}
600
601
	/**
602
	 * @since 1.28
603
	 * @return \BagOStuff
604
	 */
605
	public function getMainObjectStash() {
606
		return $this->getService( 'MainObjectStash' );
607
	}
608
609
	/**
610
	 * @since 1.28
611
	 * @return \WANObjectCache
612
	 */
613
	public function getMainWANObjectCache() {
614
		return $this->getService( 'MainWANObjectCache' );
615
	}
616
617
	/**
618
	 * @since 1.28
619
	 * @return \BagOStuff
620
	 */
621
	public function getLocalServerObjectCache() {
622
		return $this->getService( 'LocalServerObjectCache' );
623
	}
624
625
	/**
626
	 * @since 1.28
627
	 * @return VirtualRESTServiceClient
628
	 */
629
	public function getVirtualRESTServiceClient() {
630
		return $this->getService( 'VirtualRESTServiceClient' );
631
	}
632
633
	///////////////////////////////////////////////////////////////////////////
634
	// NOTE: When adding a service getter here, don't forget to add a test
635
	// case for it in MediaWikiServicesTest::provideGetters() and in
636
	// MediaWikiServicesTest::provideGetService()!
637
	///////////////////////////////////////////////////////////////////////////
638
639
}
640