'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php',
'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php',
- 'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/Services/CannotReplaceActiveServiceException.php',
- 'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/Services/ContainerDisabledException.php',
- 'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/Services/DestructibleService.php',
- 'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/Services/NoSuchServiceException.php',
- 'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/Services/SalvageableService.php',
- 'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/Services/ServiceAlreadyDefinedException.php',
- 'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/Services/ServiceContainer.php',
- 'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/Services/ServiceDisabledException.php',
+ 'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/services/CannotReplaceActiveServiceException.php',
+ 'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/services/ContainerDisabledException.php',
+ 'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/services/DestructibleService.php',
+ 'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/services/NoSuchServiceException.php',
+ 'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/services/SalvageableService.php',
+ 'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/services/ServiceAlreadyDefinedException.php',
+ 'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/services/ServiceContainer.php',
+ 'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/services/ServiceDisabledException.php',
'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ . '/includes/session/BotPasswordSessionProvider.php',
'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ . '/includes/session/CookieSessionProvider.php',
'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ . '/includes/session/ImmutableSessionProviderWithCookie.php',
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to replace an already active service.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * Exception thrown when trying to replace an already active service.
- */
-class CannotReplaceActiveServiceException extends RuntimeException {
-
- /**
- * @param string $serviceName
- * @param Exception|null $previous
- */
- public function __construct( $serviceName, Exception $previous = null ) {
- parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
- }
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- */
-class ContainerDisabledException extends RuntimeException {
-
- /**
- * @param Exception|null $previous
- */
- public function __construct( Exception $previous = null ) {
- parent::__construct( 'Container disabled!', 0, $previous );
- }
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for destructible services.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * DestructibleService defines a standard interface for shutting down a service instance.
- * The intended use is for a service container to be able to shut down services that should
- * no longer be used, and allow such services to release any system resources.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface DestructibleService {
-
- /**
- * Notifies the service object that it should expect to no longer be used, and should release
- * any system resources it may own. The behavior of all service methods becomes undefined after
- * destroy() has been called. It is recommended that implementing classes should throw an
- * exception when service methods are accessed after destroy() has been called.
- */
- public function destroy();
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when the requested service is not known.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * Exception thrown when the requested service is not known.
- */
-class NoSuchServiceException extends RuntimeException {
-
- /**
- * @param string $serviceName
- * @param Exception|null $previous
- */
- public function __construct( $serviceName, Exception $previous = null ) {
- parent::__construct( "No such service: $serviceName", 0, $previous );
- }
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for salvageable services.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.28
- */
-
-/**
- * SalvageableService defines an interface for services that are able to salvage state from a
- * previous instance of the same class. The intent is to allow new service instances to re-use
- * resources that would be expensive to re-create, such as cached data or network connections.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface SalvageableService {
-
- /**
- * Re-uses state from $other. $other must not be used after being passed to salvage(),
- * and should be considered to be destroyed.
- *
- * @note Implementations are responsible for determining what parts of $other can be re-used
- * safely. In particular, implementations should check that the relevant configuration of
- * $other is the same as in $this before re-using resources from $other.
- *
- * @note Implementations must take care to detach any re-used resources from the original
- * service instance. If $other is destroyed later, resources that are now used by the
- * new service instance must not be affected.
- *
- * @note If $other is a DestructibleService, implementations should make sure that $other
- * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
- * after carefully detaching all relevant resources.
- *
- * @param SalvageableService $other The object to salvage state from. $other must have the
- * exact same type as $this.
- */
- public function salvage( SalvageableService $other );
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- */
-class ServiceAlreadyDefinedException extends RuntimeException {
-
- /**
- * @param string $serviceName
- * @param Exception|null $previous
- */
- public function __construct( $serviceName, Exception $previous = null ) {
- parent::__construct( "Service already defined: $serviceName", 0, $previous );
- }
-
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use InvalidArgumentException;
-use RuntimeException;
-use Wikimedia\Assert\Assert;
-
-/**
- * Generic service container.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * ServiceContainer provides a generic service to manage named services using
- * lazy instantiation based on instantiator callback functions.
- *
- * Services managed by an instance of ServiceContainer may or may not implement
- * a common interface.
- *
- * @note When using ServiceContainer to manage a set of services, consider
- * creating a wrapper or a subclass that provides access to the services via
- * getter methods with more meaningful names and more specific return type
- * declarations.
- *
- * @see docs/injection.txt for an overview of using dependency injection in the
- * MediaWiki code base.
- */
-class ServiceContainer implements DestructibleService {
-
- /**
- * @var object[]
- */
- private $services = [];
-
- /**
- * @var callable[]
- */
- private $serviceInstantiators = [];
-
- /**
- * @var boolean[] disabled status, per service name
- */
- private $disabled = [];
-
- /**
- * @var array
- */
- private $extraInstantiationParams;
-
- /**
- * @var boolean
- */
- private $destroyed = false;
-
- /**
- * @param array $extraInstantiationParams Any additional parameters to be passed to the
- * instantiator function when creating a service. This is typically used to provide
- * access to additional ServiceContainers or Config objects.
- */
- public function __construct( array $extraInstantiationParams = [] ) {
- $this->extraInstantiationParams = $extraInstantiationParams;
- }
-
- /**
- * Destroys all contained service instances that implement the DestructibleService
- * interface. This will render all services obtained from this MediaWikiServices
- * instance unusable. In particular, this will disable access to the storage backend
- * via any of these services. Any future call to getService() will throw an exception.
- *
- * @see resetGlobalInstance()
- */
- public function destroy() {
- foreach ( $this->getServiceNames() as $name ) {
- $service = $this->peekService( $name );
- if ( $service !== null && $service instanceof DestructibleService ) {
- $service->destroy();
- }
- }
-
- $this->destroyed = true;
- }
-
- /**
- * @param array $wiringFiles A list of PHP files to load wiring information from.
- * Each file is loaded using PHP's include mechanism. Each file is expected to
- * return an associative array that maps service names to instantiator functions.
- */
- public function loadWiringFiles( array $wiringFiles ) {
- foreach ( $wiringFiles as $file ) {
- // the wiring file is required to return an array of instantiators.
- $wiring = require $file;
-
- Assert::postcondition(
- is_array( $wiring ),
- "Wiring file $file is expected to return an array!"
- );
-
- $this->applyWiring( $wiring );
- }
- }
-
- /**
- * Registers multiple services (aka a "wiring").
- *
- * @param array $serviceInstantiators An associative array mapping service names to
- * instantiator functions.
- */
- public function applyWiring( array $serviceInstantiators ) {
- Assert::parameterElementType( 'callable', $serviceInstantiators, '$serviceInstantiators' );
-
- foreach ( $serviceInstantiators as $name => $instantiator ) {
- $this->defineService( $name, $instantiator );
- }
- }
-
- /**
- * Imports all wiring defined in $container. Wiring defined in $container
- * will override any wiring already defined locally. However, already
- * existing service instances will be preserved.
- *
- * @since 1.28
- *
- * @param ServiceContainer $container
- * @param string[] $skip A list of service names to skip during import
- */
- public function importWiring( ServiceContainer $container, $skip = [] ) {
- $newInstantiators = array_diff_key(
- $container->serviceInstantiators,
- array_flip( $skip )
- );
-
- $this->serviceInstantiators = array_merge(
- $this->serviceInstantiators,
- $newInstantiators
- );
- }
-
- /**
- * Returns true if a service is defined for $name, that is, if a call to getService( $name )
- * would return a service instance.
- *
- * @param string $name
- *
- * @return bool
- */
- public function hasService( $name ) {
- return isset( $this->serviceInstantiators[$name] );
- }
-
- /**
- * Returns the service instance for $name only if that service has already been instantiated.
- * This is intended for situations where services get destroyed/cleaned up, so we can
- * avoid creating a service just to destroy it again.
- *
- * @note This is intended for internal use and for test fixtures.
- * Application logic should use getService() instead.
- *
- * @see getService().
- *
- * @param string $name
- *
- * @return object|null The service instance, or null if the service has not yet been instantiated.
- * @throws RuntimeException if $name does not refer to a known service.
- */
- public function peekService( $name ) {
- if ( !$this->hasService( $name ) ) {
- throw new NoSuchServiceException( $name );
- }
-
- return isset( $this->services[$name] ) ? $this->services[$name] : null;
- }
-
- /**
- * @return string[]
- */
- public function getServiceNames() {
- return array_keys( $this->serviceInstantiators );
- }
-
- /**
- * Define a new service. The service must not be known already.
- *
- * @see getService().
- * @see replaceService().
- *
- * @param string $name The name of the service to register, for use with getService().
- * @param callable $instantiator Callback that returns a service instance.
- * Will be called with this MediaWikiServices instance as the only parameter.
- * Any extra instantiation parameters provided to the constructor will be
- * passed as subsequent parameters when invoking the instantiator.
- *
- * @throws RuntimeException if there is already a service registered as $name.
- */
- public function defineService( $name, callable $instantiator ) {
- Assert::parameterType( 'string', $name, '$name' );
-
- if ( $this->hasService( $name ) ) {
- throw new ServiceAlreadyDefinedException( $name );
- }
-
- $this->serviceInstantiators[$name] = $instantiator;
- }
-
- /**
- * Replace an already defined service.
- *
- * @see defineService().
- *
- * @note This causes any previously instantiated instance of the service to be discarded.
- *
- * @param string $name The name of the service to register.
- * @param callable $instantiator Callback function that returns a service instance.
- * Will be called with this MediaWikiServices instance as the only parameter.
- * The instantiator must return a service compatible with the originally defined service.
- * Any extra instantiation parameters provided to the constructor will be
- * passed as subsequent parameters when invoking the instantiator.
- *
- * @throws RuntimeException if $name is not a known service.
- */
- public function redefineService( $name, callable $instantiator ) {
- Assert::parameterType( 'string', $name, '$name' );
-
- if ( !$this->hasService( $name ) ) {
- throw new NoSuchServiceException( $name );
- }
-
- if ( isset( $this->services[$name] ) ) {
- throw new CannotReplaceActiveServiceException( $name );
- }
-
- $this->serviceInstantiators[$name] = $instantiator;
- unset( $this->disabled[$name] );
- }
-
- /**
- * Disables a service.
- *
- * @note Attempts to call getService() for a disabled service will result
- * in a DisabledServiceException. Calling peekService for a disabled service will
- * return null. Disabled services are listed by getServiceNames(). A disabled service
- * can be enabled again using redefineService().
- *
- * @note If the service was already active (that is, instantiated) when getting disabled,
- * and the service instance implements DestructibleService, destroy() is called on the
- * service instance.
- *
- * @see redefineService()
- * @see resetService()
- *
- * @param string $name The name of the service to disable.
- *
- * @throws RuntimeException if $name is not a known service.
- */
- public function disableService( $name ) {
- $this->resetService( $name );
-
- $this->disabled[$name] = true;
- }
-
- /**
- * Resets a service by dropping the service instance.
- * If the service instances implements DestructibleService, destroy()
- * is called on the service instance.
- *
- * @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 This is declared final so subclasses can not interfere with the expectations
- * disableService() has when calling resetService().
- *
- * @see redefineService()
- * @see disableService().
- *
- * @param string $name The name of the service to reset.
- * @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 RuntimeException if $name is not a known service.
- */
- final protected function resetService( $name, $destroy = true ) {
- Assert::parameterType( 'string', $name, '$name' );
-
- $instance = $this->peekService( $name );
-
- if ( $destroy && $instance instanceof DestructibleService ) {
- $instance->destroy();
- }
-
- unset( $this->services[$name] );
- unset( $this->disabled[$name] );
- }
-
- /**
- * Returns a service object of the kind associated with $name.
- * Services instances are instantiated lazily, on demand.
- * This method may or may not return the same service instance
- * when called multiple times with the same $name.
- *
- * @note Rather than calling this method directly, it is recommended to provide
- * getters with more meaningful names and more specific return types, using
- * a subclass or wrapper.
- *
- * @see redefineService().
- *
- * @param string $name The service name
- *
- * @throws NoSuchServiceException if $name is not a known service.
- * @throws ContainerDisabledException if this container has already been destroyed.
- * @throws ServiceDisabledException if the requested service has been disabled.
- *
- * @return object The service instance
- */
- public function getService( $name ) {
- if ( $this->destroyed ) {
- throw new ContainerDisabledException();
- }
-
- if ( isset( $this->disabled[$name] ) ) {
- throw new ServiceDisabledException( $name );
- }
-
- if ( !isset( $this->services[$name] ) ) {
- $this->services[$name] = $this->createService( $name );
- }
-
- return $this->services[$name];
- }
-
- /**
- * @param string $name
- *
- * @throws InvalidArgumentException if $name is not a known service.
- * @return object
- */
- private function createService( $name ) {
- if ( isset( $this->serviceInstantiators[$name] ) ) {
- $service = call_user_func_array(
- $this->serviceInstantiators[$name],
- array_merge( [ $this ], $this->extraInstantiationParams )
- );
- // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
- } else {
- throw new NoSuchServiceException( $name );
- }
-
- return $service;
- }
-
- /**
- * @param string $name
- * @return bool Whether the service is disabled
- * @since 1.28
- */
- public function isServiceDisabled( $name ) {
- return isset( $this->disabled[$name] );
- }
-}
+++ /dev/null
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a disabled service.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @since 1.27
- */
-
-/**
- * Exception thrown when trying to access a disabled service.
- */
-class ServiceDisabledException extends RuntimeException {
-
- /**
- * @param string $serviceName
- * @param Exception|null $previous
- */
- public function __construct( $serviceName, Exception $previous = null ) {
- parent::__construct( "Service disabled: $serviceName", 0, $previous );
- }
-
-}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ */
+class CannotReplaceActiveServiceException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ */
+class ContainerDisabledException extends RuntimeException {
+
+ /**
+ * @param Exception|null $previous
+ */
+ public function __construct( Exception $previous = null ) {
+ parent::__construct( 'Container disabled!', 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for destructible services.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * DestructibleService defines a standard interface for shutting down a service instance.
+ * The intended use is for a service container to be able to shut down services that should
+ * no longer be used, and allow such services to release any system resources.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface DestructibleService {
+
+ /**
+ * Notifies the service object that it should expect to no longer be used, and should release
+ * any system resources it may own. The behavior of all service methods becomes undefined after
+ * destroy() has been called. It is recommended that implementing classes should throw an
+ * exception when service methods are accessed after destroy() has been called.
+ */
+ public function destroy();
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when the requested service is not known.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when the requested service is not known.
+ */
+class NoSuchServiceException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "No such service: $serviceName", 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for salvageable services.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.28
+ */
+
+/**
+ * SalvageableService defines an interface for services that are able to salvage state from a
+ * previous instance of the same class. The intent is to allow new service instances to re-use
+ * resources that would be expensive to re-create, such as cached data or network connections.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface SalvageableService {
+
+ /**
+ * Re-uses state from $other. $other must not be used after being passed to salvage(),
+ * and should be considered to be destroyed.
+ *
+ * @note Implementations are responsible for determining what parts of $other can be re-used
+ * safely. In particular, implementations should check that the relevant configuration of
+ * $other is the same as in $this before re-using resources from $other.
+ *
+ * @note Implementations must take care to detach any re-used resources from the original
+ * service instance. If $other is destroyed later, resources that are now used by the
+ * new service instance must not be affected.
+ *
+ * @note If $other is a DestructibleService, implementations should make sure that $other
+ * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
+ * after carefully detaching all relevant resources.
+ *
+ * @param SalvageableService $other The object to salvage state from. $other must have the
+ * exact same type as $this.
+ */
+ public function salvage( SalvageableService $other );
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ */
+class ServiceAlreadyDefinedException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Service already defined: $serviceName", 0, $previous );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Generic service container.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * ServiceContainer provides a generic service to manage named services using
+ * lazy instantiation based on instantiator callback functions.
+ *
+ * Services managed by an instance of ServiceContainer may or may not implement
+ * a common interface.
+ *
+ * @note When using ServiceContainer to manage a set of services, consider
+ * creating a wrapper or a subclass that provides access to the services via
+ * getter methods with more meaningful names and more specific return type
+ * declarations.
+ *
+ * @see docs/injection.txt for an overview of using dependency injection in the
+ * MediaWiki code base.
+ */
+class ServiceContainer implements DestructibleService {
+
+ /**
+ * @var object[]
+ */
+ private $services = [];
+
+ /**
+ * @var callable[]
+ */
+ private $serviceInstantiators = [];
+
+ /**
+ * @var boolean[] disabled status, per service name
+ */
+ private $disabled = [];
+
+ /**
+ * @var array
+ */
+ private $extraInstantiationParams;
+
+ /**
+ * @var boolean
+ */
+ private $destroyed = false;
+
+ /**
+ * @param array $extraInstantiationParams Any additional parameters to be passed to the
+ * instantiator function when creating a service. This is typically used to provide
+ * access to additional ServiceContainers or Config objects.
+ */
+ public function __construct( array $extraInstantiationParams = [] ) {
+ $this->extraInstantiationParams = $extraInstantiationParams;
+ }
+
+ /**
+ * Destroys all contained service instances that implement the DestructibleService
+ * interface. This will render all services obtained from this MediaWikiServices
+ * instance unusable. In particular, this will disable access to the storage backend
+ * via any of these services. Any future call to getService() will throw an exception.
+ *
+ * @see resetGlobalInstance()
+ */
+ public function destroy() {
+ foreach ( $this->getServiceNames() as $name ) {
+ $service = $this->peekService( $name );
+ if ( $service !== null && $service instanceof DestructibleService ) {
+ $service->destroy();
+ }
+ }
+
+ $this->destroyed = true;
+ }
+
+ /**
+ * @param array $wiringFiles A list of PHP files to load wiring information from.
+ * Each file is loaded using PHP's include mechanism. Each file is expected to
+ * return an associative array that maps service names to instantiator functions.
+ */
+ public function loadWiringFiles( array $wiringFiles ) {
+ foreach ( $wiringFiles as $file ) {
+ // the wiring file is required to return an array of instantiators.
+ $wiring = require $file;
+
+ Assert::postcondition(
+ is_array( $wiring ),
+ "Wiring file $file is expected to return an array!"
+ );
+
+ $this->applyWiring( $wiring );
+ }
+ }
+
+ /**
+ * Registers multiple services (aka a "wiring").
+ *
+ * @param array $serviceInstantiators An associative array mapping service names to
+ * instantiator functions.
+ */
+ public function applyWiring( array $serviceInstantiators ) {
+ Assert::parameterElementType( 'callable', $serviceInstantiators, '$serviceInstantiators' );
+
+ foreach ( $serviceInstantiators as $name => $instantiator ) {
+ $this->defineService( $name, $instantiator );
+ }
+ }
+
+ /**
+ * Imports all wiring defined in $container. Wiring defined in $container
+ * will override any wiring already defined locally. However, already
+ * existing service instances will be preserved.
+ *
+ * @since 1.28
+ *
+ * @param ServiceContainer $container
+ * @param string[] $skip A list of service names to skip during import
+ */
+ public function importWiring( ServiceContainer $container, $skip = [] ) {
+ $newInstantiators = array_diff_key(
+ $container->serviceInstantiators,
+ array_flip( $skip )
+ );
+
+ $this->serviceInstantiators = array_merge(
+ $this->serviceInstantiators,
+ $newInstantiators
+ );
+ }
+
+ /**
+ * Returns true if a service is defined for $name, that is, if a call to getService( $name )
+ * would return a service instance.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasService( $name ) {
+ return isset( $this->serviceInstantiators[$name] );
+ }
+
+ /**
+ * Returns the service instance for $name only if that service has already been instantiated.
+ * This is intended for situations where services get destroyed/cleaned up, so we can
+ * avoid creating a service just to destroy it again.
+ *
+ * @note This is intended for internal use and for test fixtures.
+ * Application logic should use getService() instead.
+ *
+ * @see getService().
+ *
+ * @param string $name
+ *
+ * @return object|null The service instance, or null if the service has not yet been instantiated.
+ * @throws RuntimeException if $name does not refer to a known service.
+ */
+ public function peekService( $name ) {
+ if ( !$this->hasService( $name ) ) {
+ throw new NoSuchServiceException( $name );
+ }
+
+ return isset( $this->services[$name] ) ? $this->services[$name] : null;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getServiceNames() {
+ return array_keys( $this->serviceInstantiators );
+ }
+
+ /**
+ * Define a new service. The service must not be known already.
+ *
+ * @see getService().
+ * @see replaceService().
+ *
+ * @param string $name The name of the service to register, for use with getService().
+ * @param callable $instantiator Callback that returns a service instance.
+ * Will be called with this MediaWikiServices instance as the only parameter.
+ * Any extra instantiation parameters provided to the constructor will be
+ * passed as subsequent parameters when invoking the instantiator.
+ *
+ * @throws RuntimeException if there is already a service registered as $name.
+ */
+ public function defineService( $name, callable $instantiator ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ if ( $this->hasService( $name ) ) {
+ throw new ServiceAlreadyDefinedException( $name );
+ }
+
+ $this->serviceInstantiators[$name] = $instantiator;
+ }
+
+ /**
+ * Replace an already defined service.
+ *
+ * @see defineService().
+ *
+ * @note This causes any previously instantiated instance of the service to be discarded.
+ *
+ * @param string $name The name of the service to register.
+ * @param callable $instantiator Callback function that returns a service instance.
+ * Will be called with this MediaWikiServices instance as the only parameter.
+ * The instantiator must return a service compatible with the originally defined service.
+ * Any extra instantiation parameters provided to the constructor will be
+ * passed as subsequent parameters when invoking the instantiator.
+ *
+ * @throws RuntimeException if $name is not a known service.
+ */
+ public function redefineService( $name, callable $instantiator ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ if ( !$this->hasService( $name ) ) {
+ throw new NoSuchServiceException( $name );
+ }
+
+ if ( isset( $this->services[$name] ) ) {
+ throw new CannotReplaceActiveServiceException( $name );
+ }
+
+ $this->serviceInstantiators[$name] = $instantiator;
+ unset( $this->disabled[$name] );
+ }
+
+ /**
+ * Disables a service.
+ *
+ * @note Attempts to call getService() for a disabled service will result
+ * in a DisabledServiceException. Calling peekService for a disabled service will
+ * return null. Disabled services are listed by getServiceNames(). A disabled service
+ * can be enabled again using redefineService().
+ *
+ * @note If the service was already active (that is, instantiated) when getting disabled,
+ * and the service instance implements DestructibleService, destroy() is called on the
+ * service instance.
+ *
+ * @see redefineService()
+ * @see resetService()
+ *
+ * @param string $name The name of the service to disable.
+ *
+ * @throws RuntimeException if $name is not a known service.
+ */
+ public function disableService( $name ) {
+ $this->resetService( $name );
+
+ $this->disabled[$name] = true;
+ }
+
+ /**
+ * Resets a service by dropping the service instance.
+ * If the service instances implements DestructibleService, destroy()
+ * is called on the service instance.
+ *
+ * @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 This is declared final so subclasses can not interfere with the expectations
+ * disableService() has when calling resetService().
+ *
+ * @see redefineService()
+ * @see disableService().
+ *
+ * @param string $name The name of the service to reset.
+ * @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 RuntimeException if $name is not a known service.
+ */
+ final protected function resetService( $name, $destroy = true ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ $instance = $this->peekService( $name );
+
+ if ( $destroy && $instance instanceof DestructibleService ) {
+ $instance->destroy();
+ }
+
+ unset( $this->services[$name] );
+ unset( $this->disabled[$name] );
+ }
+
+ /**
+ * Returns a service object of the kind associated with $name.
+ * Services instances are instantiated lazily, on demand.
+ * This method may or may not return the same service instance
+ * when called multiple times with the same $name.
+ *
+ * @note Rather than calling this method directly, it is recommended to provide
+ * getters with more meaningful names and more specific return types, using
+ * a subclass or wrapper.
+ *
+ * @see redefineService().
+ *
+ * @param string $name The service name
+ *
+ * @throws NoSuchServiceException if $name is not a known service.
+ * @throws ContainerDisabledException if this container has already been destroyed.
+ * @throws ServiceDisabledException if the requested service has been disabled.
+ *
+ * @return object The service instance
+ */
+ public function getService( $name ) {
+ if ( $this->destroyed ) {
+ throw new ContainerDisabledException();
+ }
+
+ if ( isset( $this->disabled[$name] ) ) {
+ throw new ServiceDisabledException( $name );
+ }
+
+ if ( !isset( $this->services[$name] ) ) {
+ $this->services[$name] = $this->createService( $name );
+ }
+
+ return $this->services[$name];
+ }
+
+ /**
+ * @param string $name
+ *
+ * @throws InvalidArgumentException if $name is not a known service.
+ * @return object
+ */
+ private function createService( $name ) {
+ if ( isset( $this->serviceInstantiators[$name] ) ) {
+ $service = call_user_func_array(
+ $this->serviceInstantiators[$name],
+ array_merge( [ $this ], $this->extraInstantiationParams )
+ );
+ // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
+ } else {
+ throw new NoSuchServiceException( $name );
+ }
+
+ return $service;
+ }
+
+ /**
+ * @param string $name
+ * @return bool Whether the service is disabled
+ * @since 1.28
+ */
+ public function isServiceDisabled( $name ) {
+ return isset( $this->disabled[$name] );
+ }
+}
--- /dev/null
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ */
+class ServiceDisabledException extends RuntimeException {
+
+ /**
+ * @param string $serviceName
+ * @param Exception|null $previous
+ */
+ public function __construct( $serviceName, Exception $previous = null ) {
+ parent::__construct( "Service disabled: $serviceName", 0, $previous );
+ }
+
+}