X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/MediaWikiServices.php diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php new file mode 100644 index 00000000..0d010b49 --- /dev/null +++ b/includes/MediaWikiServices.php @@ -0,0 +1,699 @@ +getBootstrapConfig(); + } + + $oldInstance = self::$instance; + + self::$instance = self::newInstance( $bootstrapConfig, 'load' ); + self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] ); + + if ( $quick === 'quick' ) { + self::$instance->salvage( $oldInstance ); + } else { + $oldInstance->destroy(); + } + } + + /** + * Salvages the state of any salvageable service instances in $other. + * + * @note $other will have been destroyed when salvage() returns. + * + * @param MediaWikiServices $other + */ + private function salvage( self $other ) { + foreach ( $this->getServiceNames() as $name ) { + // The service could be new in the new instance and not registered in the + // other instance (e.g. an extension that was loaded after the instantiation of + // the other instance. Skip this service in this case. See T143974 + try { + $oldService = $other->peekService( $name ); + } catch ( NoSuchServiceException $e ) { + continue; + } + + if ( $oldService instanceof SalvageableService ) { + /** @var SalvageableService $newService */ + $newService = $this->getService( $name ); + $newService->salvage( $oldService ); + } + } + + $other->destroy(); + } + + /** + * Creates a new MediaWikiServices instance and initializes it according to the + * given $bootstrapConfig. In particular, all wiring files defined in the + * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called. + * + * @param Config|null $bootstrapConfig The Config object to be registered as the + * 'BootstrapConfig' service. + * + * @param string $loadWiring set this to 'load' to load the wiring files specified + * in the 'ServiceWiringFiles' setting in $bootstrapConfig. + * + * @return MediaWikiServices + * @throws MWException + * @throws \FatalError + */ + private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) { + $instance = new self( $bootstrapConfig ); + + // Load the default wiring from the specified files. + if ( $loadWiring === 'load' ) { + $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' ); + $instance->loadWiringFiles( $wiringFiles ); + } + + // Provide a traditional hook point to allow extensions to configure services. + Hooks::run( 'MediaWikiServices', [ $instance ] ); + + return $instance; + } + + /** + * Disables all storage layer services. After calling this, any attempt to access the + * storage layer will result in an error. Use resetGlobalInstance() to restore normal + * operation. + * + * @since 1.28 + * + * @warning This is intended for extreme situations only and should never be used + * while serving normal web requests. Legitimate use cases for this method include + * the installation process. Test fixtures may also use this, if the fixture relies + * on globalState. + * + * @see resetGlobalInstance() + * @see resetChildProcessServices() + */ + public static function disableStorageBackend() { + // TODO: also disable some Caches, JobQueues, etc + $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ]; + $services = self::getInstance(); + + foreach ( $destroy as $name ) { + $services->disableService( $name ); + } + + ObjectCache::clear(); + } + + /** + * Resets any services that may have become stale after a child process + * returns from after pcntl_fork(). It's also safe, but generally unnecessary, + * to call this method from the parent process. + * + * @since 1.28 + * + * @note This is intended for use in the context of process forking only! + * + * @see resetGlobalInstance() + * @see disableStorageBackend() + */ + public static function resetChildProcessServices() { + // NOTE: for now, just reset everything. Since we don't know the interdependencies + // between services, we can't do this more selectively at this time. + self::resetGlobalInstance(); + + // Child, reseed because there is no bug in PHP: + // https://bugs.php.net/bug.php?id=42465 + mt_srand( getmypid() ); + } + + /** + * Resets the given service for testing purposes. + * + * @since 1.28 + * + * @warning This is generally unsafe! Other services may still retain references + * to the stale service instance, leading to failures and inconsistencies. Subclasses + * may use this method to reset specific services under specific instances, but + * it should not be exposed to application logic. + * + * @note With proper dependency injection used throughout the codebase, this method + * should not be needed. It is provided to allow tests that pollute global service + * instances to clean up. + * + * @param string $name + * @param bool $destroy Whether the service instance should be destroyed if it exists. + * When set to false, any existing service instance will effectively be detached + * from the container. + * + * @throws MWException if called outside of PHPUnit tests. + */ + public function resetServiceForTesting( $name, $destroy = true ) { + if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) { + throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' ); + } + + $this->resetService( $name, $destroy ); + } + + /** + * Convenience method that throws an exception unless it is called during a phase in which + * resetting of global services is allowed. In general, services should not be reset + * individually, since that may introduce inconsistencies. + * + * @since 1.28 + * + * This method will throw an exception if: + * + * - self::$resetInProgress is false (to allow all services to be reset together + * via resetGlobalInstance) + * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation) + * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing) + * + * This method is intended to be used to safeguard against accidentally resetting + * global service instances that are not yet managed by MediaWikiServices. It is + * defined here in the MediaWikiServices services class to have a central place + * for managing service bootstrapping and resetting. + * + * @param string $method the name of the caller method, as given by __METHOD__. + * + * @throws MWException if called outside bootstrap mode. + * + * @see resetGlobalInstance() + * @see forceGlobalInstance() + * @see disableStorageBackend() + */ + public static function failIfResetNotAllowed( $method ) { + if ( !defined( 'MW_PHPUNIT_TEST' ) + && !defined( 'MW_PARSER_TEST' ) + && !defined( 'MEDIAWIKI_INSTALL' ) + && !defined( 'RUN_MAINTENANCE_IF_MAIN' ) + && defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' ) + ) { + throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' ); + } + } + + /** + * @param Config $config The Config object to be registered as the 'BootstrapConfig' service. + * This has to contain at least the information needed to set up the 'ConfigFactory' + * service. + */ + public function __construct( Config $config ) { + parent::__construct(); + + // Register the given Config object as the bootstrap config service. + $this->defineService( 'BootstrapConfig', function () use ( $config ) { + return $config; + } ); + } + + // CONVENIENCE GETTERS //////////////////////////////////////////////////// + + /** + * Returns the Config object containing the bootstrap configuration. + * Bootstrap configuration would typically include database credentials + * and other information that may be needed before the ConfigFactory + * service can be instantiated. + * + * @note This should only be used during bootstrapping, in particular + * when creating the MainConfig service. Application logic should + * use getMainConfig() to get a Config instances. + * + * @since 1.27 + * @return Config + */ + public function getBootstrapConfig() { + return $this->getService( 'BootstrapConfig' ); + } + + /** + * @since 1.27 + * @return ConfigFactory + */ + public function getConfigFactory() { + return $this->getService( 'ConfigFactory' ); + } + + /** + * Returns the Config object that provides configuration for MediaWiki core. + * This may or may not be the same object that is returned by getBootstrapConfig(). + * + * @since 1.27 + * @return Config + */ + public function getMainConfig() { + return $this->getService( 'MainConfig' ); + } + + /** + * @since 1.27 + * @return SiteLookup + */ + public function getSiteLookup() { + return $this->getService( 'SiteLookup' ); + } + + /** + * @since 1.27 + * @return SiteStore + */ + public function getSiteStore() { + return $this->getService( 'SiteStore' ); + } + + /** + * @since 1.28 + * @return InterwikiLookup + */ + public function getInterwikiLookup() { + return $this->getService( 'InterwikiLookup' ); + } + + /** + * @since 1.27 + * @return IBufferingStatsdDataFactory + */ + public function getStatsdDataFactory() { + return $this->getService( 'StatsdDataFactory' ); + } + + /** + * @since 1.27 + * @return EventRelayerGroup + */ + public function getEventRelayerGroup() { + return $this->getService( 'EventRelayerGroup' ); + } + + /** + * @since 1.27 + * @return SearchEngine + */ + public function newSearchEngine() { + // New engine object every time, since they keep state + return $this->getService( 'SearchEngineFactory' )->create(); + } + + /** + * @since 1.27 + * @return SearchEngineFactory + */ + public function getSearchEngineFactory() { + return $this->getService( 'SearchEngineFactory' ); + } + + /** + * @since 1.27 + * @return SearchEngineConfig + */ + public function getSearchEngineConfig() { + return $this->getService( 'SearchEngineConfig' ); + } + + /** + * @since 1.27 + * @return SkinFactory + */ + public function getSkinFactory() { + return $this->getService( 'SkinFactory' ); + } + + /** + * @since 1.28 + * @return LBFactory + */ + public function getDBLoadBalancerFactory() { + return $this->getService( 'DBLoadBalancerFactory' ); + } + + /** + * @since 1.28 + * @return LoadBalancer The main DB load balancer for the local wiki. + */ + public function getDBLoadBalancer() { + return $this->getService( 'DBLoadBalancer' ); + } + + /** + * @since 1.28 + * @return WatchedItemStore + */ + public function getWatchedItemStore() { + return $this->getService( 'WatchedItemStore' ); + } + + /** + * @since 1.28 + * @return WatchedItemQueryService + */ + public function getWatchedItemQueryService() { + return $this->getService( 'WatchedItemQueryService' ); + } + + /** + * @since 1.28 + * @return CryptRand + */ + public function getCryptRand() { + return $this->getService( 'CryptRand' ); + } + + /** + * @since 1.28 + * @return CryptHKDF + */ + public function getCryptHKDF() { + return $this->getService( 'CryptHKDF' ); + } + + /** + * @since 1.28 + * @return MediaHandlerFactory + */ + public function getMediaHandlerFactory() { + return $this->getService( 'MediaHandlerFactory' ); + } + + /** + * @since 1.28 + * @return MimeAnalyzer + */ + public function getMimeAnalyzer() { + return $this->getService( 'MimeAnalyzer' ); + } + + /** + * @since 1.28 + * @return ProxyLookup + */ + public function getProxyLookup() { + return $this->getService( 'ProxyLookup' ); + } + + /** + * @since 1.29 + * @return Parser + */ + public function getParser() { + return $this->getService( 'Parser' ); + } + + /** + * @since 1.30 + * @return ParserCache + */ + public function getParserCache() { + return $this->getService( 'ParserCache' ); + } + + /** + * @since 1.28 + * @return GenderCache + */ + public function getGenderCache() { + return $this->getService( 'GenderCache' ); + } + + /** + * @since 1.28 + * @return LinkCache + */ + public function getLinkCache() { + return $this->getService( 'LinkCache' ); + } + + /** + * @since 1.28 + * @return LinkRendererFactory + */ + public function getLinkRendererFactory() { + return $this->getService( 'LinkRendererFactory' ); + } + + /** + * LinkRenderer instance that can be used + * if no custom options are needed + * + * @since 1.28 + * @return LinkRenderer + */ + public function getLinkRenderer() { + return $this->getService( 'LinkRenderer' ); + } + + /** + * @since 1.28 + * @return TitleFormatter + */ + public function getTitleFormatter() { + return $this->getService( 'TitleFormatter' ); + } + + /** + * @since 1.28 + * @return TitleParser + */ + public function getTitleParser() { + return $this->getService( 'TitleParser' ); + } + + /** + * @since 1.28 + * @return \BagOStuff + */ + public function getMainObjectStash() { + return $this->getService( 'MainObjectStash' ); + } + + /** + * @since 1.28 + * @return \WANObjectCache + */ + public function getMainWANObjectCache() { + return $this->getService( 'MainWANObjectCache' ); + } + + /** + * @since 1.28 + * @return \BagOStuff + */ + public function getLocalServerObjectCache() { + return $this->getService( 'LocalServerObjectCache' ); + } + + /** + * @since 1.28 + * @return VirtualRESTServiceClient + */ + public function getVirtualRESTServiceClient() { + return $this->getService( 'VirtualRESTServiceClient' ); + } + + /** + * @since 1.29 + * @return \ConfiguredReadOnlyMode + */ + public function getConfiguredReadOnlyMode() { + return $this->getService( 'ConfiguredReadOnlyMode' ); + } + + /** + * @since 1.29 + * @return \ReadOnlyMode + */ + public function getReadOnlyMode() { + return $this->getService( 'ReadOnlyMode' ); + } + + /** + * @since 1.30 + * @return CommandFactory + */ + public function getShellCommandFactory() { + return $this->getService( 'ShellCommandFactory' ); + } + + /////////////////////////////////////////////////////////////////////////// + // NOTE: When adding a service getter here, don't forget to add a test + // case for it in MediaWikiServicesTest::provideGetters() and in + // MediaWikiServicesTest::provideGetService()! + /////////////////////////////////////////////////////////////////////////// + +}