Mercurial > hg > isophonics-drupal-site
diff vendor/symfony/dependency-injection/ContainerBuilder.php @ 14:1fec387a4317
Update Drupal core to 8.5.2 via Composer
author | Chris Cannam |
---|---|
date | Mon, 23 Apr 2018 09:46:53 +0100 |
parents | 7a779792577d |
children | c2387f117808 |
line wrap: on
line diff
--- a/vendor/symfony/dependency-injection/ContainerBuilder.php Mon Apr 23 09:33:26 2018 +0100 +++ b/vendor/symfony/dependency-injection/ContainerBuilder.php Mon Apr 23 09:46:53 2018 +0100 @@ -11,9 +11,14 @@ namespace Symfony\Component\DependencyInjection; +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass; use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; @@ -22,11 +27,19 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Config\Resource\ComposerResource; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -86,13 +99,6 @@ */ private $expressionLanguageProviders = array(); - public function __construct(ParameterBagInterface $parameterBag = null) - { - parent::__construct($parameterBag); - - $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface'); - } - /** * @var string[] with tag names used by findTaggedServiceIds */ @@ -109,12 +115,37 @@ private $envCounters = array(); /** + * @var string[] the list of vendor directories + */ + private $vendors; + + private $autoconfiguredInstanceof = array(); + + private $removedIds = array(); + private $alreadyLoading = array(); + + public function __construct(ParameterBagInterface $parameterBag = null) + { + parent::__construct($parameterBag); + + $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface'); + $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true)); + $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false)); + $this->setAlias(ContainerInterface::class, new Alias('service_container', false)); + } + + /** + * @var \ReflectionClass[] a list of class reflectors + */ + private $classReflectors; + + /** * Sets the track resources flag. * * If you are not using the loaders and therefore don't want * to depend on the Config component, set this flag to false. * - * @param bool $track true if you want to track resources, false otherwise + * @param bool $track True if you want to track resources, false otherwise */ public function setResourceTracking($track) { @@ -124,7 +155,7 @@ /** * Checks if resources are tracked. * - * @return bool true if resources are tracked, false otherwise + * @return bool true If resources are tracked, false otherwise */ public function isTrackingResources() { @@ -133,19 +164,12 @@ /** * Sets the instantiator to be used when fetching proxies. - * - * @param InstantiatorInterface $proxyInstantiator */ public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) { $this->proxyInstantiator = $proxyInstantiator; } - /** - * Registers an extension. - * - * @param ExtensionInterface $extension An extension instance - */ public function registerExtension(ExtensionInterface $extension) { $this->extensions[$extension->getAlias()] = $extension; @@ -206,14 +230,10 @@ */ public function getResources() { - return array_unique($this->resources); + return array_values($this->resources); } /** - * Adds a resource for this configuration. - * - * @param ResourceInterface $resource A resource instance - * * @return $this */ public function addResource(ResourceInterface $resource) @@ -222,7 +242,11 @@ return $this; } - $this->resources[] = $resource; + if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) { + return $this; + } + + $this->resources[(string) $resource] = $resource; return $this; } @@ -248,14 +272,39 @@ /** * Adds the object class hierarchy as resources. * - * @param object $object An object instance + * @param object|string $object An object instance or class name * * @return $this */ public function addObjectResource($object) { if ($this->trackResources) { - $this->addClassResource(new \ReflectionClass($object)); + if (is_object($object)) { + $object = get_class($object); + } + if (!isset($this->classReflectors[$object])) { + $this->classReflectors[$object] = new \ReflectionClass($object); + } + $class = $this->classReflectors[$object]; + + foreach ($class->getInterfaceNames() as $name) { + if (null === $interface = &$this->classReflectors[$name]) { + $interface = new \ReflectionClass($name); + } + $file = $interface->getFileName(); + if (false !== $file && file_exists($file)) { + $this->fileExists($file); + } + } + do { + $file = $class->getFileName(); + if (false !== $file && file_exists($file)) { + $this->fileExists($file); + } + foreach ($class->getTraitNames() as $name) { + $this->addObjectResource($name); + } + } while ($class = $class->getParentClass()); } return $this; @@ -264,23 +313,104 @@ /** * Adds the given class hierarchy as resources. * - * @param \ReflectionClass $class + * @return $this * - * @return $this + * @deprecated since version 3.3, to be removed in 4.0. Use addObjectResource() or getReflectionClass() instead. */ public function addClassResource(\ReflectionClass $class) { - if (!$this->trackResources) { - return $this; + @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the addObjectResource() or the getReflectionClass() method instead.', E_USER_DEPRECATED); + + return $this->addObjectResource($class->name); + } + + /** + * Retrieves the requested reflection class and registers it for resource tracking. + * + * @param string $class + * @param bool $throw + * + * @return \ReflectionClass|null + * + * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true + * + * @final + */ + public function getReflectionClass($class, $throw = true) + { + if (!$class = $this->getParameterBag()->resolveValue($class)) { + return; + } + $resource = null; + + try { + if (isset($this->classReflectors[$class])) { + $classReflector = $this->classReflectors[$class]; + } elseif ($this->trackResources) { + $resource = new ClassExistenceResource($class, false); + $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); + } else { + $classReflector = new \ReflectionClass($class); + } + } catch (\ReflectionException $e) { + if ($throw) { + throw $e; + } + $classReflector = false; } - do { - if (is_file($class->getFileName())) { - $this->addResource(new FileResource($class->getFileName())); + if ($this->trackResources) { + if (!$classReflector) { + $this->addResource($resource ?: new ClassExistenceResource($class, false)); + } elseif (!$classReflector->isInternal()) { + $path = $classReflector->getFileName(); + + if (!$this->inVendors($path)) { + $this->addResource(new ReflectionClassResource($classReflector, $this->vendors)); + } } - } while ($class = $class->getParentClass()); + $this->classReflectors[$class] = $classReflector; + } - return $this; + return $classReflector ?: null; + } + + /** + * Checks whether the requested file or directory exists and registers the result for resource tracking. + * + * @param string $path The file or directory path for which to check the existence + * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, + * it will be used as pattern for tracking contents of the requested directory + * + * @return bool + * + * @final + */ + public function fileExists($path, $trackContents = true) + { + $exists = file_exists($path); + + if (!$this->trackResources || $this->inVendors($path)) { + return $exists; + } + + if (!$exists) { + $this->addResource(new FileExistenceResource($path)); + + return $exists; + } + + if (is_dir($path)) { + if ($trackContents) { + $this->addResource(new DirectoryResource($path, is_string($trackContents) ? $trackContents : null)); + } else { + $this->addResource(new GlobResource($path, '/*', false)); + } + } elseif ($trackContents) { + $this->addResource(new FileResource($path)); + } + + return $exists; } /** @@ -291,13 +421,17 @@ * * @return $this * - * @throws BadMethodCallException When this ContainerBuilder is frozen - * @throws \LogicException if the container is frozen + * @throws BadMethodCallException When this ContainerBuilder is compiled + * @throws \LogicException if the extension is not registered */ - public function loadFromExtension($extension, array $values = array()) + public function loadFromExtension($extension, array $values = null) { - if ($this->isFrozen()) { - throw new BadMethodCallException('Cannot load from an extension on a frozen container.'); + if ($this->isCompiled()) { + throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); + } + + if (func_num_args() < 2) { + $values = array(); } $namespace = $this->getExtension($extension)->getAlias(); @@ -316,7 +450,7 @@ * * @return $this */ - public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, $priority = 0*/) + public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/) { if (func_num_args() >= 3) { $priority = func_get_arg(2); @@ -324,7 +458,7 @@ if (__CLASS__ !== get_class($this)) { $r = new \ReflectionMethod($this, __FUNCTION__); if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a third `$priority = 0` argument in version 4.0. Not defining it is deprecated since 3.2.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Method %s() will have a third `int $priority = 0` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', __METHOD__), E_USER_DEPRECATED); } } @@ -368,18 +502,18 @@ * @param string $id The service identifier * @param object $service The service instance * - * @throws BadMethodCallException When this ContainerBuilder is frozen + * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function set($id, $service) { - $id = strtolower($id); + $id = $this->normalizeId($id); - if ($this->isFrozen() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { - // setting a synthetic service on a frozen container is alright - throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a frozen container is not allowed.', $id)); + if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { + // setting a synthetic service on a compiled container is alright + throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id)); } - unset($this->definitions[$id], $this->aliasDefinitions[$id]); + unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]); parent::set($id, $service); } @@ -391,7 +525,10 @@ */ public function removeDefinition($id) { - unset($this->definitions[strtolower($id)]); + if (isset($this->definitions[$id = $this->normalizeId($id)])) { + unset($this->definitions[$id]); + $this->removedIds[$id] = true; + } } /** @@ -403,7 +540,7 @@ */ public function has($id) { - $id = strtolower($id); + $id = $this->normalizeId($id); return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); } @@ -425,14 +562,29 @@ */ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - $id = strtolower($id); + if ($this->isCompiled() && isset($this->removedIds[$id = $this->normalizeId($id)])) { + @trigger_error(sprintf('Fetching the "%s" private service or alias is deprecated since Symfony 3.4 and will fail in 4.0. Make it public instead.', $id), E_USER_DEPRECATED); + } + return $this->doGet($id, $invalidBehavior); + } + + private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = array()) + { + $id = $this->normalizeId($id); + + if (isset($inlineServices[$id])) { + return $inlineServices[$id]; + } + if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { + return parent::get($id, $invalidBehavior); + } if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { return $service; } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { - return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior); + return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices); } try { @@ -445,12 +597,13 @@ throw $e; } - $this->loading[$id] = true; + $loading = isset($this->alreadyLoading[$id]) ? 'loading' : 'alreadyLoading'; + $this->{$loading}[$id] = true; try { - $service = $this->createService($definition, $id); + $service = $this->createService($definition, $inlineServices, $id); } finally { - unset($this->loading[$id]); + unset($this->{$loading}[$id]); } return $service; @@ -474,14 +627,12 @@ * parameter, the value will still be 'bar' as defined in the ContainerBuilder * constructor. * - * @param ContainerBuilder $container The ContainerBuilder instance to merge - * - * @throws BadMethodCallException When this ContainerBuilder is frozen + * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function merge(ContainerBuilder $container) + public function merge(self $container) { - if ($this->isFrozen()) { - throw new BadMethodCallException('Cannot merge on a frozen container.'); + if ($this->isCompiled()) { + throw new BadMethodCallException('Cannot merge on a compiled container.'); } $this->addDefinitions($container->getDefinitions()); @@ -503,16 +654,30 @@ } if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) { + $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders(); $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag()); + } else { + $envPlaceholders = array(); } foreach ($container->envCounters as $env => $count) { + if (!$count && !isset($envPlaceholders[$env])) { + continue; + } if (!isset($this->envCounters[$env])) { $this->envCounters[$env] = $count; } else { $this->envCounters[$env] += $count; } } + + foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) { + if (isset($this->autoconfiguredInstanceof[$interface])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface)); + } + + $this->autoconfiguredInstanceof[$interface] = $childDefinition; + } } /** @@ -559,9 +724,25 @@ * * Parameter values are resolved; * * The parameter bag is frozen; * * Extension loading is disabled. + * + * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current + * env vars or be replaced by uniquely identifiable placeholders. + * Set to "true" when you want to use the current ContainerBuilder + * directly, keep to "false" when the container is dumped instead. */ - public function compile() + public function compile(/*$resolveEnvPlaceholders = false*/) { + if (1 <= func_num_args()) { + $resolveEnvPlaceholders = func_get_arg(0); + } else { + if (__CLASS__ !== static::class) { + $r = new \ReflectionMethod($this, __FUNCTION__); + if (__CLASS__ !== $r->getDeclaringClass()->getName() && (1 > $r->getNumberOfParameters() || 'resolveEnvPlaceholders' !== $r->getParameters()[0]->name)) { + @trigger_error(sprintf('The %s::compile() method expects a first "$resolveEnvPlaceholders" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED); + } + } + $resolveEnvPlaceholders = false; + } $compiler = $this->getCompiler(); if ($this->trackResources) { @@ -569,21 +750,37 @@ $this->addObjectResource($pass); } } + $bag = $this->getParameterBag(); + + if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) { + $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000); + } $compiler->compile($this); foreach ($this->definitions as $id => $definition) { - if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { - $this->addClassResource(new \ReflectionClass($class)); + if ($this->trackResources && $definition->isLazy()) { + $this->getReflectionClass($definition->getClass()); } } $this->extensionConfigs = array(); - $bag = $this->getParameterBag(); + + if ($bag instanceof EnvPlaceholderParameterBag) { + if ($resolveEnvPlaceholders) { + $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true)); + } + + $this->envPlaceholders = $bag->getEnvPlaceholders(); + } parent::compile(); - $this->envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : array(); + foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { + if (!$definition->isPublic() || $definition->isPrivate()) { + $this->removedIds[$id] = true; + } + } } /** @@ -597,9 +794,17 @@ } /** + * Gets removed service or alias ids. + * + * @return array + */ + public function getRemovedIds() + { + return $this->removedIds; + } + + /** * Adds the service aliases. - * - * @param array $aliases An array of aliases */ public function addAliases(array $aliases) { @@ -610,8 +815,6 @@ /** * Sets the service aliases. - * - * @param array $aliases An array of aliases */ public function setAliases(array $aliases) { @@ -625,15 +828,17 @@ * @param string $alias The alias to create * @param string|Alias $id The service to alias * + * @return Alias + * * @throws InvalidArgumentException if the id is not a string or an Alias * @throws InvalidArgumentException if the alias is for itself */ public function setAlias($alias, $id) { - $alias = strtolower($alias); + $alias = $this->normalizeId($alias); if (is_string($id)) { - $id = new Alias($id); + $id = new Alias($this->normalizeId($id)); } elseif (!$id instanceof Alias) { throw new InvalidArgumentException('$id must be a string, or an Alias object.'); } @@ -642,9 +847,9 @@ throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias)); } - unset($this->definitions[$alias]); + unset($this->definitions[$alias], $this->removedIds[$alias]); - $this->aliasDefinitions[$alias] = $id; + return $this->aliasDefinitions[$alias] = $id; } /** @@ -654,7 +859,10 @@ */ public function removeAlias($alias) { - unset($this->aliasDefinitions[strtolower($alias)]); + if (isset($this->aliasDefinitions[$alias = $this->normalizeId($alias)])) { + unset($this->aliasDefinitions[$alias]); + $this->removedIds[$alias] = true; + } } /** @@ -666,7 +874,7 @@ */ public function hasAlias($id) { - return isset($this->aliasDefinitions[strtolower($id)]); + return isset($this->aliasDefinitions[$this->normalizeId($id)]); } /** @@ -690,7 +898,7 @@ */ public function getAlias($id) { - $id = strtolower($id); + $id = $this->normalizeId($id); if (!isset($this->aliasDefinitions[$id])) { throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); @@ -705,8 +913,8 @@ * This methods allows for simple registration of service definition * with a fluid interface. * - * @param string $id The service identifier - * @param string $class The service class + * @param string $id The service identifier + * @param string $class|null The service class * * @return Definition A Definition instance */ @@ -716,6 +924,22 @@ } /** + * Registers an autowired service definition. + * + * This method implements a shortcut for using setDefinition() with + * an autowired definition. + * + * @param string $id The service identifier + * @param null|string $class The service class + * + * @return Definition The created definition + */ + public function autowire($id, $class = null) + { + return $this->setDefinition($id, (new Definition($class))->setAutowired(true)); + } + + /** * Adds the service definitions. * * @param Definition[] $definitions An array of service definitions @@ -756,17 +980,17 @@ * * @return Definition the service definition * - * @throws BadMethodCallException When this ContainerBuilder is frozen + * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function setDefinition($id, Definition $definition) { - if ($this->isFrozen()) { - throw new BadMethodCallException('Adding definition to a frozen container is not allowed'); + if ($this->isCompiled()) { + throw new BadMethodCallException('Adding definition to a compiled container is not allowed'); } - $id = strtolower($id); + $id = $this->normalizeId($id); - unset($this->aliasDefinitions[$id]); + unset($this->aliasDefinitions[$id], $this->removedIds[$id]); return $this->definitions[$id] = $definition; } @@ -780,7 +1004,7 @@ */ public function hasDefinition($id) { - return isset($this->definitions[strtolower($id)]); + return isset($this->definitions[$this->normalizeId($id)]); } /** @@ -794,7 +1018,7 @@ */ public function getDefinition($id) { - $id = strtolower($id); + $id = $this->normalizeId($id); if (!isset($this->definitions[$id])) { throw new ServiceNotFoundException($id); @@ -816,10 +1040,21 @@ */ public function findDefinition($id) { - $id = strtolower($id); + $id = $this->normalizeId($id); + $seen = array(); while (isset($this->aliasDefinitions[$id])) { $id = (string) $this->aliasDefinitions[$id]; + + if (isset($seen[$id])) { + $seen = array_values($seen); + $seen = array_slice($seen, array_search($id, $seen)); + $seen[] = $id; + + throw new ServiceCircularReferenceException($id, $seen); + } + + $seen[$id] = $id; } return $this->getDefinition($id); @@ -838,9 +1073,13 @@ * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, $id, $tryProxy = true) + private function createService(Definition $definition, array &$inlineServices, $id = null, $tryProxy = true) { - if ($definition instanceof DefinitionDecorator) { + if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { + return $inlineServices[$h]; + } + + if ($definition instanceof ChildDefinition) { throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id)); } @@ -858,11 +1097,11 @@ ->instantiateProxy( $this, $definition, - $id, function () use ($definition, $id) { - return $this->createService($definition, $id, false); + $id, function () use ($definition, &$inlineServices, $id) { + return $this->createService($definition, $inlineServices, $id, false); } ); - $this->shareService($definition, $proxy, $id); + $this->shareService($definition, $proxy, $id, $inlineServices); return $proxy; } @@ -873,11 +1112,15 @@ require_once $parameterBag->resolveValue($definition->getFile()); } - $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments()))); + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices); + + if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { + return $this->services[$id]; + } if (null !== $factory = $definition->getFactory()) { if (is_array($factory)) { - $factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]); + $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices), $factory[1]); } elseif (!is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); } @@ -892,27 +1135,30 @@ } } } else { - $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); + $r = new \ReflectionClass($class = $parameterBag->resolveValue($definition->getClass())); $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); + // don't trigger deprecations for internal uses + // @deprecated since version 3.3, to be removed in 4.0 along with the deprecated class + $deprecationWhitelist = array('event_dispatcher' => ContainerAwareEventDispatcher::class); - if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ") && (!isset($deprecationWhitelist[$id]) || $deprecationWhitelist[$id] !== $class)) { @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED); } } if ($tryProxy || !$definition->isLazy()) { // share only if proxying failed, or if not a proxy - $this->shareService($definition, $service, $id); + $this->shareService($definition, $service, $id, $inlineServices); } - $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties()))); + $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices); foreach ($properties as $name => $value) { $service->$name = $value; } foreach ($definition->getMethodCalls() as $call) { - $this->callMethod($service, $call); + $this->callMethod($service, $call, $inlineServices); } if ($callable = $definition->getConfigurator()) { @@ -920,9 +1166,9 @@ $callable[0] = $parameterBag->resolveValue($callable[0]); if ($callable[0] instanceof Reference) { - $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior()); + $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices); } elseif ($callable[0] instanceof Definition) { - $callable[0] = $this->createService($callable[0], null); + $callable[0] = $this->createService($callable[0], $inlineServices); } } @@ -946,14 +1192,61 @@ */ public function resolveServices($value) { + return $this->doResolveServices($value); + } + + private function doResolveServices($value, array &$inlineServices = array()) + { if (is_array($value)) { foreach ($value as $k => $v) { - $value[$k] = $this->resolveServices($v); + $value[$k] = $this->doResolveServices($v, $inlineServices); } + } elseif ($value instanceof ServiceClosureArgument) { + $reference = $value->getValues()[0]; + $value = function () use ($reference) { + return $this->resolveServices($reference); + }; + } elseif ($value instanceof IteratorArgument) { + $value = new RewindableGenerator(function () use ($value) { + foreach ($value->getValues() as $k => $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + continue 2; + } + } + + yield $k => $this->resolveServices($v); + } + }, function () use ($value) { + $count = 0; + foreach ($value->getValues() as $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + foreach (self::getInitializedConditionals($v) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + continue 2; + } + } + + ++$count; + } + + return $count; + }); } elseif ($value instanceof Reference) { - $value = $this->get((string) $value, $value->getInvalidBehavior()); + $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices); } elseif ($value instanceof Definition) { - $value = $this->createService($value, null); + $value = $this->createService($value, $inlineServices); + } elseif ($value instanceof Parameter) { + $value = $this->getParameter((string) $value); } elseif ($value instanceof Expression) { $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this)); } @@ -975,16 +1268,20 @@ * } * } * - * @param string $name The tag name + * @param string $name + * @param bool $throwOnAbstract * * @return array An array of tags with the tagged service as key, holding a list of attribute arrays */ - public function findTaggedServiceIds($name) + public function findTaggedServiceIds($name, $throwOnAbstract = false) { $this->usedTags[] = $name; $tags = array(); foreach ($this->getDefinitions() as $id => $definition) { if ($definition->hasTag($name)) { + if ($throwOnAbstract && $definition->isAbstract()) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name)); + } $tags[$id] = $definition->getTag($name); } } @@ -1031,13 +1328,41 @@ } /** + * Returns a ChildDefinition that will be used for autoconfiguring the interface/class. + * + * @param string $interface The class or interface to match + * + * @return ChildDefinition + */ + public function registerForAutoconfiguration($interface) + { + if (!isset($this->autoconfiguredInstanceof[$interface])) { + $this->autoconfiguredInstanceof[$interface] = new ChildDefinition(''); + } + + return $this->autoconfiguredInstanceof[$interface]; + } + + /** + * Returns an array of ChildDefinition[] keyed by interface. + * + * @return ChildDefinition[] + */ + public function getAutoconfiguredInstanceof() + { + return $this->autoconfiguredInstanceof; + } + + /** * Resolves env parameter placeholders in a string or an array. * - * @param mixed $value The value to resolve - * @param string|null $format A sprintf() format to use as replacement for env placeholders or null to use the default parameter format - * @param array &$usedEnvs Env vars found while resolving are added to this array + * @param mixed $value The value to resolve + * @param string|true|null $format A sprintf() format returning the replacement for each env var name or + * null to resolve back to the original "%env(VAR)%" format or + * true to resolve to the actual values of the referenced env vars + * @param array &$usedEnvs Env vars found while resolving are added to this array * - * @return string The string with env parameters resolved + * @return mixed The value with env parameters resolved if a string or an array is passed */ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null) { @@ -1045,26 +1370,41 @@ $format = '%%env(%s)%%'; } - if (is_array($value)) { + $bag = $this->getParameterBag(); + if (true === $format) { + $value = $bag->resolveValue($value); + } + + if (\is_array($value)) { $result = array(); foreach ($value as $k => $v) { - $result[$this->resolveEnvPlaceholders($k, $format, $usedEnvs)] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs); + $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs); } return $result; } - if (!is_string($value)) { + if (!\is_string($value) || 38 > \strlen($value)) { return $value; } - - $bag = $this->getParameterBag(); $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; foreach ($envPlaceholders as $env => $placeholders) { foreach ($placeholders as $placeholder) { if (false !== stripos($value, $placeholder)) { - $value = str_ireplace($placeholder, sprintf($format, $env), $value); + if (true === $format) { + $resolved = $bag->escapeValue($this->getEnv($env)); + } else { + $resolved = sprintf($format, $env); + } + if ($placeholder === $value) { + $value = $resolved; + } else { + if (!is_string($resolved) && !is_numeric($resolved)) { + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $value)); + } + $value = str_ireplace($placeholder, $resolved, $value); + } $usedEnvs[$env] = $env; $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1; } @@ -1094,11 +1434,49 @@ } /** + * @internal + */ + public function getNormalizedIds() + { + $normalizedIds = array(); + + foreach ($this->normalizedIds as $k => $v) { + if ($v !== (string) $k) { + $normalizedIds[$k] = $v; + } + } + + return $normalizedIds; + } + + /** + * @final + */ + public function log(CompilerPassInterface $pass, $message) + { + $this->getCompiler()->log($pass, $message); + } + + /** + * {@inheritdoc} + */ + public function normalizeId($id) + { + if (!\is_string($id)) { + $id = (string) $id; + } + + return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || isset($this->removedIds[$id]) ? $id : parent::normalizeId($id); + } + + /** * Returns the Service Conditionals. * * @param mixed $value An array of conditionals to return * * @return array An array of Service conditionals + * + * @internal since version 3.4 */ public static function getServiceConditionals($value) { @@ -1108,7 +1486,7 @@ foreach ($value as $v) { $services = array_unique(array_merge($services, self::getServiceConditionals($v))); } - } elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE) { + } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { $services[] = (string) $value; } @@ -1116,6 +1494,72 @@ } /** + * Returns the initialized conditionals. + * + * @param mixed $value An array of conditionals to return + * + * @return array An array of uninitialized conditionals + * + * @internal + */ + public static function getInitializedConditionals($value) + { + $services = array(); + + if (is_array($value)) { + foreach ($value as $v) { + $services = array_unique(array_merge($services, self::getInitializedConditionals($v))); + } + } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) { + $services[] = (string) $value; + } + + return $services; + } + + /** + * Computes a reasonably unique hash of a value. + * + * @param mixed $value A serializable value + * + * @return string + */ + public static function hash($value) + { + $hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7); + + return str_replace(array('/', '+'), array('.', '_'), strtolower($hash)); + } + + /** + * {@inheritdoc} + */ + protected function getEnv($name) + { + $value = parent::getEnv($name); + $bag = $this->getParameterBag(); + + if (!is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) { + return $value; + } + + foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { + if (isset($placeholders[$value])) { + $bag = new ParameterBag($bag->all()); + + return $bag->unescapeValue($bag->get("env($name)")); + } + } + + $this->resolving["env($name)"] = true; + try { + return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true)); + } finally { + unset($this->resolving["env($name)"]); + } + } + + /** * Retrieves the currently set proxy instantiator or instantiates one. * * @return InstantiatorInterface @@ -1129,30 +1573,36 @@ return $this->proxyInstantiator; } - private function callMethod($service, $call) + private function callMethod($service, $call, array &$inlineServices) { - $services = self::getServiceConditionals($call[1]); - - foreach ($services as $s) { + foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { return; } } + foreach (self::getInitializedConditionals($call[1]) as $s) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { + return; + } + } - call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])))); + call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); } /** * Shares a given service in the container. * * @param Definition $definition - * @param mixed $service + * @param object $service * @param string|null $id */ - private function shareService(Definition $definition, $service, $id) + private function shareService(Definition $definition, $service, $id, array &$inlineServices) { + $inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service; + if (null !== $id && $definition->isShared()) { - $this->services[strtolower($id)] = $service; + $this->services[$id] = $service; + unset($this->loading[$id], $this->alreadyLoading[$id]); } } @@ -1167,4 +1617,22 @@ return $this->expressionLanguage; } + + private function inVendors($path) + { + if (null === $this->vendors) { + $resource = new ComposerResource(); + $this->vendors = $resource->getVendors(); + $this->addResource($resource); + } + $path = realpath($path) ?: $path; + + foreach ($this->vendors as $vendor) { + if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) { + return true; + } + } + + return false; + } }