X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FMediaWikiServices.php;h=ff292cfb617532ad35113e17ad24ff34987336d0;hb=5063fa6ee86d965f130066e0d5503998b1f93590;hp=1f3d81c22eea5c261347f49493caa50e6af28930;hpb=fb993d6b8bc94b6349dc996180e0938e59f129e6;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 1f3d81c22e..ff292cfb61 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -1,19 +1,33 @@ get( 'ServiceWiringFiles' ); - $instance->loadWiringFiles( $wiringFiles ); + return self::$instance; + } + + /** + * Replaces the global MediaWikiServices instance. + * + * @since 1.28 + * + * @note This is for use in PHPUnit tests only! + * + * @throws MWException if called outside of PHPUnit tests. + * + * @param MediaWikiServices $services The new MediaWikiServices object. + * + * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later. + */ + public static function forceGlobalInstance( MediaWikiServices $services ) { + if ( !defined( 'MW_PHPUNIT_TEST' ) ) { + throw new MWException( __METHOD__ . ' must not be used outside unit tests.' ); + } + + $old = self::getInstance(); + self::$instance = $services; + + return $old; + } + + /** + * Creates a new instance of MediaWikiServices and sets it as the global default + * instance. getInstance() will return a different MediaWikiServices object + * after every call to resetGlobalInstance(). + * + * @since 1.28 + * + * @warning This should not be used during normal operation. It is intended for use + * when the configuration has changed significantly since bootstrap time, e.g. + * during the installation process or during testing. + * + * @warning Calling resetGlobalInstance() may leave the application in an inconsistent + * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to + * any of the services managed by MediaWikiServices exist. If any service objects + * managed by the old MediaWikiServices instance remain in use, they may INTERFERE + * with the operation of the services managed by the new MediaWikiServices. + * Operating with a mix of services created by the old and the new + * MediaWikiServices instance may lead to INCONSISTENCIES and even DATA LOSS! + * Any class implementing LAZY LOADING is especially prone to this problem, + * since instances would typically retain a reference to a storage layer service. + * + * @see forceGlobalInstance() + * @see resetGlobalInstance() + * @see resetBetweenTest() + * + * @param Config|null $bootstrapConfig 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. If not given, the bootstrap + * config of the old instance of MediaWikiServices will be re-used. If there + * was no previous instance, a new GlobalVarConfig object will be used to + * bootstrap the services. + * + * @param string $quick Set this to "quick" to allow expensive resources to be re-used. + * See SalvageableService for details. + * + * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in + * Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN + * is defined). + */ + public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) { + if ( self::$instance === null ) { + // no global instance yet, nothing to reset + return; + } + + self::failIfResetNotAllowed( __METHOD__ ); + + if ( $bootstrapConfig === null ) { + $bootstrapConfig = self::$instance->getBootstrapConfig(); + } + + $oldInstance = self::$instance; - // Provide a traditional hook point to allow extensions to configure services. - Hooks::run( 'MediaWikiServices', [ $instance ] ); + self::$instance = self::newInstance( $bootstrapConfig ); + 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 ) { + $oldService = $other->peekService( $name ); + + 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: + // http://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' @@ -95,12 +362,14 @@ class MediaWikiServices extends ServiceContainer { public function __construct( Config $config ) { parent::__construct(); - // register the given Config object as the bootstrap config service. + // 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 @@ -111,6 +380,7 @@ class MediaWikiServices extends ServiceContainer { * when creating the MainConfig service. Application logic should * use getMainConfig() to get a Config instances. * + * @since 1.27 * @return Config */ public function getBootstrapConfig() { @@ -118,6 +388,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return ConfigFactory */ public function getConfigFactory() { @@ -128,6 +399,7 @@ class MediaWikiServices extends ServiceContainer { * 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() { @@ -135,6 +407,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return SiteLookup */ public function getSiteLookup() { @@ -142,6 +415,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return SiteStore */ public function getSiteStore() { @@ -149,6 +423,15 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.28 + * @return InterwikiLookup + */ + public function getInterwikiLookup() { + return $this->getService( 'InterwikiLookup' ); + } + + /** + * @since 1.27 * @return StatsdDataFactory */ public function getStatsdDataFactory() { @@ -156,6 +439,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return EventRelayerGroup */ public function getEventRelayerGroup() { @@ -163,6 +447,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return SearchEngine */ public function newSearchEngine() { @@ -171,6 +456,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return SearchEngineFactory */ public function getSearchEngineFactory() { @@ -178,6 +464,7 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.27 * @return SearchEngineConfig */ public function getSearchEngineConfig() { @@ -185,12 +472,96 @@ class MediaWikiServices extends ServiceContainer { } /** + * @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 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' ); + } + /////////////////////////////////////////////////////////////////////////// // NOTE: When adding a service getter here, don't forget to add a test // case for it in MediaWikiServicesTest::provideGetters() and in