Skip to content

[DependencyInjection] Implement lazy collection type using generators #20907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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\Argument;

/**
* Represents a complex argument containing nested values.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
interface ArgumentInterface
{
/**
* @return array
*/
public function getValues();

public function setValues(array $values);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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\Argument;

/**
* Represents a collection of values to lazily iterate over.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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\Argument;

/**
* @internal
*/
class RewindableGenerator implements \IteratorAggregate
{
private $generator;

public function __construct(callable $generator)
{
$this->generator = $generator;
}

public function getIterator()
{
$g = $this->generator;

return $g();
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
}
15 changes: 15 additions & 0 deletions src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Loading