namespace Wikimedia\Services;
use InvalidArgumentException;
+use Psr\Container\ContainerInterface;
use RuntimeException;
use Wikimedia\Assert\Assert;
+use Wikimedia\ScopedCallback;
/**
* Generic service container.
* @see docs/injection.txt for an overview of using dependency injection in the
* MediaWiki code base.
*/
-class ServiceContainer implements DestructibleService {
+class ServiceContainer implements ContainerInterface, DestructibleService {
/**
* @var object[]
*/
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
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
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 RuntimeException( "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
}
}
+ $removeFromStack->consume();
+
// NOTE: when adding more wiring logic here, make sure importWiring() is kept in sync!
} else {
throw new NoSuchServiceException( $name );