From f23e460faddb9fca214ff9761388d0023a4ab349 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Sun, 29 Jan 2017 20:10:36 +0100 Subject: [PATCH] [DI] Allow to count on lazy collection arguments --- .../Argument/RewindableGenerator.php | 19 ++++++- .../DependencyInjection/ContainerBuilder.php | 13 +++++ .../DependencyInjection/Dumper/PhpDumper.php | 40 +++++++++++--- .../Argument/RewindableGeneratorTest.php | 52 +++++++++++++++++++ .../Tests/ContainerBuilderTest.php | 1 + .../Tests/Fixtures/php/services9.php | 8 +-- .../Tests/Fixtures/php/services9_compiled.php | 8 +-- 7 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Argument/RewindableGeneratorTest.php diff --git a/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php b/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php index ea6ec4444d3d6..e162a7c34deca 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php +++ b/src/Symfony/Component/DependencyInjection/Argument/RewindableGenerator.php @@ -14,13 +14,19 @@ /** * @internal */ -class RewindableGenerator implements \IteratorAggregate +class RewindableGenerator implements \IteratorAggregate, \Countable { private $generator; + private $count; - public function __construct(callable $generator) + /** + * @param callable $generator + * @param int|callable $count + */ + public function __construct(callable $generator, $count) { $this->generator = $generator; + $this->count = $count; } public function getIterator() @@ -29,4 +35,13 @@ public function getIterator() return $g(); } + + public function count() + { + if (is_callable($count = $this->count)) { + $this->count = $count(); + } + + return $this->count; + } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index bde38b8413f29..2e5d6014a7bde 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1000,6 +1000,19 @@ public function resolveServices($value) yield $k => $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v))); } + }, function () use ($value) { + $count = 0; + foreach ($value->getValues() as $v) { + foreach (self::getServiceConditionals($v) as $s) { + if (!$this->has($s)) { + continue 2; + } + } + + ++$count; + } + + return $count; }); } elseif ($value instanceof ClosureProxyArgument) { $parameterBag = $this->getParameterBag(); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 0597bd8c426ad..f31b778e77d09 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1362,19 +1362,36 @@ private function instantiateProxy($class, $args, $useConstructor) */ private function wrapServiceConditionals($value, $code, &$isUnconditional = null, $containerRef = '$this') { - if ($isUnconditional = !$services = ContainerBuilder::getServiceConditionals($value)) { + if ($isUnconditional = !$condition = $this->getServiceConditionals($value, $containerRef)) { return $code; } + // re-indent the wrapped code + $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + + return sprintf(" if (%s) {\n%s }\n", $condition, $code); + } + + /** + * Get the conditions to execute for conditional services. + * + * @param string $value + * @param string $containerRef + * + * @return null|string + */ + private function getServiceConditionals($value, $containerRef = '$this') + { + if (!$services = ContainerBuilder::getServiceConditionals($value)) { + return null; + } + $conditions = array(); foreach ($services as $service) { $conditions[] = sprintf("%s->has('%s')", $containerRef, $service); } - // re-indent the wrapped code - $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); - - return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code); + return implode(' && ', $conditions); } /** @@ -1524,9 +1541,14 @@ private function dumpValue($value, $interpolate = true) return sprintf('array(%s)', implode(', ', $code)); } elseif ($value instanceof IteratorArgument) { + $countCode = array(); + $countCode[] = 'function () {'; + $operands = array(0); + $code = array(); - $code[] = 'new RewindableGenerator(function() {'; + $code[] = 'new RewindableGenerator(function () {'; foreach ($value->getValues() as $k => $v) { + ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; $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) { @@ -1534,7 +1556,11 @@ private function dumpValue($value, $interpolate = true) } } } - $code[] = ' })'; + + $countCode[] = sprintf(' return %s;', implode(' + ', $operands)); + $countCode[] = ' }'; + + $code[] = sprintf(' }, %s)', count($operands) > 1 ? implode("\n", $countCode) : $operands[0]); return implode("\n", $code); } elseif ($value instanceof Definition) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Argument/RewindableGeneratorTest.php b/src/Symfony/Component/DependencyInjection/Tests/Argument/RewindableGeneratorTest.php new file mode 100644 index 0000000000000..43adf0d92f4a4 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Argument/RewindableGeneratorTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Argument; + +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; + +class RewindableGeneratorTest extends \PHPUnit_Framework_TestCase +{ + public function testImplementsCountable() + { + $this->assertInstanceOf(\Countable::class, new RewindableGenerator(function () { + yield 1; + }, 1)); + } + + public function testCountUsesProvidedValue() + { + $generator = new RewindableGenerator(function () { + yield 1; + }, 3); + + $this->assertCount(3, $generator); + } + + public function testCountUsesProvidedValueAsCallback() + { + $called = 0; + $generator = new RewindableGenerator(function () { + yield 1; + }, function () use (&$called) { + ++$called; + + return 3; + }); + + $this->assertSame(0, $called, 'Count callback is called lazily'); + $this->assertCount(3, $generator); + + count($generator); + + $this->assertSame(1, $called, 'Count callback is called only once'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 6922fbe9a4757..c031335a01c28 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -422,6 +422,7 @@ public function testCreateServiceWithIteratorArgument() $lazyContext = $builder->get('lazy_context'); $this->assertInstanceOf(RewindableGenerator::class, $lazyContext->lazyValues); + $this->assertCount(1, $lazyContext->lazyValues); $i = 0; foreach ($lazyContext->lazyValues as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index ec90404300a97..91afec33403cf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -314,13 +314,13 @@ protected function getFooWithInlineService() */ protected function getLazyContextService() { - return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function() { + 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($this->getParameter('foo') => 'foo is '.$this->getParameter('foo').'', 'foobar' => $this->getParameter('foo')); yield 3 => true; yield 4 => $this; - })); + }, 5)); } /** @@ -333,11 +333,13 @@ protected function getLazyContextService() */ protected function getLazyContextIgnoreInvalidRefService() { - return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function() { + return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { yield 0 => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->get('foo.baz')) && false ?: '_'}; if ($this->has('invalid')) { yield 1 => $this->get('invalid', ContainerInterface::NULL_ON_INVALID_REFERENCE); } + }, function () { + return 1 + (int) ($this->has('invalid')); })); } 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 c861ec1abf7c6..acaebde4a77e6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -313,13 +313,13 @@ protected function getFooWithInlineService() */ protected function getLazyContextService() { - return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function() { + 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 3 => true; yield 4 => $this; - })); + }, 5)); } /** @@ -332,9 +332,9 @@ protected function getLazyContextService() */ protected function getLazyContextIgnoreInvalidRefService() { - return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function() { + return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { yield 0 => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->get('foo.baz')) && false ?: '_'}; - })); + }, 1)); } /**