From 6acb80f48f7772c7b300d03456c8241de31ad103 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 17 Jan 2017 21:04:53 +0100 Subject: [PATCH] [DI] Factorize compiler passes around new AbstractRecursivePass --- .../Compiler/AbstractRecursivePass.php | 76 +++++++++++ .../Compiler/AnalyzeServiceReferencesPass.php | 101 +++++++-------- .../Compiler/AutowirePass.php | 81 ++++++------ ...xceptionOnInvalidReferenceBehaviorPass.php | 45 ++----- .../Compiler/CheckReferenceValidityPass.php | 79 ++---------- .../Compiler/InlineServiceDefinitionsPass.php | 84 +++--------- .../ReplaceAliasByActualDefinitionPass.php | 66 ++-------- .../ResolveDefinitionTemplatesPass.php | 93 ++++--------- .../Compiler/ResolveInvalidReferencesPass.php | 122 ++++++++---------- .../ResolveParameterPlaceHoldersPass.php | 67 +++++----- .../Tests/Fixtures/php/services9_compiled.php | 2 +- 11 files changed, 331 insertions(+), 485 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php new file mode 100644 index 0000000000..11a2197d04 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractRecursivePass implements CompilerPassInterface +{ + protected $container; + protected $currentId; + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + try { + $this->processValue($container->getDefinitions(), true); + } finally { + $this->container = null; + } + } + + /** + * Processes a value found in a definition tree. + * + * @param mixed $value + * @param bool $isRoot + * + * @return mixed The processed value + */ + protected function processValue($value, $isRoot = false) + { + if (is_array($value)) { + foreach ($value as $k => $v) { + if ($isRoot) { + $this->currentId = $k; + } + if ($v !== $processedValue = $this->processValue($v, $isRoot)) { + $value[$k] = $processedValue; + } + } + } elseif ($value instanceof ArgumentInterface) { + $value->setValues($this->processValue($value->getValues())); + } elseif ($value instanceof Definition) { + $value->setArguments($this->processValue($value->getArguments())); + $value->setProperties($this->processValue($value->getProperties())); + $value->setMethodCalls($this->processValue($value->getMethodCalls())); + + if ($v = $value->getFactory()) { + $value->setFactory($this->processValue($v)); + } + if ($v = $value->getConfigurator()) { + $value->setConfigurator($this->processValue($v)); + } + } + + return $value; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index aa7dccfd64..e6333c9133 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -25,14 +25,13 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; * * @author Johannes M. Schmitt */ -class AnalyzeServiceReferencesPass implements RepeatablePassInterface +class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface { private $graph; - private $container; - private $currentId; private $currentDefinition; private $repeatedPass; private $onlyConstructorArguments; + private $lazy; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls @@ -60,68 +59,60 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface $this->container = $container; $this->graph = $container->getCompiler()->getServiceReferenceGraph(); $this->graph->clear(); - - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isSynthetic() || $definition->isAbstract()) { - continue; - } - - $this->currentId = $id; - $this->currentDefinition = $definition; - - $this->processArguments($definition->getArguments()); - if (is_array($definition->getFactory())) { - $this->processArguments($definition->getFactory()); - } - - if (!$this->onlyConstructorArguments) { - $this->processArguments($definition->getMethodCalls()); - $this->processArguments($definition->getProperties()); - if ($definition->getConfigurator()) { - $this->processArguments(array($definition->getConfigurator())); - } - } - } + $this->lazy = false; foreach ($container->getAliases() as $id => $alias) { $this->graph->connect($id, $alias, (string) $alias, $this->getDefinition((string) $alias), null); } + + parent::process($container); } - /** - * Processes service definitions for arguments to find relationships for the service graph. - * - * @param array $arguments An array of Reference or Definition objects relating to service definitions - * @param bool $lazy Whether the references nested in the arguments should be considered lazy or not - */ - private function processArguments(array $arguments, $lazy = false) + protected function processValue($value, $isRoot = false) { - foreach ($arguments as $argument) { - if (is_array($argument)) { - $this->processArguments($argument, $lazy); - } elseif ($argument instanceof ArgumentInterface) { - $this->processArguments($argument->getValues(), true); - } elseif ($argument instanceof Reference) { - $targetDefinition = $this->getDefinition((string) $argument); + $lazy = $this->lazy; - $this->graph->connect( - $this->currentId, - $this->currentDefinition, - $this->getDefinitionId((string) $argument), - $targetDefinition, - $argument, - $lazy || ($targetDefinition && $targetDefinition->isLazy()) - ); - } elseif ($argument instanceof Definition) { - $this->processArguments($argument->getArguments()); - $this->processArguments($argument->getMethodCalls()); - $this->processArguments($argument->getProperties()); + if ($value instanceof ArgumentInterface) { + $this->lazy = true; + parent::processValue($value); + $this->lazy = $lazy; - if (is_array($argument->getFactory())) { - $this->processArguments($argument->getFactory()); - } - } + return $value; } + if ($value instanceof Reference) { + $targetDefinition = $this->getDefinition((string) $value); + + $this->graph->connect( + $this->currentId, + $this->currentDefinition, + $this->getDefinitionId((string) $value), + $targetDefinition, + $value, + $this->lazy || ($targetDefinition && $targetDefinition->isLazy()) + ); + + return $value; + } + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + if ($isRoot) { + if ($value->isSynthetic() || $value->isAbstract()) { + return $value; + } + $this->currentDefinition = $value; + } + $this->lazy = false; + + if ($this->onlyConstructorArguments) { + $this->processValue($value->getFactory()); + $this->processValue($value->getArguments()); + } else { + parent::processValue($value, $isRoot); + } + $this->lazy = $lazy; + + return $value; } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 2afce310ea..6757bdfd3b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -22,12 +22,11 @@ use Symfony\Component\DependencyInjection\Reference; * * @author Kévin Dunglas */ -class AutowirePass implements CompilerPassInterface +class AutowirePass extends AbstractRecursivePass implements CompilerPassInterface { /** * @var ContainerBuilder */ - private $container; private $reflectionClasses = array(); private $definedTypes = array(); private $types; @@ -42,17 +41,11 @@ class AutowirePass implements CompilerPassInterface spl_autoload_register($throwingAutoloader); try { - $this->container = $container; - foreach ($container->getDefinitions() as $id => $definition) { - if ($autowiredMethods = $definition->getAutowiredMethods()) { - $this->completeDefinition($id, $definition, $autowiredMethods); - } - } + parent::process($container); } finally { spl_autoload_unregister($throwingAutoloader); // Free memory and remove circular reference to container - $this->container = null; $this->reflectionClasses = array(); $this->definedTypes = array(); $this->types = null; @@ -85,18 +78,16 @@ class AutowirePass implements CompilerPassInterface } /** - * Wires the given definition. - * - * @param string $id - * @param Definition $definition - * @param string[] $autowiredMethods - * - * @throws RuntimeException + * {@inheritdoc} */ - private function completeDefinition($id, Definition $definition, array $autowiredMethods) + protected function processValue($value, $isRoot = false) { - if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { - return; + if (!$value instanceof Definition || !$autowiredMethods = $value->getAutowiredMethods()) { + return parent::processValue($value, $isRoot); + } + + if (!$reflectionClass = $this->getReflectionClass($isRoot ? $this->currentId : null, $value)) { + return parent::processValue($value, $isRoot); } if ($this->container->isTrackingResources()) { @@ -104,27 +95,28 @@ class AutowirePass implements CompilerPassInterface } $methodsCalled = array(); - foreach ($definition->getMethodCalls() as $methodCall) { - $methodsCalled[$methodCall[0]] = true; + foreach ($value->getMethodCalls() as $methodCall) { + $methodsCalled[strtolower($methodCall[0])] = true; } - foreach ($this->getMethodsToAutowire($id, $reflectionClass, $autowiredMethods) as $reflectionMethod) { - if (!isset($methodsCalled[$reflectionMethod->name])) { - $this->autowireMethod($id, $definition, $reflectionMethod); + foreach ($this->getMethodsToAutowire($reflectionClass, $autowiredMethods) as $reflectionMethod) { + if (!isset($methodsCalled[strtolower($reflectionMethod->name)])) { + $this->autowireMethod($value, $reflectionMethod); } } + + return parent::processValue($value, $isRoot); } /** * Gets the list of methods to autowire. * - * @param string $id * @param \ReflectionClass $reflectionClass * @param string[] $configuredAutowiredMethods * * @return \ReflectionMethod[] */ - private function getMethodsToAutowire($id, \ReflectionClass $reflectionClass, array $configuredAutowiredMethods) + private function getMethodsToAutowire(\ReflectionClass $reflectionClass, array $configuredAutowiredMethods) { $found = array(); $regexList = array(); @@ -157,20 +149,19 @@ class AutowirePass implements CompilerPassInterface if ($notFound = array_diff($configuredAutowiredMethods, $found)) { $compiler = $this->container->getCompiler(); - $compiler->addLogMessage($compiler->getLoggingFormatter()->formatUnusedAutowiringPatterns($this, $id, $notFound)); + $compiler->addLogMessage($compiler->getLoggingFormatter()->formatUnusedAutowiringPatterns($this, $this->currentId, $notFound)); } } /** * Autowires the constructor or a setter. * - * @param string $id * @param Definition $definition * @param \ReflectionMethod $reflectionMethod * * @throws RuntimeException */ - private function autowireMethod($id, Definition $definition, \ReflectionMethod $reflectionMethod) + private function autowireMethod(Definition $definition, \ReflectionMethod $reflectionMethod) { if ($isConstructor = $reflectionMethod->isConstructor()) { $arguments = $definition->getArguments(); @@ -189,7 +180,7 @@ class AutowirePass implements CompilerPassInterface // no default value? Then fail if (!$parameter->isOptional()) { if ($isConstructor) { - throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id)); + throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $this->currentId)); } return; @@ -210,7 +201,7 @@ class AutowirePass implements CompilerPassInterface $addMethodCall = true; } else { try { - $value = $this->createAutowiredDefinition($typeHint, $id); + $value = $this->createAutowiredDefinition($typeHint); $addMethodCall = true; } catch (RuntimeException $e) { if ($parameter->allowsNull()) { @@ -338,38 +329,40 @@ class AutowirePass implements CompilerPassInterface * Registers a definition for the type if possible or throws an exception. * * @param \ReflectionClass $typeHint - * @param string $id * * @return Reference A reference to the registered definition * * @throws RuntimeException */ - private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) + private function createAutowiredDefinition(\ReflectionClass $typeHint) { if (isset($this->ambiguousServiceTypes[$typeHint->name])) { $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]); - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices), 1); + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $this->currentId, $classOrInterface, $matchingServices), 1); } if (!$typeHint->isInstantiable()) { $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface)); + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $this->currentId, $classOrInterface)); } - $argumentId = sprintf('autowired.%s', $typeHint->name); + $currentId = $this->currentId; + $this->currentId = $argumentId = sprintf('autowired.%s', $typeHint->name); $argumentDefinition = $this->container->register($argumentId, $typeHint->name); $argumentDefinition->setPublic(false); + $argumentDefinition->setAutowired(true); $this->populateAvailableType($argumentId, $argumentDefinition); try { - $this->completeDefinition($argumentId, $argumentDefinition, array('__construct')); + $this->processValue($argumentDefinition, true); + $this->currentId = $currentId; } catch (RuntimeException $e) { $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface); + $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $this->currentId, $classOrInterface); throw new RuntimeException($message, 0, $e); } @@ -379,14 +372,14 @@ class AutowirePass implements CompilerPassInterface /** * Retrieves the reflection class associated with the given service. * - * @param string $id - * @param Definition $definition + * @param string|null $id + * @param Definition $definition * * @return \ReflectionClass|false */ private function getReflectionClass($id, Definition $definition) { - if (isset($this->reflectionClasses[$id])) { + if (null !== $id && isset($this->reflectionClasses[$id])) { return $this->reflectionClasses[$id]; } @@ -403,7 +396,11 @@ class AutowirePass implements CompilerPassInterface $reflector = false; } - return $this->reflectionClasses[$id] = $reflector; + if (null !== $id) { + $this->reflectionClasses[$id] = $reflector; + } + + return $reflector; } private function addServiceToAmbiguousType($id, $type) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index 0e4040740d..1da5100786 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -11,56 +11,27 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Checks that all references are pointing to a valid service. * * @author Johannes M. Schmitt */ -class CheckExceptionOnInvalidReferenceBehaviorPass implements CompilerPassInterface +class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass implements CompilerPassInterface { - private $container; - private $sourceId; - - public function process(ContainerBuilder $container) + protected function processValue($value, $isRoot = false) { - $this->container = $container; + if ($value instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $destId = (string) $value; - foreach ($container->getDefinitions() as $id => $definition) { - $this->sourceId = $id; - $this->processDefinition($definition); - } - } - - private function processDefinition(Definition $definition) - { - $this->processReferences($definition->getArguments()); - $this->processReferences($definition->getMethodCalls()); - $this->processReferences($definition->getProperties()); - } - - private function processReferences(array $arguments) - { - foreach ($arguments as $argument) { - if (is_array($argument)) { - $this->processReferences($argument); - } elseif ($argument instanceof ArgumentInterface) { - $this->processReferences($argument->getValues()); - } elseif ($argument instanceof Definition) { - $this->processDefinition($argument); - } elseif ($argument instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $argument->getInvalidBehavior()) { - $destId = (string) $argument; - - if (!$this->container->has($destId)) { - throw new ServiceNotFoundException($destId, $this->sourceId); - } + if (!$this->container->has($destId)) { + throw new ServiceNotFoundException($destId, $this->currentId); } } + + return parent::processValue($value, $isRoot); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php index cd23246e7d..2a8a00eda3 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php @@ -11,10 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\RuntimeException; /** @@ -25,75 +23,26 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; * * @author Johannes M. Schmitt */ -class CheckReferenceValidityPass implements CompilerPassInterface +class CheckReferenceValidityPass extends AbstractRecursivePass implements CompilerPassInterface { - private $container; - private $currentId; - - /** - * Processes the ContainerBuilder to validate References. - * - * @param ContainerBuilder $container - */ - public function process(ContainerBuilder $container) + protected function processValue($value, $isRoot = false) { - $this->container = $container; - - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isSynthetic() || $definition->isAbstract()) { - continue; - } - - $this->currentId = $id; - - $this->validateReferences($definition->getArguments()); - $this->validateReferences($definition->getMethodCalls()); - $this->validateReferences($definition->getProperties()); + if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) { + return $value; } - } + if ($value instanceof Reference && $this->container->hasDefinition((string) $value)) { + $targetDefinition = $this->container->getDefinition((string) $value); - /** - * Validates an array of References. - * - * @param array $arguments An array of Reference objects - * - * @throws RuntimeException when there is a reference to an abstract definition. - */ - private function validateReferences(array $arguments) - { - foreach ($arguments as $argument) { - if (is_array($argument)) { - $this->validateReferences($argument); - } elseif ($argument instanceof ArgumentInterface) { - $this->validateReferences($argument->getValues()); - } elseif ($argument instanceof Reference) { - $targetDefinition = $this->getDefinition((string) $argument); - - if (null !== $targetDefinition && $targetDefinition->isAbstract()) { - throw new RuntimeException(sprintf( - 'The definition "%s" has a reference to an abstract definition "%s". ' - .'Abstract definitions cannot be the target of references.', - $this->currentId, - $argument - )); - } + if ($targetDefinition->isAbstract()) { + throw new RuntimeException(sprintf( + 'The definition "%s" has a reference to an abstract definition "%s". ' + .'Abstract definitions cannot be the target of references.', + $this->currentId, + $value + )); } } - } - /** - * Returns the Definition given an id. - * - * @param string $id Definition identifier - * - * @return Definition - */ - private function getDefinition($id) - { - if (!$this->container->hasDefinition($id)) { - return; - } - - return $this->container->getDefinition($id); + return parent::processValue($value, $isRoot); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 8ba4547e99..367a2f0104 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -14,20 +14,15 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Inline service definitions where this is possible. * * @author Johannes M. Schmitt */ -class InlineServiceDefinitionsPass implements RepeatablePassInterface +class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { private $repeatedPass; - private $graph; - private $compiler; - private $formatter; - private $currentId; /** * {@inheritdoc} @@ -38,77 +33,38 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface } /** - * Processes the ContainerBuilder for inline service definitions. - * - * @param ContainerBuilder $container + * {@inheritdoc} */ - public function process(ContainerBuilder $container) + protected function processValue($value, $isRoot = false) { - $this->compiler = $container->getCompiler(); - $this->formatter = $this->compiler->getLoggingFormatter(); - $this->graph = $this->compiler->getServiceReferenceGraph(); + if ($value instanceof ArgumentInterface) { + $this->processValue($value->getValues()); - $container->setDefinitions($this->inlineArguments($container, $container->getDefinitions(), true)); - } + return $value; + } + if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) { + $compiler = $this->container->getCompiler(); + $definition = $this->container->getDefinition($id); - /** - * Processes inline arguments. - * - * @param ContainerBuilder $container The ContainerBuilder - * @param array $arguments An array of arguments - * @param bool $isRoot If we are processing the root definitions or not - * - * @return array - */ - private function inlineArguments(ContainerBuilder $container, array $arguments, $isRoot = false) - { - foreach ($arguments as $k => $argument) { - if ($isRoot) { - $this->currentId = $k; - } - if (is_array($argument)) { - $arguments[$k] = $this->inlineArguments($container, $argument); - } elseif ($argument instanceof ArgumentInterface) { - $this->inlineArguments($container, $argument->getValues()); - } elseif ($argument instanceof Reference) { - if (!$container->hasDefinition($id = (string) $argument)) { - continue; + if ($this->isInlineableDefinition($id, $definition, $compiler->getServiceReferenceGraph())) { + $compiler->addLogMessage($compiler->getLoggingFormatter()->formatInlineService($this, $id, $this->currentId)); + + if ($definition->isShared()) { + return $definition; } - - if ($this->isInlineableDefinition($id, $definition = $container->getDefinition($id))) { - $this->compiler->addLogMessage($this->formatter->formatInlineService($this, $id, $this->currentId)); - - if ($definition->isShared()) { - $arguments[$k] = $definition; - } else { - $arguments[$k] = clone $definition; - } - } - } elseif ($argument instanceof Definition) { - $argument->setArguments($this->inlineArguments($container, $argument->getArguments())); - $argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls())); - $argument->setProperties($this->inlineArguments($container, $argument->getProperties())); - - $configurator = $this->inlineArguments($container, array($argument->getConfigurator())); - $argument->setConfigurator($configurator[0]); - - $factory = $this->inlineArguments($container, array($argument->getFactory())); - $argument->setFactory($factory[0]); + $value = clone $definition; } } - return $arguments; + return parent::processValue($value, $isRoot); } /** * Checks if the definition is inlineable. * - * @param string $id - * @param Definition $definition - * * @return bool If the definition is inlineable */ - private function isInlineableDefinition($id, Definition $definition) + private function isInlineableDefinition($id, Definition $definition, ServiceReferenceGraph $graph) { if (!$definition->isShared()) { return true; @@ -118,7 +74,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface return false; } - if (!$this->graph->hasNode($id)) { + if (!$graph->hasNode($id)) { return true; } @@ -127,7 +83,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface } $ids = array(); - foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + foreach ($graph->getNode($id)->getInEdges() as $edge) { $ids[] = $edge->getSourceNode()->getId(); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php index c36e88b22a..a871e92dec 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; @@ -22,10 +21,9 @@ use Symfony\Component\DependencyInjection\Reference; * * @author Johannes M. Schmitt */ -class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface +class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass implements CompilerPassInterface { - private $compiler; - private $formatter; + private $replacements; /** * Process the Container to replace aliases with service definitions. @@ -36,9 +34,6 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - // Setup - $this->compiler = $container->getCompiler(); - $this->formatter = $this->compiler->getLoggingFormatter(); // First collect all alias targets that need to be replaced $seenAliasTargets = array(); $replacements = array(); @@ -72,60 +67,25 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface $container->removeDefinition($targetId); $replacements[$targetId] = $definitionId; } + $this->replacements = $replacements; - // Now replace target instances in all definitions - foreach ($container->getDefinitions() as $definitionId => $definition) { - $definition->setArguments($this->updateArgumentReferences($replacements, $definitionId, $definition->getArguments())); - $definition->setMethodCalls($this->updateArgumentReferences($replacements, $definitionId, $definition->getMethodCalls())); - $definition->setProperties($this->updateArgumentReferences($replacements, $definitionId, $definition->getProperties())); - $definition->setFactory($this->updateFactoryReference($replacements, $definition->getFactory())); - } + parent::process($container); + $this->replacements = array(); } /** - * Recursively updates references in an array. - * - * @param array $replacements Table of aliases to replace - * @param string $definitionId Identifier of this definition - * @param array $arguments Where to replace the aliases - * - * @return array + * {@inheritdoc} */ - private function updateArgumentReferences(array $replacements, $definitionId, array $arguments) + protected function processValue($value, $isRoot = false) { - foreach ($arguments as $k => $argument) { - // Handle recursion step - if (is_array($argument)) { - $arguments[$k] = $this->updateArgumentReferences($replacements, $definitionId, $argument); - continue; - } - if ($argument instanceof ArgumentInterface) { - $argument->setValues($this->updateArgumentReferences($replacements, $definitionId, $argument->getValues())); - continue; - } - // Skip arguments that don't need replacement - if (!$argument instanceof Reference) { - continue; - } - $referenceId = (string) $argument; - if (!isset($replacements[$referenceId])) { - continue; - } + if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { // Perform the replacement - $newId = $replacements[$referenceId]; - $arguments[$k] = new Reference($newId, $argument->getInvalidBehavior()); - $this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $definitionId, $referenceId, $newId)); + $newId = $this->replacements[$referenceId]; + $value = new Reference($newId, $value->getInvalidBehavior()); + $compiler = $this->container->getCompiler(); + $compiler->addLogMessage($compiler->getLoggingFormatter()->formatUpdateReference($this, $this->currentId, $referenceId, $newId)); } - return $arguments; - } - - private function updateFactoryReference(array $replacements, $factory) - { - if (is_array($factory) && $factory[0] instanceof Reference && isset($replacements[$referenceId = (string) $factory[0]])) { - $factory[0] = new Reference($replacements[$referenceId], $factory[0]->getInvalidBehavior()); - } - - return $factory; + return parent::processValue($value, $isRoot); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index f8b1ab6464..a05acece53 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -11,10 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -25,83 +23,39 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; * @author Johannes M. Schmitt * @author Nicolas Grekas */ -class ResolveDefinitionTemplatesPass implements CompilerPassInterface +class ResolveDefinitionTemplatesPass extends AbstractRecursivePass implements CompilerPassInterface { - private $compiler; - private $formatter; - private $currentId; - - /** - * Process the ContainerBuilder to replace ChildDefinition instances with their real Definition instances. - * - * @param ContainerBuilder $container - */ - public function process(ContainerBuilder $container) + protected function processValue($value, $isRoot = false) { - $this->compiler = $container->getCompiler(); - $this->formatter = $this->compiler->getLoggingFormatter(); - - $container->setDefinitions($this->resolveArguments($container, $container->getDefinitions(), true)); - } - - /** - * Resolves definition decorator arguments. - * - * @param ContainerBuilder $container The ContainerBuilder - * @param array $arguments An array of arguments - * @param bool $isRoot If we are processing the root definitions or not - * - * @return array - */ - private function resolveArguments(ContainerBuilder $container, array $arguments, $isRoot = false) - { - foreach ($arguments as $k => $argument) { + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + if ($isRoot) { + // yes, we are specifically fetching the definition from the + // container to ensure we are not operating on stale data + $value = $this->container->getDefinition($this->currentId); + } + if ($value instanceof ChildDefinition) { + $value = $this->resolveDefinition($value); if ($isRoot) { - // yes, we are specifically fetching the definition from the - // container to ensure we are not operating on stale data - $arguments[$k] = $argument = $container->getDefinition($k); - $this->currentId = $k; - } - if (is_array($argument)) { - $arguments[$k] = $this->resolveArguments($container, $argument); - } elseif ($argument instanceof ArgumentInterface) { - $argument->setValues($this->resolveArguments($container, $argument->getValues())); - } elseif ($argument instanceof Definition) { - if ($argument instanceof ChildDefinition) { - $arguments[$k] = $argument = $this->resolveDefinition($container, $argument); - if ($isRoot) { - $container->setDefinition($k, $argument); - } - } - $argument->setArguments($this->resolveArguments($container, $argument->getArguments())); - $argument->setMethodCalls($this->resolveArguments($container, $argument->getMethodCalls())); - $argument->setProperties($this->resolveArguments($container, $argument->getProperties())); - - $configurator = $this->resolveArguments($container, array($argument->getConfigurator())); - $argument->setConfigurator($configurator[0]); - - $factory = $this->resolveArguments($container, array($argument->getFactory())); - $argument->setFactory($factory[0]); + $this->container->setDefinition($this->currentId, $value); } } - return $arguments; + return parent::processValue($value, $isRoot); } /** * Resolves the definition. * - * @param ContainerBuilder $container The ContainerBuilder - * @param ChildDefinition $definition - * * @return Definition * - * @throws \RuntimeException When the definition is invalid + * @throws RuntimeException When the definition is invalid */ - private function resolveDefinition(ContainerBuilder $container, ChildDefinition $definition) + private function resolveDefinition(ChildDefinition $definition) { try { - return $this->doResolveDefinition($container, $definition); + return $this->doResolveDefinition($definition); } catch (ExceptionInterface $e) { $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(true); @@ -111,22 +65,23 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface } } - private function doResolveDefinition(ContainerBuilder $container, ChildDefinition $definition) + private function doResolveDefinition(ChildDefinition $definition) { - if (!$container->has($parent = $definition->getParent())) { + if (!$this->container->has($parent = $definition->getParent())) { throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); } - $parentDef = $container->findDefinition($parent); + $parentDef = $this->container->findDefinition($parent); if ($parentDef instanceof ChildDefinition) { $id = $this->currentId; $this->currentId = $parent; - $parentDef = $this->resolveDefinition($container, $parentDef); - $container->setDefinition($parent, $parentDef); + $parentDef = $this->resolveDefinition($parentDef); + $this->container->setDefinition($parent, $parentDef); $this->currentId = $id; } - $this->compiler->addLogMessage($this->formatter->formatResolveInheritance($this, $this->currentId, $parent)); + $compiler = $this->container->getCompiler(); + $compiler->addLogMessage($compiler->getLoggingFormatter()->formatResolveInheritance($this, $this->currentId, $parent)); $def = new Definition(); // merge in parent definition diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php index dc6e44e051..7e31cdaafe 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php @@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -26,6 +27,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; class ResolveInvalidReferencesPass implements CompilerPassInterface { private $container; + private $signalingException; /** * Process the ContainerBuilder to resolve invalid references. @@ -35,86 +37,76 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface public function process(ContainerBuilder $container) { $this->container = $container; - foreach ($container->getDefinitions() as $definition) { - if ($definition->isSynthetic() || $definition->isAbstract()) { - continue; - } + $this->signalingException = new RuntimeException('Invalid reference.'); - $definition->setArguments( - $this->processArguments($definition->getArguments()) - ); - - $calls = array(); - foreach ($definition->getMethodCalls() as $call) { - try { - $calls[] = array($call[0], $this->processArguments($call[1], true)); - } catch (RuntimeException $e) { - // this call is simply removed - } - } - $definition->setMethodCalls($calls); - - $properties = array(); - foreach ($definition->getProperties() as $name => $value) { - try { - $value = $this->processArguments(array($value), true); - $properties[$name] = reset($value); - } catch (RuntimeException $e) { - // ignore property - } - } - $definition->setProperties($properties); + try { + $this->processValue($container->getDefinitions(), 1); + } finally { + $this->container = $this->signalingException = null; } } /** * Processes arguments to determine invalid references. * - * @param array $arguments An array of Reference objects - * @param bool $inMethodCall - * @param bool $inCollection - * - * @return array - * - * @throws RuntimeException When the config is invalid + * @throws RuntimeException When an invalid reference is found */ - private function processArguments(array $arguments, $inMethodCall = false, $inCollection = false) + private function processValue($value, $rootLevel = 0, $level = 0) { - $isNumeric = array_keys($arguments) === range(0, count($arguments) - 1); + if ($value instanceof Definition) { + if ($value->isSynthetic() || $value->isAbstract()) { + return $value; + } + $value->setArguments($this->processValue($value->getArguments(), 0)); + $value->setProperties($this->processValue($value->getProperties(), 1)); + $value->setMethodCalls($this->processValue($value->getMethodCalls(), 2)); + } elseif (is_array($value)) { + $i = 0; - foreach ($arguments as $k => $argument) { - if (is_array($argument)) { - $arguments[$k] = $this->processArguments($argument, $inMethodCall, true); - } elseif ($argument instanceof ArgumentInterface) { - $argument->setValues($this->processArguments($argument->getValues(), $inMethodCall, true)); - } elseif ($argument instanceof Reference) { - $id = (string) $argument; - - $invalidBehavior = $argument->getInvalidBehavior(); - $exists = $this->container->has($id); - - // resolve invalid behavior - if (!$exists && ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { - $arguments[$k] = null; - } elseif (!$exists && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { - if ($inCollection) { - unset($arguments[$k]); - continue; + foreach ($value as $k => $v) { + try { + if (false !== $i && $k !== $i++) { + $i = false; } - if ($inMethodCall) { - throw new RuntimeException('Method shouldn\'t be called.'); + if ($v !== $processedValue = $this->processValue($v, $rootLevel, 1 + $level)) { + $value[$k] = $processedValue; + } + } catch (RuntimeException $e) { + if ($rootLevel < $level || ($rootLevel && !$level)) { + unset($value[$k]); + } elseif ($rootLevel) { + throw $e; + } else { + $value[$k] = null; } - - $arguments[$k] = null; } } + + // Ensure numerically indexed arguments have sequential numeric keys. + if (false !== $i) { + $value = array_values($value); + } + } elseif ($value instanceof ArgumentInterface) { + $value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level)); + } elseif ($value instanceof Reference) { + $id = (string) $value; + + if ($this->container->has($id)) { + return $value; + } + $invalidBehavior = $value->getInvalidBehavior(); + + // resolve invalid behavior + if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { + $value = null; + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { + if (0 < $level || $rootLevel) { + throw $this->signalingException; + } + $value = null; + } } - // Ensure numerically indexed arguments have sequential numeric keys. - if ($isNumeric) { - $arguments = array_values($arguments); - } - - return $arguments; + return $value; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php index 0c5963cc11..a0fa3ae1a5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; /** @@ -19,52 +20,50 @@ use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; * * @author Johannes M. Schmitt */ -class ResolveParameterPlaceHoldersPass implements CompilerPassInterface +class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass implements CompilerPassInterface { + private $bag; + /** - * Processes the ContainerBuilder to resolve parameter placeholders. - * - * @param ContainerBuilder $container + * {@inheritdoc} * * @throws ParameterNotFoundException */ public function process(ContainerBuilder $container) { - $parameterBag = $container->getParameterBag(); + $this->bag = $container->getParameterBag(); - foreach ($container->getDefinitions() as $id => $definition) { - try { - $definition->setClass($parameterBag->resolveValue($definition->getClass())); - $definition->setFile($parameterBag->resolveValue($definition->getFile())); - $definition->setArguments($parameterBag->resolveValue($definition->getArguments())); + try { + parent::process($container); - $factory = $definition->getFactory(); - - if (is_array($factory) && isset($factory[0])) { - $factory[0] = $parameterBag->resolveValue($factory[0]); - $definition->setFactory($factory); - } - - $calls = array(); - foreach ($definition->getMethodCalls() as $name => $arguments) { - $calls[$parameterBag->resolveValue($name)] = $parameterBag->resolveValue($arguments); - } - $definition->setMethodCalls($calls); - - $definition->setProperties($parameterBag->resolveValue($definition->getProperties())); - } catch (ParameterNotFoundException $e) { - $e->setSourceId($id); - - throw $e; + $aliases = array(); + foreach ($container->getAliases() as $name => $target) { + $this->currentId = $name; + $aliases[$this->bag->resolveValue($name)] = $this->bag->resolveValue($target); } + $container->setAliases($aliases); + } catch (ParameterNotFoundException $e) { + $e->setSourceId($this->currentId); + + throw $e; } - $aliases = array(); - foreach ($container->getAliases() as $name => $target) { - $aliases[$parameterBag->resolveValue($name)] = $parameterBag->resolveValue($target); - } - $container->setAliases($aliases); + $this->bag->resolve(); + $this->bag = null; + } - $parameterBag->resolve(); + protected function processValue($value, $isRoot = false) + { + if (is_string($value)) { + return $this->bag->resolveValue($value); + } + if ($value instanceof Definition) { + $value->setClass($this->bag->resolveValue($value->getClass())); + $value->setFile($this->bag->resolveValue($value->getFile())); + $value->setProperties($this->bag->resolveValue($value->getProperties())); + $value->setMethodCalls($this->bag->resolveValue($value->getMethodCalls())); + } + + return parent::processValue($value, $isRoot); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 975e612083..c861ec1abf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -316,7 +316,7 @@ class ProjectServiceContainer extends Container return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function() { yield 0 => 'foo'; yield 1 => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->get('foo.baz')) && false ?: '_'}; - yield 2 => array('bar' => 'foo is '.'bar'.'', 'foobar' => 'bar'); + yield 2 => array('bar' => 'foo is bar', 'foobar' => 'bar'); yield 3 => true; yield 4 => $this; }));