[DI] Allow to count on lazy collection arguments

This commit is contained in:
Maxime Steinhausser 2017-01-29 20:10:36 +01:00 committed by Maxime Steinhausser
parent 2183f98f54
commit f23e460fad
7 changed files with 125 additions and 16 deletions

View File

@ -14,13 +14,19 @@ namespace Symfony\Component\DependencyInjection\Argument;
/**
* @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 @@ class RewindableGenerator implements \IteratorAggregate
return $g();
}
public function count()
{
if (is_callable($count = $this->count)) {
$this->count = $count();
}
return $this->count;
}
}

View File

@ -1000,6 +1000,19 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
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();

View File

@ -1362,19 +1362,36 @@ EOF;
*/
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 @@ EOF;
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 @@ EOF;
}
}
}
$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) {

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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');
}
}

View File

@ -422,6 +422,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$lazyContext = $builder->get('lazy_context');
$this->assertInstanceOf(RewindableGenerator::class, $lazyContext->lazyValues);
$this->assertCount(1, $lazyContext->lazyValues);
$i = 0;
foreach ($lazyContext->lazyValues as $k => $v) {

View File

@ -314,13 +314,13 @@ class ProjectServiceContainer extends Container
*/
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 @@ class ProjectServiceContainer extends Container
*/
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'));
}));
}

View File

@ -313,13 +313,13 @@ class ProjectServiceContainer extends Container
*/
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 @@ class ProjectServiceContainer extends Container
*/
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));
}
/**