Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
94.74% |
18 / 19 |
CRAP | |
95.24% |
180 / 189 |
Container | |
0.00% |
0 / 1 |
|
94.74% |
18 / 19 |
104 | |
95.24% |
180 / 189 |
__construct | |
100.00% |
1 / 1 |
8 | |
100.00% |
8 / 8 |
|||
get | |
100.00% |
1 / 1 |
15 | |
100.00% |
26 / 26 |
|||
createService | |
0.00% |
0 / 1 |
38.95 | |
88.75% |
71 / 80 |
|||
set | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
has | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
getParameter | |
100.00% |
1 / 1 |
4 | |
100.00% |
5 / 5 |
|||
hasParameter | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setParameter | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
initialized | |
100.00% |
1 / 1 |
3 | |
100.00% |
3 / 3 |
|||
resolveServicesAndParameters | |
100.00% |
1 / 1 |
18 | |
100.00% |
44 / 44 |
|||
getAlternatives | |
100.00% |
1 / 1 |
4 | |
100.00% |
6 / 6 |
|||
getServiceAlternatives | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getParameterAlternatives | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
enterScope | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
leaveScope | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
addScope | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
hasScope | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isScopeActive | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getServiceIds | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Component\DependencyInjection\Container. | |
*/ | |
namespace Drupal\Component\DependencyInjection; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface; | |
use Symfony\Component\DependencyInjection\ScopeInterface; | |
use Symfony\Component\DependencyInjection\Exception\LogicException; | |
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; | |
use Symfony\Component\DependencyInjection\Exception\RuntimeException; | |
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; | |
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; | |
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; | |
/** | |
* Provides a container optimized for Drupal's needs. | |
* | |
* This container implementation is compatible with the default Symfony | |
* dependency injection container and similar to the Symfony ContainerBuilder | |
* class, but optimized for speed. | |
* | |
* It is based on a PHP array container definition dumped as a | |
* performance-optimized machine-readable format. | |
* | |
* The best way to initialize this container is to use a Container Builder, | |
* compile it and then retrieve the definition via | |
* \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getArray(). | |
* | |
* The retrieved array can be cached safely and then passed to this container | |
* via the constructor. | |
* | |
* As the container is unfrozen by default, a second parameter can be passed to | |
* the container to "freeze" the parameter bag. | |
* | |
* This container is different in behavior from the default Symfony container in | |
* the following ways: | |
* | |
* - It only allows lowercase service and parameter names, though it does only | |
* enforce it via assertions for performance reasons. | |
* - The following functions, that are not part of the interface, are explicitly | |
* not supported: getParameterBag(), isFrozen(), compile(), | |
* getAServiceWithAnIdByCamelCase(). | |
* - The function getServiceIds() was added as it has a use-case in core and | |
* contrib. | |
* - Scopes are explicitly not allowed, because Symfony 2.8 has deprecated | |
* them and they will be removed in Symfony 3.0. | |
* - Synchronized services are explicitly not supported, because Symfony 2.8 has | |
* deprecated them and they will be removed in Symfony 3.0. | |
* | |
* @ingroup container | |
*/ | |
class Container implements IntrospectableContainerInterface { | |
/** | |
* The parameters of the container. | |
* | |
* @var array | |
*/ | |
protected $parameters = array(); | |
/** | |
* The aliases of the container. | |
* | |
* @var array | |
*/ | |
protected $aliases = array(); | |
/** | |
* The service definitions of the container. | |
* | |
* @var array | |
*/ | |
protected $serviceDefinitions = array(); | |
/** | |
* The instantiated services. | |
* | |
* @var array | |
*/ | |
protected $services = array(); | |
/** | |
* The instantiated private services. | |
* | |
* @var array | |
*/ | |
protected $privateServices = array(); | |
/** | |
* The currently loading services. | |
* | |
* @var array | |
*/ | |
protected $loading = array(); | |
/** | |
* Whether the container parameters can still be changed. | |
* | |
* For testing purposes the container needs to be changed. | |
* | |
* @var bool | |
*/ | |
protected $frozen = TRUE; | |
/** | |
* Constructs a new Container instance. | |
* | |
* @param array $container_definition | |
* An array containing the following keys: | |
* - aliases: The aliases of the container. | |
* - parameters: The parameters of the container. | |
* - services: The service definitions of the container. | |
* - frozen: Whether the container definition came from a frozen | |
* container builder or not. | |
* - machine_format: Whether this container definition uses the optimized | |
* machine-readable container format. | |
*/ | |
public function __construct(array $container_definition = array()) { | |
if (!empty($container_definition) && (!isset($container_definition['machine_format']) || $container_definition['machine_format'] !== TRUE)) { | |
throw new InvalidArgumentException('The non-optimized format is not supported by this class. Use an optimized machine-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.'); | |
} | |
$this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array(); | |
$this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array(); | |
$this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array(); | |
$this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE; | |
// Register the service_container with itself. | |
$this->services['service_container'] = $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { | |
if (isset($this->aliases[$id])) { | |
$id = $this->aliases[$id]; | |
} | |
// Re-use shared service instance if it exists. | |
if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) { | |
return $this->services[$id]; | |
} | |
if (isset($this->loading[$id])) { | |
throw new ServiceCircularReferenceException($id, array_keys($this->loading)); | |
} | |
$definition = isset($this->serviceDefinitions[$id]) ? $this->serviceDefinitions[$id] : NULL; | |
if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { | |
if (!$id) { | |
throw new ServiceNotFoundException($id); | |
} | |
throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id)); | |
} | |
// In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE | |
// is used, the actual wanted behavior is to re-try getting the service at a | |
// later point. | |
if (!$definition) { | |
return; | |
} | |
// Definition is a keyed array, so [0] is only defined when it is a | |
// serialized string. | |
if (isset($definition[0])) { | |
$definition = unserialize($definition); | |
} | |
// Now create the service. | |
$this->loading[$id] = TRUE; | |
try { | |
$service = $this->createService($definition, $id); | |
} | |
catch (\Exception $e) { | |
unset($this->loading[$id]); | |
// Remove a potentially shared service that was constructed incompletely. | |
if (array_key_exists($id, $this->services)) { | |
unset($this->services[$id]); | |
} | |
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) { | |
return; | |
} | |
throw $e; | |
} | |
unset($this->loading[$id]); | |
return $service; | |
} | |
/** | |
* Creates a service from a service definition. | |
* | |
* @param array $definition | |
* The service definition to create a service from. | |
* @param string $id | |
* The service identifier, necessary so it can be shared if its public. | |
* | |
* @return object | |
* The service described by the service definition. | |
* | |
* @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException | |
* Thrown when the service is a synthetic service. | |
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException | |
* Thrown when the configurator callable in $definition['configurator'] is | |
* not actually a callable. | |
* @throws \ReflectionException | |
* Thrown when the service class takes more than 10 parameters to construct, | |
* and cannot be instantiated. | |
*/ | |
protected function createService(array $definition, $id) { | |
if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) { | |
throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id)); | |
} | |
$arguments = array(); | |
if (isset($definition['arguments'])) { | |
$arguments = $definition['arguments']; | |
if ($arguments instanceof \stdClass) { | |
$arguments = $this->resolveServicesAndParameters($arguments); | |
} | |
} | |
if (isset($definition['file'])) { | |
$file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file']))); | |
require_once $file; | |
} | |
if (isset($definition['factory'])) { | |
$factory = $definition['factory']; | |
if (is_array($factory)) { | |
$factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1])); | |
} | |
elseif (!is_string($factory)) { | |
throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); | |
} | |
$service = call_user_func_array($factory, $arguments); | |
} | |
else { | |
$class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class']))); | |
$length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments); | |
// Optimize class instantiation for services with up to 10 parameters as | |
// ReflectionClass is noticeably slow. | |
switch ($length) { | |
case 0: | |
$service = new $class(); | |
break; | |
case 1: | |
$service = new $class($arguments[0]); | |
break; | |
case 2: | |
$service = new $class($arguments[0], $arguments[1]); | |
break; | |
case 3: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2]); | |
break; | |
case 4: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]); | |
break; | |
case 5: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]); | |
break; | |
case 6: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]); | |
break; | |
case 7: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]); | |
break; | |
case 8: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]); | |
break; | |
case 9: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]); | |
break; | |
case 10: | |
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]); | |
break; | |
default: | |
$r = new \ReflectionClass($class); | |
$service = $r->newInstanceArgs($arguments); | |
break; | |
} | |
} | |
// Share the service if it is public. | |
if (!isset($definition['public']) || $definition['public'] !== FALSE) { | |
// Forward compatibility fix for Symfony 2.8 update. | |
if (!isset($definition['shared']) || $definition['shared'] !== FALSE) { | |
$this->services[$id] = $service; | |
} | |
} | |
if (isset($definition['calls'])) { | |
foreach ($definition['calls'] as $call) { | |
$method = $call[0]; | |
$arguments = array(); | |
if (!empty($call[1])) { | |
$arguments = $call[1]; | |
if ($arguments instanceof \stdClass) { | |
$arguments = $this->resolveServicesAndParameters($arguments); | |
} | |
} | |
call_user_func_array(array($service, $method), $arguments); | |
} | |
} | |
if (isset($definition['properties'])) { | |
if ($definition['properties'] instanceof \stdClass) { | |
$definition['properties'] = $this->resolveServicesAndParameters($definition['properties']); | |
} | |
foreach ($definition['properties'] as $key => $value) { | |
$service->{$key} = $value; | |
} | |
} | |
if (isset($definition['configurator'])) { | |
$callable = $definition['configurator']; | |
if (is_array($callable)) { | |
$callable = $this->resolveServicesAndParameters($callable); | |
} | |
if (!is_callable($callable)) { | |
throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service))); | |
} | |
call_user_func($callable, $service); | |
} | |
return $service; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) { | |
$this->services[$id] = $service; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function has($id) { | |
return isset($this->services[$id]) || isset($this->serviceDefinitions[$id]); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getParameter($name) { | |
if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) { | |
if (!$name) { | |
throw new ParameterNotFoundException($name); | |
} | |
throw new ParameterNotFoundException($name, NULL, NULL, NULL, $this->getParameterAlternatives($name)); | |
} | |
return $this->parameters[$name]; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function hasParameter($name) { | |
return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function setParameter($name, $value) { | |
if ($this->frozen) { | |
throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); | |
} | |
$this->parameters[$name] = $value; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function initialized($id) { | |
if (isset($this->aliases[$id])) { | |
$id = $this->aliases[$id]; | |
} | |
return isset($this->services[$id]) || array_key_exists($id, $this->services); | |
} | |
/** | |
* Resolves arguments that represent services or variables to the real values. | |
* | |
* @param array|\stdClass $arguments | |
* The arguments to resolve. | |
* | |
* @return array | |
* The resolved arguments. | |
* | |
* @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException | |
* If a parameter/service could not be resolved. | |
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException | |
* If an unknown type is met while resolving parameters and services. | |
*/ | |
protected function resolveServicesAndParameters($arguments) { | |
// Check if this collection needs to be resolved. | |
if ($arguments instanceof \stdClass) { | |
if ($arguments->type !== 'collection') { | |
throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $arguments->type)); | |
} | |
// In case there is nothing to resolve, we are done here. | |
if (!$arguments->resolve) { | |
return $arguments->value; | |
} | |
$arguments = $arguments->value; | |
} | |
// Process the arguments. | |
foreach ($arguments as $key => $argument) { | |
// For this machine-optimized format, only \stdClass arguments are | |
// processed and resolved. All other values are kept as is. | |
if ($argument instanceof \stdClass) { | |
$type = $argument->type; | |
// Check for parameter. | |
if ($type == 'parameter') { | |
$name = $argument->name; | |
if (!isset($this->parameters[$name])) { | |
$arguments[$key] = $this->getParameter($name); | |
// This can never be reached as getParameter() throws an Exception, | |
// because we already checked that the parameter is not set above. | |
} | |
// Update argument. | |
$argument = $arguments[$key] = $this->parameters[$name]; | |
// In case there is not a machine readable value (e.g. a service) | |
// behind this resolved parameter, continue. | |
if (!($argument instanceof \stdClass)) { | |
continue; | |
} | |
// Fall through. | |
$type = $argument->type; | |
} | |
// Create a service. | |
if ($type == 'service') { | |
$id = $argument->id; | |
// Does the service already exist? | |
if (isset($this->aliases[$id])) { | |
$id = $this->aliases[$id]; | |
} | |
if (isset($this->services[$id])) { | |
$arguments[$key] = $this->services[$id]; | |
continue; | |
} | |
// Return the service. | |
$arguments[$key] = $this->get($id, $argument->invalidBehavior); | |
continue; | |
} | |
// Create private service. | |
elseif ($type == 'private_service') { | |
$id = $argument->id; | |
// Does the private service already exist. | |
if (isset($this->privateServices[$id])) { | |
$arguments[$key] = $this->privateServices[$id]; | |
continue; | |
} | |
// Create the private service. | |
$arguments[$key] = $this->createService($argument->value, $id); | |
if ($argument->shared) { | |
$this->privateServices[$id] = $arguments[$key]; | |
} | |
continue; | |
} | |
// Check for collection. | |
elseif ($type == 'collection') { | |
$value = $argument->value; | |
// Does this collection need resolving? | |
if ($argument->resolve) { | |
$arguments[$key] = $this->resolveServicesAndParameters($value); | |
} | |
else { | |
$arguments[$key] = $value; | |
} | |
continue; | |
} | |
if ($type !== NULL) { | |
throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $type)); | |
} | |
} | |
} | |
return $arguments; | |
} | |
/** | |
* Provides alternatives for a given array and key. | |
* | |
* @param string $search_key | |
* The search key to get alternatives for. | |
* @param array $keys | |
* The search space to search for alternatives in. | |
* | |
* @return string[] | |
* An array of strings with suitable alternatives. | |
*/ | |
protected function getAlternatives($search_key, array $keys) { | |
$alternatives = array(); | |
foreach ($keys as $key) { | |
$lev = levenshtein($search_key, $key); | |
if ($lev <= strlen($search_key) / 3 || strpos($key, $search_key) !== FALSE) { | |
$alternatives[] = $key; | |
} | |
} | |
return $alternatives; | |
} | |
/** | |
* Provides alternatives in case a service was not found. | |
* | |
* @param string $id | |
* The service to get alternatives for. | |
* | |
* @return string[] | |
* An array of strings with suitable alternatives. | |
*/ | |
protected function getServiceAlternatives($id) { | |
$all_service_keys = array_unique(array_merge(array_keys($this->services), array_keys($this->serviceDefinitions))); | |
return $this->getAlternatives($id, $all_service_keys); | |
} | |
/** | |
* Provides alternatives in case a parameter was not found. | |
* | |
* @param string $name | |
* The parameter to get alternatives for. | |
* | |
* @return string[] | |
* An array of strings with suitable alternatives. | |
*/ | |
protected function getParameterAlternatives($name) { | |
return $this->getAlternatives($name, array_keys($this->parameters)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function enterScope($name) { | |
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function leaveScope($name) { | |
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function addScope(ScopeInterface $scope) { | |
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function hasScope($name) { | |
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function isScopeActive($name) { | |
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); | |
} | |
/** | |
* Gets all defined service IDs. | |
* | |
* @return array | |
* An array of all defined service IDs. | |
*/ | |
public function getServiceIds() { | |
return array_keys($this->serviceDefinitions + $this->services); | |
} | |
} |