Use wikimedia/services 2.0.1
wikimedia/services is a near identical copy of includes/libs/services. Bug: T211608 Depends-On: Iefde0001648bf665d94f115ff1d83b57d13936a6 Change-Id: I3f004d32682be5d732efb36061733494c0779bac
This commit is contained in:
parent
9e6f262b28
commit
f6603734d2
14 changed files with 2 additions and 1412 deletions
|
|
@ -252,6 +252,7 @@ For notes on 1.34.x and older releases, see HISTORY.
|
|||
==== New external libraries ====
|
||||
* Added wikimedia/ip-utils 1.0.0.
|
||||
* Added wikimedia/parsoid 0.12.0.
|
||||
* Added wikimedia/services 2.0.1.
|
||||
* Added taylorhakes/promise-polyfill v8.1.3.
|
||||
* Added vuejs v2.6.11.
|
||||
* Added vuex v3.1.3.
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
"wikimedia/remex-html": "2.2.0",
|
||||
"wikimedia/running-stat": "1.2.1",
|
||||
"wikimedia/scoped-callback": "3.0.0",
|
||||
"wikimedia/services": "2.0.1",
|
||||
"wikimedia/utfnormal": "2.0.0",
|
||||
"wikimedia/timestamp": "3.0.0",
|
||||
"wikimedia/wait-condition-loop": "1.0.1",
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
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
|
||||
implements ContainerExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
* @param Exception|null $previous
|
||||
*/
|
||||
public function __construct( $serviceName, Exception $previous = null ) {
|
||||
parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
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
|
||||
implements ContainerExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param Exception|null $previous
|
||||
*/
|
||||
public function __construct( Exception $previous = null ) {
|
||||
parent::__construct( 'Container disabled!', 0, $previous );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\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();
|
||||
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
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
|
||||
implements NotFoundExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
* @param Exception|null $previous
|
||||
*/
|
||||
public function __construct( $serviceName, Exception $previous = null ) {
|
||||
parent::__construct( "No such service: $serviceName", 0, $previous );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown when trying to instantiate a currently instantiating service.
|
||||
*
|
||||
* @since 1.35
|
||||
*/
|
||||
class RecursiveServiceDependencyException extends RuntimeException
|
||||
implements ContainerExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
*/
|
||||
public function __construct( $serviceName ) {
|
||||
parent::__construct( "Recursive service instantiation: $serviceName" );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\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 );
|
||||
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
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
|
||||
implements ContainerExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
* @param Exception|null $previous
|
||||
*/
|
||||
public function __construct( $serviceName, Exception $previous = null ) {
|
||||
parent::__construct( "Service already defined: $serviceName", 0, $previous );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,488 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RuntimeException;
|
||||
use Wikimedia\Assert\Assert;
|
||||
use Wikimedia\ScopedCallback;
|
||||
|
||||
/**
|
||||
* 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.md for an overview of using dependency injection in the
|
||||
* MediaWiki code base.
|
||||
*/
|
||||
class ServiceContainer implements ContainerInterface, DestructibleService {
|
||||
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
private $services = [];
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
private $serviceInstantiators = [];
|
||||
|
||||
/**
|
||||
* @var callable[][]
|
||||
*/
|
||||
private $serviceManipulators = [];
|
||||
|
||||
/**
|
||||
* @var bool[] disabled status, per service name
|
||||
*/
|
||||
private $disabled = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $extraInstantiationParams;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $destroyed = false;
|
||||
|
||||
/**
|
||||
* @var array Set of services currently being created, to detect loops
|
||||
*/
|
||||
private $servicesBeingCreated = [];
|
||||
|
||||
/**
|
||||
* @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 ServiceContainer
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Break circular references due to the $this reference in closures, by
|
||||
// erasing the instantiator array. This allows the ServiceContainer to
|
||||
// be deleted when it goes out of scope.
|
||||
$this->serviceInstantiators = [];
|
||||
// Also remove the services themselves, to avoid confusion.
|
||||
$this->services = [];
|
||||
$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
|
||||
);
|
||||
|
||||
$newManipulators = array_diff(
|
||||
array_keys( $container->serviceManipulators ),
|
||||
$skip
|
||||
);
|
||||
|
||||
foreach ( $newManipulators as $name ) {
|
||||
if ( isset( $this->serviceManipulators[$name] ) ) {
|
||||
$this->serviceManipulators[$name] = array_merge(
|
||||
$this->serviceManipulators[$name],
|
||||
$container->serviceManipulators[$name]
|
||||
);
|
||||
} else {
|
||||
$this->serviceManipulators[$name] = $container->serviceManipulators[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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] );
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function has( $name ) {
|
||||
return $this->hasService( $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 $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 redefineService().
|
||||
*
|
||||
* @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 ServiceContainer 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( string $name, callable $instantiator ) {
|
||||
if ( $this->hasService( $name ) ) {
|
||||
throw new ServiceAlreadyDefinedException( $name );
|
||||
}
|
||||
|
||||
$this->serviceInstantiators[$name] = $instantiator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an already defined service.
|
||||
*
|
||||
* @see defineService().
|
||||
*
|
||||
* @note This will fail if the service was already instantiated. If the service was previously
|
||||
* disabled, it will be re-enabled by this call. Any manipulators registered for the service
|
||||
* will remain in place.
|
||||
*
|
||||
* @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 ServiceContainer 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 NoSuchServiceException if $name is not a known service.
|
||||
* @throws CannotReplaceActiveServiceException if the service was already instantiated.
|
||||
*/
|
||||
public function redefineService( string $name, callable $instantiator ) {
|
||||
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] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a service manipulator callback for the given service.
|
||||
* This method may be used by extensions that need to wrap, replace, or re-configure a
|
||||
* service. It would typically be called from a MediaWikiServices hook handler.
|
||||
*
|
||||
* The manipulator callback is called just after the service is instantiated.
|
||||
* It can call methods on the service to change configuration, or wrap or otherwise
|
||||
* replace it.
|
||||
*
|
||||
* @see defineService().
|
||||
* @see redefineService().
|
||||
*
|
||||
* @note This will fail if the service was already instantiated.
|
||||
*
|
||||
* @since 1.32
|
||||
*
|
||||
* @param string $name The name of the service to manipulate.
|
||||
* @param callable $manipulator Callback function that manipulates, wraps or replaces a
|
||||
* service instance. The callback receives the new service instance and this
|
||||
* ServiceContainer as parameters, as well as any extra instantiation parameters specified
|
||||
* when constructing this ServiceContainer. If the callback returns a value, that
|
||||
* value replaces the original service instance.
|
||||
*
|
||||
* @throws NoSuchServiceException if $name is not a known service.
|
||||
* @throws CannotReplaceActiveServiceException if the service was already instantiated.
|
||||
*/
|
||||
public function addServiceManipulator( string $name, callable $manipulator ) {
|
||||
if ( !$this->hasService( $name ) ) {
|
||||
throw new NoSuchServiceException( $name );
|
||||
}
|
||||
|
||||
if ( isset( $this->services[$name] ) ) {
|
||||
throw new CannotReplaceActiveServiceException( $name );
|
||||
}
|
||||
|
||||
$this->serviceManipulators[$name][] = $manipulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( string $name, $destroy = true ) {
|
||||
$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 mixed 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];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function get( $name ) {
|
||||
return $this->getService( $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @throws InvalidArgumentException if $name is not a known service.
|
||||
* @throws RuntimeException if a circular dependency is detected.
|
||||
* @return object
|
||||
*/
|
||||
private function createService( $name ) {
|
||||
if ( isset( $this->serviceInstantiators[$name] ) ) {
|
||||
if ( isset( $this->servicesBeingCreated[$name] ) ) {
|
||||
throw new RecursiveServiceDependencyException(
|
||||
"Circular dependency when creating service! " .
|
||||
implode( ' -> ', array_keys( $this->servicesBeingCreated ) ) . " -> $name" );
|
||||
}
|
||||
$this->servicesBeingCreated[$name] = true;
|
||||
$removeFromStack = new ScopedCallback( function () use ( $name ) {
|
||||
unset( $this->servicesBeingCreated[$name] );
|
||||
} );
|
||||
|
||||
$service = ( $this->serviceInstantiators[$name] )(
|
||||
$this,
|
||||
...$this->extraInstantiationParams
|
||||
);
|
||||
|
||||
if ( isset( $this->serviceManipulators[$name] ) ) {
|
||||
foreach ( $this->serviceManipulators[$name] as $callback ) {
|
||||
$ret = call_user_func_array(
|
||||
$callback,
|
||||
array_merge( [ $service, $this ], $this->extraInstantiationParams )
|
||||
);
|
||||
|
||||
// If the manipulator callback returns an object, that object replaces
|
||||
// the original service instance. This allows the manipulator to wrap
|
||||
// or fully replace the service.
|
||||
if ( $ret !== null ) {
|
||||
$service = $ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$removeFromStack->consume();
|
||||
|
||||
// NOTE: when adding more wiring logic here, make sure importWiring() 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] );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Services;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
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
|
||||
implements ContainerExceptionInterface {
|
||||
|
||||
/**
|
||||
* @param string $serviceName
|
||||
* @param Exception|null $previous
|
||||
*/
|
||||
public function __construct( $serviceName, Exception $previous = null ) {
|
||||
parent::__construct( "Service disabled: $serviceName", 0, $previous );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,524 +0,0 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Wikimedia\Services\RecursiveServiceDependencyException;
|
||||
use Wikimedia\Services\ServiceContainer;
|
||||
|
||||
/**
|
||||
* @covers Wikimedia\Services\ServiceContainer
|
||||
*/
|
||||
class ServiceContainerTest extends PHPUnit\Framework\TestCase {
|
||||
|
||||
use MediaWikiCoversValidator; // TODO this library is supposed to be independent of MediaWiki
|
||||
|
||||
private function newServiceContainer( $extraArgs = [] ) {
|
||||
return new ServiceContainer( $extraArgs );
|
||||
}
|
||||
|
||||
public function testGetServiceNames() {
|
||||
$services = $this->newServiceContainer();
|
||||
$names = $services->getServiceNames();
|
||||
|
||||
$this->assertSame( [], $names );
|
||||
|
||||
$name = 'TestService92834576';
|
||||
$services->defineService( $name, function () {
|
||||
return null;
|
||||
} );
|
||||
|
||||
$names = $services->getServiceNames();
|
||||
$this->assertContains( $name, $names );
|
||||
}
|
||||
|
||||
public function testHasService() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$name = 'TestService92834576';
|
||||
$this->assertFalse( $services->hasService( $name ) );
|
||||
|
||||
$services->defineService( $name, function () {
|
||||
return null;
|
||||
} );
|
||||
|
||||
$this->assertTrue( $services->hasService( $name ) );
|
||||
}
|
||||
|
||||
public function testGetService() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
$count = 0;
|
||||
|
||||
$services->defineService(
|
||||
$name,
|
||||
function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
|
||||
$count++;
|
||||
Assert::assertSame( $services, $actualLocator );
|
||||
Assert::assertSame( $extra, 'Foo' );
|
||||
return $theService;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertSame( $theService, $services->getService( $name ) );
|
||||
|
||||
$services->getService( $name );
|
||||
$this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
|
||||
}
|
||||
|
||||
public function testGetServiceRecursionCheck() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$services->defineService( 'service1', function ( ServiceContainer $services ) {
|
||||
$services->getService( 'service2' );
|
||||
} );
|
||||
|
||||
$services->defineService( 'service2', function ( ServiceContainer $services ) {
|
||||
$services->getService( 'service3' );
|
||||
} );
|
||||
|
||||
$services->defineService( 'service3', function ( ServiceContainer $services ) {
|
||||
$services->getService( 'service1' );
|
||||
} );
|
||||
|
||||
$exceptionThrown = false;
|
||||
try {
|
||||
$services->getService( 'service1' );
|
||||
} catch ( RecursiveServiceDependencyException $e ) {
|
||||
$exceptionThrown = true;
|
||||
$this->assertSame(
|
||||
'Recursive service instantiation: ' .
|
||||
'Circular dependency when creating service! ' .
|
||||
'service1 -> service2 -> service3 -> service1', $e->getMessage() );
|
||||
}
|
||||
$this->assertTrue( $exceptionThrown, 'RuntimeException must be thrown' );
|
||||
}
|
||||
|
||||
public function testGetService_fail_unknown() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$this->expectException( Wikimedia\Services\NoSuchServiceException::class );
|
||||
|
||||
$services->getService( $name );
|
||||
}
|
||||
|
||||
public function testPeekService() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$services->defineService(
|
||||
'Foo',
|
||||
function () {
|
||||
return (object)[];
|
||||
}
|
||||
);
|
||||
|
||||
$services->defineService(
|
||||
'Bar',
|
||||
function () {
|
||||
return (object)[];
|
||||
}
|
||||
);
|
||||
|
||||
// trigger instantiation of Foo
|
||||
$services->getService( 'Foo' );
|
||||
|
||||
$this->assertIsObject(
|
||||
$services->peekService( 'Foo' ),
|
||||
'Peek should return the service object if it had been accessed before.'
|
||||
);
|
||||
|
||||
$this->assertNull(
|
||||
$services->peekService( 'Bar' ),
|
||||
'Peek should return null if the service was never accessed.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testPeekService_fail_unknown() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$this->expectException( Wikimedia\Services\NoSuchServiceException::class );
|
||||
|
||||
$services->peekService( $name );
|
||||
}
|
||||
|
||||
public function testDefineService() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) {
|
||||
Assert::assertSame( $services, $actualLocator );
|
||||
return $theService;
|
||||
} );
|
||||
|
||||
$this->assertTrue( $services->hasService( $name ) );
|
||||
$this->assertSame( $theService, $services->getService( $name ) );
|
||||
}
|
||||
|
||||
public function testDefineService_fail_duplicate() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
|
||||
$this->expectException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
|
||||
|
||||
$services->defineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
}
|
||||
|
||||
public function testApplyWiring() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$wiring = [
|
||||
'Foo' => function () {
|
||||
return 'Foo!';
|
||||
},
|
||||
'Bar' => function () {
|
||||
return 'Bar!';
|
||||
},
|
||||
];
|
||||
|
||||
$services->applyWiring( $wiring );
|
||||
|
||||
$this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
|
||||
$this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
|
||||
}
|
||||
|
||||
public function testImportWiring() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$wiring = [
|
||||
'Foo' => function () {
|
||||
return 'Foo!';
|
||||
},
|
||||
'Bar' => function () {
|
||||
return 'Bar!';
|
||||
},
|
||||
'Car' => function () {
|
||||
return 'FUBAR!';
|
||||
},
|
||||
];
|
||||
|
||||
$services->applyWiring( $wiring );
|
||||
|
||||
$services->addServiceManipulator( 'Foo', function ( $service ) {
|
||||
return $service . '+X';
|
||||
} );
|
||||
|
||||
$services->addServiceManipulator( 'Car', function ( $service ) {
|
||||
return $service . '+X';
|
||||
} );
|
||||
|
||||
$newServices = $this->newServiceContainer();
|
||||
|
||||
// create a service with manipulator
|
||||
$newServices->defineService( 'Foo', function () {
|
||||
return 'Foo!';
|
||||
} );
|
||||
|
||||
$newServices->addServiceManipulator( 'Foo', function ( $service ) {
|
||||
return $service . '+Y';
|
||||
} );
|
||||
|
||||
// create a service before importing, so we can later check that
|
||||
// existing service instances survive importWiring()
|
||||
$newServices->defineService( 'Car', function () {
|
||||
return 'Car!';
|
||||
} );
|
||||
|
||||
// force instantiation
|
||||
$newServices->getService( 'Car' );
|
||||
|
||||
// Define another service, so we can later check that extra wiring
|
||||
// is not lost.
|
||||
$newServices->defineService( 'Xar', function () {
|
||||
return 'Xar!';
|
||||
} );
|
||||
|
||||
// import wiring, but skip `Bar`
|
||||
$newServices->importWiring( $services, [ 'Bar' ] );
|
||||
|
||||
$this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
|
||||
$this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
|
||||
|
||||
// import all wiring, but preserve existing service instance
|
||||
$newServices->importWiring( $services );
|
||||
|
||||
$this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
|
||||
$this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
|
||||
$this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
|
||||
$this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
|
||||
}
|
||||
|
||||
public function testLoadWiringFiles() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$wiringFiles = [
|
||||
__DIR__ . '/TestWiring1.php',
|
||||
__DIR__ . '/TestWiring2.php',
|
||||
];
|
||||
|
||||
$services->loadWiringFiles( $wiringFiles );
|
||||
|
||||
$this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
|
||||
$this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
|
||||
}
|
||||
|
||||
public function testLoadWiringFiles_fail_duplicate() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$wiringFiles = [
|
||||
__DIR__ . '/TestWiring1.php',
|
||||
__DIR__ . '/./TestWiring1.php',
|
||||
];
|
||||
|
||||
// loading the same file twice should fail, because
|
||||
$this->expectException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
|
||||
|
||||
$services->loadWiringFiles( $wiringFiles );
|
||||
}
|
||||
|
||||
public function testRedefineService() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService1 = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function () {
|
||||
Assert::fail(
|
||||
'The original instantiator function should not get called'
|
||||
);
|
||||
} );
|
||||
|
||||
// redefine before instantiation
|
||||
$services->redefineService(
|
||||
$name,
|
||||
function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
|
||||
Assert::assertSame( $services, $actualLocator );
|
||||
Assert::assertSame( 'Foo', $extra );
|
||||
return $theService1;
|
||||
}
|
||||
);
|
||||
|
||||
// force instantiation, check result
|
||||
$this->assertSame( $theService1, $services->getService( $name ) );
|
||||
}
|
||||
|
||||
public function testRedefineService_disabled() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService1 = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function () {
|
||||
return 'Foo';
|
||||
} );
|
||||
|
||||
// disable the service. we should be able to redefine it anyway.
|
||||
$services->disableService( $name );
|
||||
|
||||
$services->redefineService( $name, function () use ( $theService1 ) {
|
||||
return $theService1;
|
||||
} );
|
||||
|
||||
// force instantiation, check result
|
||||
$this->assertSame( $theService1, $services->getService( $name ) );
|
||||
}
|
||||
|
||||
public function testRedefineService_fail_undefined() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$this->expectException( Wikimedia\Services\NoSuchServiceException::class );
|
||||
|
||||
$services->redefineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
}
|
||||
|
||||
public function testRedefineService_fail_in_use() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function () {
|
||||
return 'Foo';
|
||||
} );
|
||||
|
||||
// create the service, so it can no longer be redefined
|
||||
$services->getService( $name );
|
||||
|
||||
$this->expectException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
|
||||
|
||||
$services->redefineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
}
|
||||
|
||||
public function testAddServiceManipulator() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService1 = (object)[];
|
||||
$theService2 = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService(
|
||||
$name,
|
||||
function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
|
||||
Assert::assertSame( $services, $actualLocator );
|
||||
Assert::assertSame( 'Foo', $extra );
|
||||
return $theService1;
|
||||
}
|
||||
);
|
||||
|
||||
$services->addServiceManipulator(
|
||||
$name,
|
||||
function (
|
||||
$theService, $actualLocator, $extra
|
||||
) use (
|
||||
$services, $theService1, $theService2
|
||||
) {
|
||||
Assert::assertSame( $theService1, $theService );
|
||||
Assert::assertSame( $services, $actualLocator );
|
||||
Assert::assertSame( 'Foo', $extra );
|
||||
return $theService2;
|
||||
}
|
||||
);
|
||||
|
||||
// force instantiation, check result
|
||||
$this->assertSame( $theService2, $services->getService( $name ) );
|
||||
}
|
||||
|
||||
public function testAddServiceManipulator_fail_undefined() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$this->expectException( Wikimedia\Services\NoSuchServiceException::class );
|
||||
|
||||
$services->addServiceManipulator( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
}
|
||||
|
||||
public function testAddServiceManipulator_fail_in_use() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$services->defineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
|
||||
// create the service, so it can no longer be redefined
|
||||
$services->getService( $name );
|
||||
|
||||
$this->expectException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
|
||||
|
||||
$services->addServiceManipulator( $name, function () {
|
||||
return 'Foo';
|
||||
} );
|
||||
}
|
||||
|
||||
public function testDisableService() {
|
||||
$services = $this->newServiceContainer( [ 'Foo' ] );
|
||||
|
||||
$destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
|
||||
->getMock();
|
||||
$destructible->expects( $this->once() )
|
||||
->method( 'destroy' );
|
||||
|
||||
$services->defineService( 'Foo', function () use ( $destructible ) {
|
||||
return $destructible;
|
||||
} );
|
||||
$services->defineService( 'Bar', function () {
|
||||
return (object)[];
|
||||
} );
|
||||
$services->defineService( 'Qux', function () {
|
||||
return (object)[];
|
||||
} );
|
||||
|
||||
// instantiate Foo and Bar services
|
||||
$services->getService( 'Foo' );
|
||||
$services->getService( 'Bar' );
|
||||
|
||||
// disable service, should call destroy() once.
|
||||
$services->disableService( 'Foo' );
|
||||
|
||||
// disabled service should still be listed
|
||||
$this->assertContains( 'Foo', $services->getServiceNames() );
|
||||
|
||||
// getting other services should still work
|
||||
$services->getService( 'Bar' );
|
||||
|
||||
// disable non-destructible service, and not-yet-instantiated service
|
||||
$services->disableService( 'Bar' );
|
||||
$services->disableService( 'Qux' );
|
||||
|
||||
$this->assertNull( $services->peekService( 'Bar' ) );
|
||||
$this->assertNull( $services->peekService( 'Qux' ) );
|
||||
|
||||
// disabled service should still be listed
|
||||
$this->assertContains( 'Bar', $services->getServiceNames() );
|
||||
$this->assertContains( 'Qux', $services->getServiceNames() );
|
||||
|
||||
$this->expectException( Wikimedia\Services\ServiceDisabledException::class );
|
||||
$services->getService( 'Qux' );
|
||||
}
|
||||
|
||||
public function testDisableService_fail_undefined() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$theService = (object)[];
|
||||
$name = 'TestService92834576';
|
||||
|
||||
$this->expectException( Wikimedia\Services\NoSuchServiceException::class );
|
||||
|
||||
$services->redefineService( $name, function () use ( $theService ) {
|
||||
return $theService;
|
||||
} );
|
||||
}
|
||||
|
||||
public function testDestroy() {
|
||||
$services = $this->newServiceContainer();
|
||||
|
||||
$destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
|
||||
->getMock();
|
||||
$destructible->expects( $this->once() )
|
||||
->method( 'destroy' );
|
||||
|
||||
$services->defineService( 'Foo', function () use ( $destructible ) {
|
||||
return $destructible;
|
||||
} );
|
||||
|
||||
$services->defineService( 'Bar', function () {
|
||||
return (object)[];
|
||||
} );
|
||||
|
||||
// create the service
|
||||
$services->getService( 'Foo' );
|
||||
|
||||
// destroy the container
|
||||
$services->destroy();
|
||||
|
||||
$this->expectException( Wikimedia\Services\ContainerDisabledException::class );
|
||||
$services->getService( 'Bar' );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for testing ServiceContainer::loadWiringFiles
|
||||
*/
|
||||
|
||||
return [
|
||||
'Foo' => function () {
|
||||
return 'Foo!';
|
||||
},
|
||||
];
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Test file for testing ServiceContainer::loadWiringFiles
|
||||
*/
|
||||
|
||||
return [
|
||||
'Bar' => function () {
|
||||
return 'Bar!';
|
||||
},
|
||||
];
|
||||
Loading…
Reference in a new issue