From 53139436929201b34e1fac06b91f03350960e40a Mon Sep 17 00:00:00 2001 From: Titouan Galopin Date: Tue, 13 Dec 2016 18:37:51 +0100 Subject: [PATCH 1/2] [DependencyInjection] Implement lazy collection type using generators --- .../Argument/ArgumentInterface.php | 27 ++++++++ .../Argument/IteratorArgument.php | 43 ++++++++++++ .../Argument/RewindableGenerator.php | 32 +++++++++ .../Compiler/AnalyzeServiceReferencesPass.php | 15 ++-- .../Compiler/CheckCircularReferencesPass.php | 5 +- ...xceptionOnInvalidReferenceBehaviorPass.php | 3 + .../Compiler/CheckReferenceValidityPass.php | 3 + .../Compiler/InlineServiceDefinitionsPass.php | 3 + .../ReplaceAliasByActualDefinitionPass.php | 5 ++ .../ResolveDefinitionTemplatesPass.php | 3 + .../Compiler/ResolveInvalidReferencesPass.php | 3 + .../ResolveReferencesToAliasesPass.php | 3 + .../Compiler/ServiceReferenceGraph.php | 16 ++++- .../Compiler/ServiceReferenceGraphEdge.php | 15 +++- .../DependencyInjection/ContainerBuilder.php | 15 ++++ .../Dumper/GraphvizDumper.php | 15 ++-- .../DependencyInjection/Dumper/PhpDumper.php | 16 +++++ .../DependencyInjection/Dumper/XmlDumper.php | 4 ++ .../DependencyInjection/Dumper/YamlDumper.php | 5 ++ .../Loader/XmlFileLoader.php | 4 ++ .../schema/dic/services/services-1.0.xsd | 1 + .../AnalyzeServiceReferencesPassTest.php | 68 +++++++++++++++++++ .../CheckCircularReferencesPassTest.php | 19 ++++++ .../Tests/ContainerBuilderTest.php | 25 +++++++ .../Tests/Dumper/PhpDumperTest.php | 49 +++++++++++++ .../Tests/Fixtures/containers/container9.php | 9 +++ .../Tests/Fixtures/graphviz/services9.dot | 7 ++ .../Tests/Fixtures/includes/classes.php | 10 +++ .../Tests/Fixtures/php/services1-1.php | 1 + .../Tests/Fixtures/php/services1.php | 1 + .../Tests/Fixtures/php/services10.php | 1 + .../Tests/Fixtures/php/services12.php | 1 + .../Tests/Fixtures/php/services13.php | 1 + .../Tests/Fixtures/php/services19.php | 1 + .../Tests/Fixtures/php/services24.php | 1 + .../Tests/Fixtures/php/services26.php | 1 + .../Tests/Fixtures/php/services8.php | 1 + .../Tests/Fixtures/php/services9.php | 40 +++++++++++ .../Tests/Fixtures/php/services9_compiled.php | 37 ++++++++++ .../Tests/Fixtures/xml/services9.xml | 18 +++++ .../Tests/Fixtures/yaml/services9.yml | 6 ++ .../Tests/Loader/XmlFileLoaderTest.php | 12 ++++ 42 files changed, 531 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php create mode 100644 src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php create mode 100644 src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php diff --git a/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php b/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php new file mode 100644 index 0000000000000..b46eb77be749e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a complex argument containing nested values. + * + * @author Titouan Galopin + */ +interface ArgumentInterface +{ + /** + * @return array + */ + public function getValues(); + + public function setValues(array $values); +} diff --git a/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php new file mode 100644 index 0000000000000..5c35a688638cd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a collection of values to lazily iterate over. + * + * @author Titouan Galopin + */ +class IteratorArgument implements ArgumentInterface +{ + private $values; + + public function __construct(array $values) + { + $this->values = $values; + } + + /** + * @return array The values to lazily iterate over + */ + public function getValues() + { + return $this->values; + } + + /** + * @param array $values The values to lazily iterate over + */ + public function setValues(array $values) + { + $this->values = $values; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php b/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php new file mode 100644 index 0000000000000..ea6ec4444d3d6 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * @internal + */ +class RewindableGenerator implements \IteratorAggregate +{ + private $generator; + + public function __construct(callable $generator) + { + $this->generator = $generator; + } + + public function getIterator() + { + $g = $this->generator; + + return $g(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 717fc378e498e..aa7dccfd64d1f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -11,6 +11,7 @@ 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; @@ -91,19 +92,25 @@ public function process(ContainerBuilder $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) + private function processArguments(array $arguments, $lazy = false) { foreach ($arguments as $argument) { if (is_array($argument)) { - $this->processArguments($argument); + $this->processArguments($argument, $lazy); + } elseif ($argument instanceof ArgumentInterface) { + $this->processArguments($argument->getValues(), true); } elseif ($argument instanceof Reference) { + $targetDefinition = $this->getDefinition((string) $argument); + $this->graph->connect( $this->currentId, $this->currentDefinition, $this->getDefinitionId((string) $argument), - $this->getDefinition((string) $argument), - $argument + $targetDefinition, + $argument, + $lazy || ($targetDefinition && $targetDefinition->isLazy()) ); } elseif ($argument instanceof Definition) { $this->processArguments($argument->getArguments()); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php index 156bcc0c3ab7b..1bcf3d141924c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php @@ -60,9 +60,8 @@ private function checkOutEdges(array $edges) $id = $node->getId(); if (empty($this->checkedNodes[$id])) { - - // don't check circular dependencies for lazy services - if (!$node->getValue() || !$node->getValue()->isLazy()) { + // Don't check circular references for lazy edges + if (!$node->getValue() || !$edge->isLazy()) { $searchKey = array_search($id, $this->currentPath); $this->currentPath[] = $id; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index 304f7849516c8..0e4040740dfda 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -11,6 +11,7 @@ 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; @@ -49,6 +50,8 @@ 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()) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php index 80b81d695d66a..cd23246e7dcbf 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceValidityPass.php @@ -11,6 +11,7 @@ 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; @@ -63,6 +64,8 @@ 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); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index a4e2e041b24fe..fc05a6b27b2ac 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -11,6 +11,7 @@ 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; @@ -67,6 +68,8 @@ private function inlineArguments(ContainerBuilder $container, array $arguments, } if (is_array($argument)) { $arguments[$k] = $this->inlineArguments($container, $argument); + } elseif ($argument instanceof ArgumentInterface) { + $argument->setValues($this->inlineArguments($container, $argument->getValues())); } elseif ($argument instanceof Reference) { if (!$container->hasDefinition($id = (string) $argument)) { continue; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php index 00fc859d3e72e..c36e88b22a3ea 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -11,6 +11,7 @@ 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; @@ -98,6 +99,10 @@ private function updateArgumentReferences(array $replacements, $definitionId, ar $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; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index ba5978a734503..3f854d3b8fb13 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -11,6 +11,7 @@ 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; @@ -63,6 +64,8 @@ private function resolveArguments(ContainerBuilder $container, array $arguments, } 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); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php index 5b58fb1ecf749..dc6e44e0513d7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -84,6 +85,8 @@ private function processArguments(array $arguments, $inMethodCall = false, $inCo 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; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php index 8514739a77dfd..ad4c43c8565ac 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; @@ -65,6 +66,8 @@ private function processArguments(array $arguments) foreach ($arguments as $k => $argument) { if (is_array($argument)) { $arguments[$k] = $this->processArguments($argument); + } elseif ($argument instanceof ArgumentInterface) { + $argument->setValues($this->processArguments($argument->getValues())); } elseif ($argument instanceof Reference) { $defId = $this->getDefinitionId($id = (string) $argument); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index e7306ab560e22..4ab49d21cc579 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -84,12 +84,24 @@ public function clear() * @param string $destId * @param string $destValue * @param string $reference + * @param bool $lazy */ - public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null) + public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, $lazy = false*/) { + if (func_num_args() >= 6) { + $lazy = func_get_arg(5); + } else { + if (__CLASS__ !== get_class($this)) { + $r = new \ReflectionMethod($this, __FUNCTION__); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a 6th `$lazy = false` argument in version 4.0. Not defining it is deprecated since 3.3.', get_class($this), __FUNCTION__), E_USER_DEPRECATED); + } + } + $lazy = false; + } $sourceNode = $this->createNode($sourceId, $sourceValue); $destNode = $this->createNode($destId, $destValue); - $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy); $sourceNode->addOutEdge($edge); $destNode->addInEdge($edge); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php index e3c793c4f4eaf..17dd5d9559f9d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php @@ -23,17 +23,20 @@ class ServiceReferenceGraphEdge private $sourceNode; private $destNode; private $value; + private $lazy; /** * @param ServiceReferenceGraphNode $sourceNode * @param ServiceReferenceGraphNode $destNode * @param string $value + * @param bool $lazy */ - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; $this->value = $value; + $this->lazy = $lazy; } /** @@ -65,4 +68,14 @@ public function getDestNode() { return $this->destNode; } + + /** + * Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation. + * + * @return bool + */ + public function isLazy() + { + return $this->lazy; + } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index e5d36a66e2c3a..31b38ae33aaf8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -961,6 +963,19 @@ public function resolveServices($value) foreach ($value as $k => $v) { $value[$k] = $this->resolveServices($v); } + } elseif ($value instanceof IteratorArgument) { + $parameterBag = $this->getParameterBag(); + $value = new RewindableGenerator(function () use ($value, $parameterBag) { + foreach ($value->getValues() as $k => $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + + yield $k => $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v))); + } + }); } elseif ($value instanceof Reference) { $value = $this->get((string) $value, $value->getInvalidBehavior()); } elseif ($value instanceof Definition) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index 9e6b1904b6eeb..9a20525f626c0 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Reference; @@ -111,7 +112,7 @@ private function addEdges() $code = ''; foreach ($this->edges as $id => $edges) { foreach ($edges as $edge) { - $code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed'); + $code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed', $edge['lazy'] ? ' color="#9999ff"' : ''); } } @@ -128,7 +129,7 @@ private function addEdges() * * @return array An array of edges */ - private function findEdges($id, array $arguments, $required, $name) + private function findEdges($id, array $arguments, $required, $name, $lazy = false) { $edges = array(); foreach ($arguments as $argument) { @@ -139,13 +140,19 @@ private function findEdges($id, array $arguments, $required, $name) } if ($argument instanceof Reference) { + $lazyEdge = $lazy; + if (!$this->container->has((string) $argument)) { $this->nodes[(string) $argument] = array('name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']); + } elseif ('service_container' !== (string) $argument) { + $lazyEdge = $lazy || $this->container->getDefinition((string) $argument)->isLazy(); } - $edges[] = array('name' => $name, 'required' => $required, 'to' => $argument); + $edges[] = array('name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge); + } elseif ($argument instanceof ArgumentInterface) { + $edges = array_merge($edges, $this->findEdges($id, $argument->getValues(), $required, $name, true)); } elseif (is_array($argument)) { - $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name)); + $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name, $lazy)); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index a333377482443..7b579e161901e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -781,6 +782,7 @@ private function startClass($class, $baseClass, $namespace) return <<getValues() as $k => $v) { + $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); + foreach (explode("\n", $v) as $v) { + if ($v) { + $code[] = ' '.$v; + } + } + } + $code[] = ' })'; + + return implode("\n", $code); } elseif ($value instanceof Definition) { if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index c3a98720a3a7c..52351e5e6b258 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; @@ -283,6 +284,9 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent if (is_array($value)) { $element->setAttribute('type', 'collection'); $this->convertParameters($value, $type, $element, 'key'); + } elseif ($value instanceof IteratorArgument) { + $element->setAttribute('type', 'iterator'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); } elseif ($value instanceof Reference) { $element->setAttribute('type', 'service'); $element->setAttribute('id', (string) $value); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index d28682c716043..1aa196404d1e4 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -13,6 +13,7 @@ use Symfony\Component\Yaml\Dumper as YmlDumper; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Parameter; @@ -245,6 +246,10 @@ private function dumpCallable($callable) */ private function dumpValue($value) { + if ($value instanceof IteratorArgument) { + $value = $value->getValues(); + } + if (is_array($value)) { $code = array(); foreach ($value as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 2f3b643e448fd..6a9ffafcfb513 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -15,6 +15,7 @@ use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Reference; @@ -395,6 +396,9 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true) case 'collection': $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false); break; + case 'iterator': + $arguments[$key] = new IteratorArgument($this->getArgumentsAsPhp($arg, $name, false)); + break; case 'string': $arguments[$key] = $arg->nodeValue; break; diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index e8f9b98ac03fb..13d6532322ee2 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -189,6 +189,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php index 04fe7c2cf1161..88ddb5fe4ee9a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use Symfony\Component\DependencyInjection\Compiler\RepeatedPass; @@ -59,6 +60,54 @@ public function testProcess() $this->assertSame($ref6, $edges[3]->getValue()); } + public function testProcessMarksEdgesLazyWhenReferencedServiceIsLazy() + { + $container = new ContainerBuilder(); + + $container + ->register('a') + ->setLazy(true) + ->addArgument($ref1 = new Reference('b')) + ; + + $container + ->register('b') + ->addArgument($ref2 = new Reference('a')) + ; + + $graph = $this->process($container); + + $this->assertCount(1, $graph->getNode('b')->getInEdges()); + $this->assertCount(1, $edges = $graph->getNode('a')->getInEdges()); + + $this->assertSame($ref2, $edges[0]->getValue()); + $this->assertTrue($edges[0]->isLazy()); + } + + public function testProcessMarksEdgesLazyWhenReferencedFromIteratorArgument() + { + $container = new ContainerBuilder(); + $container->register('a'); + $container->register('b'); + + $container + ->register('c') + ->addArgument($ref1 = new Reference('a')) + ->addArgument(new IteratorArgument(array($ref2 = new Reference('b')))) + ; + + $graph = $this->process($container); + + $this->assertCount(1, $graph->getNode('a')->getInEdges()); + $this->assertCount(1, $graph->getNode('b')->getInEdges()); + $this->assertCount(2, $edges = $graph->getNode('c')->getOutEdges()); + + $this->assertSame($ref1, $edges[0]->getValue()); + $this->assertFalse($edges[0]->isLazy()); + $this->assertSame($ref2, $edges[1]->getValue()); + $this->assertTrue($edges[1]->isLazy()); + } + public function testProcessDetectsReferencesFromInlinedDefinitions() { $container = new ContainerBuilder(); @@ -78,6 +127,25 @@ public function testProcessDetectsReferencesFromInlinedDefinitions() $this->assertSame($ref, $refs[0]->getValue()); } + public function testProcessDetectsReferencesFromIteratorArguments() + { + $container = new ContainerBuilder(); + + $container + ->register('a') + ; + + $container + ->register('b') + ->addArgument(new IteratorArgument(array($ref = new Reference('a')))) + ; + + $graph = $this->process($container); + + $this->assertCount(1, $refs = $graph->getNode('a')->getInEdges()); + $this->assertSame($ref, $refs[0]->getValue()); + } + public function testProcessDetectsReferencesFromInlinedFactoryDefinitions() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckCircularReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckCircularReferencesPassTest.php index 55351e551c875..0ad77c565f909 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckCircularReferencesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckCircularReferencesPassTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; @@ -115,6 +116,24 @@ public function testProcessIgnoresMethodCalls() $this->process($container); } + public function testProcessIgnoresLazyServices() + { + $container = new ContainerBuilder(); + $container->register('a')->setLazy(true)->addArgument(new Reference('b')); + $container->register('b')->addArgument(new Reference('a')); + + $this->process($container); + } + + public function testProcessIgnoresIteratorArguments() + { + $container = new ContainerBuilder(); + $container->register('a')->addArgument(new Reference('b')); + $container->register('b')->addArgument(new IteratorArgument(array(new Reference('a')))); + + $this->process($container); + } + protected function process(ContainerBuilder $container) { $compiler = new Compiler(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index b591f767ec2de..c91a53473cf6b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -16,6 +16,8 @@ use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -407,6 +409,29 @@ public function testCreateServiceConfigurator() } } + public function testCreateServiceWithIteratorArgument() + { + $builder = new ContainerBuilder(); + $builder->register('bar', 'stdClass'); + $builder + ->register('lazy_context', 'LazyContext') + ->setArguments(array(new IteratorArgument(array('k1' => new Reference('bar'), new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))))) + ; + + $lazyContext = $builder->get('lazy_context'); + $this->assertInstanceOf(RewindableGenerator::class, $lazyContext->lazyValues); + + $i = 0; + foreach ($lazyContext->lazyValues as $k => $v) { + ++$i; + $this->assertEquals('k1', $k); + $this->assertInstanceOf('\stdClass', $v); + } + + // The second argument should have been ignored. + $this->assertEquals(1, $i); + } + /** * @expectedException \RuntimeException */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 7625eabfadc50..231be9df7189d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -13,8 +13,10 @@ use DummyProxyDumper; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; @@ -410,4 +412,51 @@ public function testCircularReferenceAllowanceForInlinedDefinitionsForLazyServic $dumper->setProxyDumper(new DummyProxyDumper()); $dumper->dump(); } + + public function testLazyArgumentProvideGenerator() + { + require_once self::$fixturesPath.'/includes/classes.php'; + + $container = new ContainerBuilder(); + $container->register('lazy_referenced', 'stdClass'); + $container + ->register('lazy_context', 'LazyContext') + ->setArguments(array(new IteratorArgument(array('foo', new Reference('lazy_referenced'), 'k1' => array('foo' => 'bar'), true, 'k2' => new Reference('service_container'))))) + ; + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Lazy_Argument_Provide_Generator'))); + + $container = new \Symfony_DI_PhpDumper_Test_Lazy_Argument_Provide_Generator(); + $lazyContext = $container->get('lazy_context'); + + $this->assertInstanceOf(RewindableGenerator::class, $lazyContext->lazyValues); + + $i = -1; + foreach ($lazyContext->lazyValues as $k => $v) { + switch (++$i) { + case 0: + $this->assertEquals(0, $k); + $this->assertEquals('foo', $v); + break; + case 1: + $this->assertEquals(1, $k); + $this->assertInstanceOf('stdClass', $v); + break; + case 2: + $this->assertEquals('k1', $k); + $this->assertEquals(array('foo' => 'bar'), $v); + break; + case 3: + $this->assertEquals(2, $k); + $this->assertTrue($v); + break; + case 4: + $this->assertEquals('k2', $k); + $this->assertInstanceOf('\Symfony_DI_PhpDumper_Test_Lazy_Argument_Provide_Generator', $v); + break; + } + } + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php index ba25dc3c99361..0d8f957765409 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php @@ -2,6 +2,7 @@ require_once __DIR__.'/../includes/classes.php'; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -129,5 +130,13 @@ ->register('factory_service_simple', 'Bar') ->setFactory(array(new Reference('factory_simple'), 'getInstance')) ; +$container + ->register('lazy_context', 'LazyContext') + ->setArguments(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container'))))) +; +$container + ->register('lazy_context_ignore_invalid_ref', 'LazyContext') + ->setArguments(array(new IteratorArgument(array(new Reference('foo.baz'), new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))))) +; return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot index 3b24ef8ffbca3..c83909d41e7a7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot @@ -26,10 +26,13 @@ digraph sc { node_service_from_static_method [label="service_from_static_method\nBar\\FooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_factory_simple [label="factory_simple\nSimpleFactoryClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_factory_service_simple [label="factory_service_simple\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_lazy_context [label="lazy_context\nLazyContext\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_lazy_context_ignore_invalid_ref [label="lazy_context_ignore_invalid_ref\nLazyContext\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"]; node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"]; node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"]; node_foobaz [label="foobaz\n\n", shape=record, fillcolor="#ff9999", style="filled"]; + node_invalid [label="invalid\n\n", shape=record, fillcolor="#ff9999", style="filled"]; node_foo -> node_foo_baz [label="" style="filled"]; node_foo -> node_service_container [label="" style="filled"]; node_foo -> node_foo_baz [label="" style="dashed"]; @@ -43,4 +46,8 @@ digraph sc { node_inlined -> node_baz [label="setBaz()" style="dashed"]; node_baz -> node_foo_with_inline [label="setFoo()" style="dashed"]; node_configurator_service -> node_baz [label="setFoo()" style="dashed"]; + node_lazy_context -> node_foo_baz [label="" style="filled" color="#9999ff"]; + node_lazy_context -> node_service_container [label="" style="filled" color="#9999ff"]; + node_lazy_context_ignore_invalid_ref -> node_foo_baz [label="" style="filled" color="#9999ff"]; + node_lazy_context_ignore_invalid_ref -> node_invalid [label="" style="filled" color="#9999ff"]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index 0ecdea3fbfb89..ffa2a896a8c72 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -97,3 +97,13 @@ public function getProxyCode(Definition $definition) return ''; } } + +class LazyContext +{ + public $lazyValues; + + public function __construct($lazyValues) + { + $this->lazyValues = $lazyValues; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php index 8f0d484866630..2cea61dec78fd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php @@ -1,6 +1,7 @@ 'getFooBarService', 'foo_with_inline' => 'getFooWithInlineService', 'inlined' => 'getInlinedService', + 'lazy_context' => 'getLazyContextService', + 'lazy_context_ignore_invalid_ref' => 'getLazyContextIgnoreInvalidRefService', 'method_call1' => 'getMethodCall1Service', 'new_factory' => 'getNewFactoryService', 'new_factory_service' => 'getNewFactoryServiceService', @@ -284,6 +287,43 @@ protected function getFooWithInlineService() return $instance; } + /** + * Gets the 'lazy_context' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \LazyContext A LazyContext instance + */ + protected function getLazyContextService() + { + return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function() { + yield 0 => 'foo'; + yield 1 => $this->get('foo.baz'); + yield 2 => array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo').'', 'foobar' => $this->getParameter('foo')); + yield 3 => true; + yield 4 => $this; + })); + } + + /** + * Gets the 'lazy_context_ignore_invalid_ref' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \LazyContext A LazyContext instance + */ + protected function getLazyContextIgnoreInvalidRefService() + { + return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function() { + yield 0 => $this->get('foo.baz'); + if ($this->has('invalid')) { + yield 1 => $this->get('invalid', ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + })); + } + /** * Gets the 'method_call1' service. * 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 f8b263c2a01f3..3c85f6a3aa1ec 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -1,5 +1,6 @@ 'getFoo_BazService', 'foo_bar' => 'getFooBarService', 'foo_with_inline' => 'getFooWithInlineService', + 'lazy_context' => 'getLazyContextService', + 'lazy_context_ignore_invalid_ref' => 'getLazyContextIgnoreInvalidRefService', 'method_call1' => 'getMethodCall1Service', 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', @@ -283,6 +286,40 @@ protected function getFooWithInlineService() return $instance; } + /** + * Gets the 'lazy_context' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \LazyContext A LazyContext instance + */ + protected function getLazyContextService() + { + return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function() { + yield 0 => 'foo'; + yield 1 => $this->get('foo.baz'); + yield 2 => array('bar' => 'foo is '.'bar'.'', 'foobar' => 'bar'); + yield 3 => true; + yield 4 => $this; + })); + } + + /** + * Gets the 'lazy_context_ignore_invalid_ref' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \LazyContext A LazyContext instance + */ + protected function getLazyContextIgnoreInvalidRefService() + { + return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function() { + yield 0 => $this->get('foo.baz'); + })); + } + /** * Gets the 'method_call1' service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml index 9ad09ad5918dd..fe17df4f68025 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml @@ -115,6 +115,24 @@ + + + foo + + + foo is %foo% + %foo% + + true + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index 44a174b6edb5b..223302598bdb9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -107,5 +107,11 @@ services: factory_service_simple: class: Bar factory: ['@factory_simple', getInstance] + lazy_context: + class: LazyContext + arguments: [[foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container']] + lazy_context_ignore_invalid_ref: + class: LazyContext + arguments: [['@foo.baz', '@?invalid']] alias_for_foo: '@foo' alias_for_alias: '@foo' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 9e601666a27be..bcec89958851f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -257,6 +258,17 @@ public function testLoadServices() $this->assertEquals(array('decorated', 'decorated.pif-pouf', 5), $services['decorator_service_with_name_and_priority']->getDecoratedService()); } + public function testParsesLazyArgument() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services9.xml'); + + $lazyDefinition = $container->getDefinition('lazy_context'); + + $this->assertEquals(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container')))), $lazyDefinition->getArguments(), '->load() parses lazy arguments'); + } + public function testParsesTags() { $container = new ContainerBuilder(); From 1dbf52ad0340fde324d82eb1279ffbdd96b5f4ef Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 5 Jan 2017 20:01:21 +0100 Subject: [PATCH 2/2] [DI] Add "=iterator" arguments to Yaml loader --- .../Component/DependencyInjection/CHANGELOG.md | 2 ++ .../DependencyInjection/Dumper/YamlDumper.php | 2 +- .../DependencyInjection/Loader/YamlFileLoader.php | 13 ++++++++++++- .../Tests/Fixtures/yaml/services9.yml | 4 ++-- .../Tests/Loader/XmlFileLoaderTest.php | 2 +- .../Tests/Loader/YamlFileLoaderTest.php | 12 ++++++++++++ 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index aaa9574ef8f77..577bd28ac637b 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 3.3.0 ----- + * Add "iterator" argument type for lazy iteration over a set of values and services + * Using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and will not be supported anymore in 4.0. diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 1aa196404d1e4..401c179f3aa4f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -247,7 +247,7 @@ private function dumpCallable($callable) private function dumpValue($value) { if ($value instanceof IteratorArgument) { - $value = $value->getValues(); + $value = array('=iterator' => $value->getValues()); } if (is_array($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 6f6f4a3d590b2..7eec03a20f7f1 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -455,7 +456,17 @@ private function validate($content, $file) private function resolveServices($value) { if (is_array($value)) { - $value = array_map(array($this, 'resolveServices'), $value); + if (array_key_exists('=iterator', $value)) { + if (1 !== count($value)) { + throw new InvalidArgumentException('Arguments typed "=iterator" must have no sibling keys.'); + } + if (!is_array($value['=iterator'])) { + throw new InvalidArgumentException('Arguments typed "=iterator" must be arrays.'); + } + $value = new IteratorArgument(array_map(array($this, 'resolveServices'), $value['=iterator'])); + } else { + $value = array_map(array($this, 'resolveServices'), $value); + } } elseif (is_string($value) && 0 === strpos($value, '@=')) { return new Expression(substr($value, 2)); } elseif (is_string($value) && 0 === strpos($value, '@')) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index 223302598bdb9..3c517354eea0e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -109,9 +109,9 @@ services: factory: ['@factory_simple', getInstance] lazy_context: class: LazyContext - arguments: [[foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container']] + arguments: [{ '=iterator': [foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container'] }] lazy_context_ignore_invalid_ref: class: LazyContext - arguments: [['@foo.baz', '@?invalid']] + arguments: [{ '=iterator': ['@foo.baz', '@?invalid'] }] alias_for_foo: '@foo' alias_for_alias: '@foo' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index bcec89958851f..5c687d6e7b1ad 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -258,7 +258,7 @@ public function testLoadServices() $this->assertEquals(array('decorated', 'decorated.pif-pouf', 5), $services['decorator_service_with_name_and_priority']->getDecoratedService()); } - public function testParsesLazyArgument() + public function testParsesIteratorArgument() { $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 1c810872a11e3..8ce3a9f9f68aa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -316,6 +317,17 @@ public function testTypes() $this->assertEquals(array('Foo'), $container->getDefinition('baz_service')->getAutowiringTypes()); } + public function testParsesIteratorArgument() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services9.yml'); + + $lazyDefinition = $container->getDefinition('lazy_context'); + + $this->assertEquals(array(new IteratorArgument(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'foobar' => '%foo%'), true, new Reference('service_container')))), $lazyDefinition->getArguments(), '->load() parses lazy arguments'); + } + public function testAutowire() { $container = new ContainerBuilder();