Container.php in Little helpers 7.2
Namespace
Drupal\little_helpers\ServicesFile
src/Services/Container.phpView source
<?php
namespace Drupal\little_helpers\Services;
/**
* Dependency injection container.
*
* The main purpose of this class is to serve as a dependency injection
* container and service registry. It also provides a method for instantiating
* classes based on DI specs, which is useful for plugin mechanisms.
*/
class Container {
/**
* Service specifications.
*
* @var array
*/
protected $specs = [];
/**
* Cached instances of services.
*
* @var array
*/
protected $instances = [];
/**
* Container used to resolve service references in specs.
*
* @var \Drupal\little_helpers\Services\Container
*/
protected $container = NULL;
/**
* Default values to merge into specs.
*
* @var array
*/
protected $defaults = [];
/**
* Create or get the singleton container instance.
*/
public static function get() {
$instance =& drupal_static(__CLASS__);
if (!$instance) {
$instance = new static();
$instance
->loadSpecsFromHook('little_helpers_services');
}
return $instance;
}
/**
* Create a new loader instance.
*
* @param string $name
* The service name of the container itself. (default: 'container').
*/
public function __construct(string $name = 'container') {
$this->instances[$name] = $this;
}
/**
* Load a (possibly cached) service by name.
*
* @param string $name
* Name of the service to load.
* @param bool $exception
* Whether to throw an exception if the service can’t be loaded. If FALSE
* then a boolean FALSE will be returned instead.
*/
public function loadService(string $name, bool $exception = TRUE) {
if ($service = $this->instances[$name] ?? NULL) {
return $service;
}
if ($spec = $this
->getSpec($name, $exception)) {
return $this->instances[$name] = $spec
->instantiate();
}
return FALSE;
}
/**
* Load specs by invoking a hook.
*
* @param string $hook
* Name of the hook to invoke.
* @param mixed ...$arguments
* Arguments that should be passed to the hook invocations.
*/
public function loadSpecsFromHook(string $hook, ...$arguments) {
$info = module_invoke_all($hook, ...$arguments);
$specs = $this
->processInfo($info);
drupal_alter($hook, $specs, ...$arguments);
$this->specs = array_replace($this->specs, $specs);
}
/**
* Turn hook info into specs.
*
* @param array $info
* Result of a hook invocation.
*
* @return array
* The processed specs.
*/
protected function processInfo(array $info) {
foreach ($info as &$spec) {
if (!is_array($spec)) {
$spec = [
'class' => $spec,
];
}
$spec = $this
->process($spec);
}
return $info;
}
/**
* Additional processing for a single spec (ie. add defaults).
*
* @param array $spec
* The spec to add default to.
*
* @return array
* The modified spec.
*/
protected function process(array $spec) {
return $spec + $this->defaults;
}
/**
* Set the container used to resolve service references in specs.
*
* @param \Drupal\little_helper\Services\Container
* The container instance to set.
*/
public function setContainer(Container $container) {
$this->container = $container;
}
/**
* Add specs to the registry.
*
* Existing specs with the same name are overridden.
*
* @param array $specs
* The specs to add.
*/
public function setSpecs(array $specs) {
$this->specs = array_replace($this->specs, $this
->processInfo($specs));
}
/**
* Set the spec defaults.
*
* @param array $defaults
* Default values to merge into all specs.
*/
public function setDefaults(array $defaults) {
$this->defaults = $defaults;
}
/**
* Get a spec to for creating a new instance of the referenced class.
*
* @param string $name
* Name of the spec to be loaded.
* @param bool $exception
* Whether to throw an exception if no spec with the name exists. If FALSE
* then a boolean FALSE will be returned instead.
*
* @return \Drupal\little_helpers\Services\Spec
* The registered spec for the $name.
*/
public function getSpec(string $name, bool $exception = TRUE) {
if ($spec = $this->specs[$name] ?? NULL) {
$spec = Spec::fromInfo($spec);
$spec
->setContainer($this->container ?? $this);
return $spec;
}
if ($exception) {
throw new UnknownServiceException("Unknown service: {$name}");
}
return FALSE;
}
/**
* Manually register an object as a service.
*
* This is mainly supposed to be used for testing.
*/
public function inject($name, $instance) {
$this->instances[$name] = $instance;
}
}