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