Chris@0: aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : []; Chris@0: $this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : []; Chris@0: $this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : []; Chris@0: $this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE; Chris@0: Chris@0: // Register the service_container with itself. Chris@0: $this->services['service_container'] = $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { Chris@0: if (isset($this->aliases[$id])) { Chris@0: $id = $this->aliases[$id]; Chris@0: } Chris@0: Chris@0: // Re-use shared service instance if it exists. Chris@0: if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) { Chris@0: return $this->services[$id]; Chris@0: } Chris@0: Chris@0: if (isset($this->loading[$id])) { Chris@0: throw new ServiceCircularReferenceException($id, array_keys($this->loading)); Chris@0: } Chris@0: Chris@0: $definition = isset($this->serviceDefinitions[$id]) ? $this->serviceDefinitions[$id] : NULL; Chris@0: Chris@0: if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { Chris@0: if (!$id) { Chris@18: throw new ServiceNotFoundException(''); Chris@0: } Chris@0: Chris@0: throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id)); Chris@0: } Chris@0: Chris@0: // In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE Chris@0: // is used, the actual wanted behavior is to re-try getting the service at a Chris@0: // later point. Chris@0: if (!$definition) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Definition is a keyed array, so [0] is only defined when it is a Chris@0: // serialized string. Chris@0: if (isset($definition[0])) { Chris@0: $definition = unserialize($definition); Chris@0: } Chris@0: Chris@0: // Now create the service. Chris@0: $this->loading[$id] = TRUE; Chris@0: Chris@0: try { Chris@0: $service = $this->createService($definition, $id); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: unset($this->loading[$id]); Chris@0: unset($this->services[$id]); Chris@0: Chris@0: if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) { Chris@0: return; Chris@0: } Chris@0: Chris@0: throw $e; Chris@0: } Chris@0: Chris@0: unset($this->loading[$id]); Chris@0: Chris@0: return $service; Chris@0: } Chris@0: Chris@0: /** Chris@18: * Resets shared services from the container. Chris@18: * Chris@18: * The container is not intended to be used again after being reset in a Chris@18: * normal workflow. This method is meant as a way to release references for Chris@18: * ref-counting. A subsequent call to ContainerInterface::get() will recreate Chris@18: * a new instance of the shared service. Chris@0: */ Chris@0: public function reset() { Chris@0: if (!empty($this->scopedServices)) { Chris@0: throw new LogicException('Resetting the container is not allowed when a scope is active.'); Chris@0: } Chris@0: Chris@0: $this->services = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a service from a service definition. Chris@0: * Chris@0: * @param array $definition Chris@0: * The service definition to create a service from. Chris@0: * @param string $id Chris@0: * The service identifier, necessary so it can be shared if its public. Chris@0: * Chris@0: * @return object Chris@0: * The service described by the service definition. Chris@0: * Chris@0: * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException Chris@0: * Thrown when the service is a synthetic service. Chris@0: * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException Chris@0: * Thrown when the configurator callable in $definition['configurator'] is Chris@0: * not actually a callable. Chris@0: * @throws \ReflectionException Chris@0: * Thrown when the service class takes more than 10 parameters to construct, Chris@0: * and cannot be instantiated. Chris@0: */ Chris@0: protected function createService(array $definition, $id) { Chris@0: if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) { Chris@0: 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)); Chris@0: } Chris@0: Chris@0: $arguments = []; Chris@0: if (isset($definition['arguments'])) { Chris@0: $arguments = $definition['arguments']; Chris@0: Chris@0: if ($arguments instanceof \stdClass) { Chris@0: $arguments = $this->resolveServicesAndParameters($arguments); Chris@0: } Chris@0: } Chris@0: Chris@0: if (isset($definition['file'])) { Chris@0: $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters([$definition['file']])); Chris@0: require_once $file; Chris@0: } Chris@0: Chris@0: if (isset($definition['factory'])) { Chris@0: $factory = $definition['factory']; Chris@0: if (is_array($factory)) { Chris@0: $factory = $this->resolveServicesAndParameters([$factory[0], $factory[1]]); Chris@0: } Chris@0: elseif (!is_string($factory)) { Chris@0: throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); Chris@0: } Chris@0: Chris@0: $service = call_user_func_array($factory, $arguments); Chris@0: } Chris@0: else { Chris@0: $class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters([$definition['class']])); Chris@0: $length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments); Chris@0: Chris@0: // Optimize class instantiation for services with up to 10 parameters as Chris@0: // ReflectionClass is noticeably slow. Chris@0: switch ($length) { Chris@0: case 0: Chris@0: $service = new $class(); Chris@0: break; Chris@0: Chris@0: case 1: Chris@0: $service = new $class($arguments[0]); Chris@0: break; Chris@0: Chris@0: case 2: Chris@0: $service = new $class($arguments[0], $arguments[1]); Chris@0: break; Chris@0: Chris@0: case 3: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2]); Chris@0: break; Chris@0: Chris@0: case 4: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]); Chris@0: break; Chris@0: Chris@0: case 5: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]); Chris@0: break; Chris@0: Chris@0: case 6: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]); Chris@0: break; Chris@0: Chris@0: case 7: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]); Chris@0: break; Chris@0: Chris@0: case 8: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]); Chris@0: break; Chris@0: Chris@0: case 9: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]); Chris@0: break; Chris@0: Chris@0: case 10: Chris@0: $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]); Chris@0: break; Chris@0: Chris@0: default: Chris@0: $r = new \ReflectionClass($class); Chris@0: $service = $r->newInstanceArgs($arguments); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: if (!isset($definition['shared']) || $definition['shared'] !== FALSE) { Chris@0: $this->services[$id] = $service; Chris@0: } Chris@0: Chris@0: if (isset($definition['calls'])) { Chris@0: foreach ($definition['calls'] as $call) { Chris@0: $method = $call[0]; Chris@0: $arguments = []; Chris@0: if (!empty($call[1])) { Chris@0: $arguments = $call[1]; Chris@0: if ($arguments instanceof \stdClass) { Chris@0: $arguments = $this->resolveServicesAndParameters($arguments); Chris@0: } Chris@0: } Chris@0: call_user_func_array([$service, $method], $arguments); Chris@0: } Chris@0: } Chris@0: Chris@0: if (isset($definition['properties'])) { Chris@0: if ($definition['properties'] instanceof \stdClass) { Chris@0: $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']); Chris@0: } Chris@0: foreach ($definition['properties'] as $key => $value) { Chris@0: $service->{$key} = $value; Chris@0: } Chris@0: } Chris@0: Chris@0: if (isset($definition['configurator'])) { Chris@0: $callable = $definition['configurator']; Chris@0: if (is_array($callable)) { Chris@0: $callable = $this->resolveServicesAndParameters($callable); Chris@0: } Chris@0: Chris@0: if (!is_callable($callable)) { Chris@0: throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service))); Chris@0: } Chris@0: Chris@0: call_user_func($callable, $service); Chris@0: } Chris@0: Chris@0: return $service; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function set($id, $service) { Chris@0: $this->services[$id] = $service; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function has($id) { Chris@0: return isset($this->aliases[$id]) || isset($this->services[$id]) || isset($this->serviceDefinitions[$id]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getParameter($name) { Chris@0: if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) { Chris@0: if (!$name) { Chris@18: throw new ParameterNotFoundException(''); Chris@0: } Chris@0: Chris@0: throw new ParameterNotFoundException($name, NULL, NULL, NULL, $this->getParameterAlternatives($name)); Chris@0: } Chris@0: Chris@0: return $this->parameters[$name]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasParameter($name) { Chris@0: return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setParameter($name, $value) { Chris@0: if ($this->frozen) { Chris@0: throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); Chris@0: } Chris@0: Chris@0: $this->parameters[$name] = $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function initialized($id) { Chris@0: if (isset($this->aliases[$id])) { Chris@0: $id = $this->aliases[$id]; Chris@0: } Chris@0: Chris@0: return isset($this->services[$id]) || array_key_exists($id, $this->services); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resolves arguments that represent services or variables to the real values. Chris@0: * Chris@0: * @param array|\stdClass $arguments Chris@0: * The arguments to resolve. Chris@0: * Chris@0: * @return array Chris@0: * The resolved arguments. Chris@0: * Chris@0: * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException Chris@0: * If a parameter/service could not be resolved. Chris@0: * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException Chris@0: * If an unknown type is met while resolving parameters and services. Chris@0: */ Chris@0: protected function resolveServicesAndParameters($arguments) { Chris@0: // Check if this collection needs to be resolved. Chris@0: if ($arguments instanceof \stdClass) { Chris@0: if ($arguments->type !== 'collection') { Chris@0: throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $arguments->type)); Chris@0: } Chris@0: // In case there is nothing to resolve, we are done here. Chris@0: if (!$arguments->resolve) { Chris@0: return $arguments->value; Chris@0: } Chris@0: $arguments = $arguments->value; Chris@0: } Chris@0: Chris@0: // Process the arguments. Chris@0: foreach ($arguments as $key => $argument) { Chris@0: // For this machine-optimized format, only \stdClass arguments are Chris@0: // processed and resolved. All other values are kept as is. Chris@0: if ($argument instanceof \stdClass) { Chris@0: $type = $argument->type; Chris@0: Chris@0: // Check for parameter. Chris@0: if ($type == 'parameter') { Chris@0: $name = $argument->name; Chris@0: if (!isset($this->parameters[$name])) { Chris@0: $arguments[$key] = $this->getParameter($name); Chris@0: // This can never be reached as getParameter() throws an Exception, Chris@0: // because we already checked that the parameter is not set above. Chris@0: } Chris@0: Chris@0: // Update argument. Chris@0: $argument = $arguments[$key] = $this->parameters[$name]; Chris@0: Chris@0: // In case there is not a machine readable value (e.g. a service) Chris@0: // behind this resolved parameter, continue. Chris@0: if (!($argument instanceof \stdClass)) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Fall through. Chris@0: $type = $argument->type; Chris@0: } Chris@0: Chris@0: // Create a service. Chris@0: if ($type == 'service') { Chris@0: $id = $argument->id; Chris@0: Chris@0: // Does the service already exist? Chris@0: if (isset($this->aliases[$id])) { Chris@0: $id = $this->aliases[$id]; Chris@0: } Chris@0: Chris@0: if (isset($this->services[$id])) { Chris@0: $arguments[$key] = $this->services[$id]; Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Return the service. Chris@0: $arguments[$key] = $this->get($id, $argument->invalidBehavior); Chris@0: Chris@0: continue; Chris@0: } Chris@0: // Create private service. Chris@0: elseif ($type == 'private_service') { Chris@0: $id = $argument->id; Chris@0: Chris@0: // Does the private service already exist. Chris@0: if (isset($this->privateServices[$id])) { Chris@0: $arguments[$key] = $this->privateServices[$id]; Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Create the private service. Chris@0: $arguments[$key] = $this->createService($argument->value, $id); Chris@0: if ($argument->shared) { Chris@0: $this->privateServices[$id] = $arguments[$key]; Chris@0: } Chris@0: Chris@0: continue; Chris@0: } Chris@0: // Check for collection. Chris@0: elseif ($type == 'collection') { Chris@0: $value = $argument->value; Chris@0: Chris@0: // Does this collection need resolving? Chris@0: if ($argument->resolve) { Chris@0: $arguments[$key] = $this->resolveServicesAndParameters($value); Chris@0: } Chris@0: else { Chris@0: $arguments[$key] = $value; Chris@0: } Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($type !== NULL) { Chris@0: throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $type)); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $arguments; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides alternatives for a given array and key. Chris@0: * Chris@0: * @param string $search_key Chris@0: * The search key to get alternatives for. Chris@0: * @param array $keys Chris@0: * The search space to search for alternatives in. Chris@0: * Chris@0: * @return string[] Chris@0: * An array of strings with suitable alternatives. Chris@0: */ Chris@0: protected function getAlternatives($search_key, array $keys) { Chris@0: $alternatives = []; Chris@0: foreach ($keys as $key) { Chris@0: $lev = levenshtein($search_key, $key); Chris@0: if ($lev <= strlen($search_key) / 3 || strpos($key, $search_key) !== FALSE) { Chris@0: $alternatives[] = $key; Chris@0: } Chris@0: } Chris@0: Chris@0: return $alternatives; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides alternatives in case a service was not found. Chris@0: * Chris@0: * @param string $id Chris@0: * The service to get alternatives for. Chris@0: * Chris@0: * @return string[] Chris@0: * An array of strings with suitable alternatives. Chris@0: */ Chris@0: protected function getServiceAlternatives($id) { Chris@0: $all_service_keys = array_unique(array_merge(array_keys($this->services), array_keys($this->serviceDefinitions))); Chris@0: return $this->getAlternatives($id, $all_service_keys); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides alternatives in case a parameter was not found. Chris@0: * Chris@0: * @param string $name Chris@0: * The parameter to get alternatives for. Chris@0: * Chris@0: * @return string[] Chris@0: * An array of strings with suitable alternatives. Chris@0: */ Chris@0: protected function getParameterAlternatives($name) { Chris@0: return $this->getAlternatives($name, array_keys($this->parameters)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets all defined service IDs. Chris@0: * Chris@0: * @return array Chris@0: * An array of all defined service IDs. Chris@0: */ Chris@0: public function getServiceIds() { Chris@0: return array_keys($this->serviceDefinitions + $this->services); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Ensure that cloning doesn't work. Chris@0: */ Chris@12: private function __clone() { Chris@0: } Chris@0: Chris@0: }