Skip to content

Commit 41edb07

Browse files
committed
feature #20907 [DependencyInjection] Implement lazy collection type using generators (tgalopin, nicolas-grekas)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DependencyInjection] Implement lazy collection type using generators | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | - | Fixed tickets | #20875 | License | MIT | Doc PR | - Following the RFC symfony/symfony#20875, this PR aims to implement lazy services collections using generators internally. For now, I suggest to support only XML and PHP definitions. Supporting YAML means adding another convention to the language and this is not the aim of this PR. - [x] value object holding the semantic (IteratorArgument) - [x] iterator dumping - [x] lazyness awareness in CheckCircularReferencesPass & GraphvizDumper - [x] rewindable iterators - [x] `*_ON_INVALID_REFERENCE` behavior - [x] ContainerBuilder::createService - [x] ArgumentInterface handling in compiler passes Commits ------- 1dbf52ad03 [DI] Add "=iterator" arguments to Yaml loader 5313943692 [DependencyInjection] Implement lazy collection type using generators
2 parents 7adf35c + 1237686 commit 41edb07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+557
-15
lines changed

Argument/ArgumentInterface.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* Represents a complex argument containing nested values.
16+
*
17+
* @author Titouan Galopin <galopintitouan@gmail.com>
18+
*/
19+
interface ArgumentInterface
20+
{
21+
/**
22+
* @return array
23+
*/
24+
public function getValues();
25+
26+
public function setValues(array $values);
27+
}

Argument/IteratorArgument.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* Represents a collection of values to lazily iterate over.
16+
*
17+
* @author Titouan Galopin <galopintitouan@gmail.com>
18+
*/
19+
class IteratorArgument implements ArgumentInterface
20+
{
21+
private $values;
22+
23+
public function __construct(array $values)
24+
{
25+
$this->values = $values;
26+
}
27+
28+
/**
29+
* @return array The values to lazily iterate over
30+
*/
31+
public function getValues()
32+
{
33+
return $this->values;
34+
}
35+
36+
/**
37+
* @param array $values The values to lazily iterate over
38+
*/
39+
public function setValues(array $values)
40+
{
41+
$this->values = $values;
42+
}
43+
}

Argument/RewindableGenerator.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* @internal
16+
*/
17+
class RewindableGenerator implements \IteratorAggregate
18+
{
19+
private $generator;
20+
21+
public function __construct(callable $generator)
22+
{
23+
$this->generator = $generator;
24+
}
25+
26+
public function getIterator()
27+
{
28+
$g = $this->generator;
29+
30+
return $g();
31+
}
32+
}

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
3.3.0
55
-----
66

7+
* Add "iterator" argument type for lazy iteration over a set of values and services
8+
79
* Using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and
810
will not be supported anymore in 4.0.
911

Compiler/AnalyzeServiceReferencesPass.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1415
use Symfony\Component\DependencyInjection\Definition;
1516
use Symfony\Component\DependencyInjection\Reference;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -91,19 +92,25 @@ public function process(ContainerBuilder $container)
9192
* Processes service definitions for arguments to find relationships for the service graph.
9293
*
9394
* @param array $arguments An array of Reference or Definition objects relating to service definitions
95+
* @param bool $lazy Whether the references nested in the arguments should be considered lazy or not
9496
*/
95-
private function processArguments(array $arguments)
97+
private function processArguments(array $arguments, $lazy = false)
9698
{
9799
foreach ($arguments as $argument) {
98100
if (is_array($argument)) {
99-
$this->processArguments($argument);
101+
$this->processArguments($argument, $lazy);
102+
} elseif ($argument instanceof ArgumentInterface) {
103+
$this->processArguments($argument->getValues(), true);
100104
} elseif ($argument instanceof Reference) {
105+
$targetDefinition = $this->getDefinition((string) $argument);
106+
101107
$this->graph->connect(
102108
$this->currentId,
103109
$this->currentDefinition,
104110
$this->getDefinitionId((string) $argument),
105-
$this->getDefinition((string) $argument),
106-
$argument
111+
$targetDefinition,
112+
$argument,
113+
$lazy || ($targetDefinition && $targetDefinition->isLazy())
107114
);
108115
} elseif ($argument instanceof Definition) {
109116
$this->processArguments($argument->getArguments());

Compiler/CheckCircularReferencesPass.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ private function checkOutEdges(array $edges)
6060
$id = $node->getId();
6161

6262
if (empty($this->checkedNodes[$id])) {
63-
64-
// don't check circular dependencies for lazy services
65-
if (!$node->getValue() || !$node->getValue()->isLazy()) {
63+
// Don't check circular references for lazy edges
64+
if (!$node->getValue() || !$edge->isLazy()) {
6665
$searchKey = array_search($id, $this->currentPath);
6766
$this->currentPath[] = $id;
6867

Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1415
use Symfony\Component\DependencyInjection\Definition;
1516
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1617
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -49,6 +50,8 @@ private function processReferences(array $arguments)
4950
foreach ($arguments as $argument) {
5051
if (is_array($argument)) {
5152
$this->processReferences($argument);
53+
} elseif ($argument instanceof ArgumentInterface) {
54+
$this->processReferences($argument->getValues());
5255
} elseif ($argument instanceof Definition) {
5356
$this->processDefinition($argument);
5457
} elseif ($argument instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $argument->getInvalidBehavior()) {

Compiler/CheckReferenceValidityPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1415
use Symfony\Component\DependencyInjection\Definition;
1516
use Symfony\Component\DependencyInjection\Reference;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -63,6 +64,8 @@ private function validateReferences(array $arguments)
6364
foreach ($arguments as $argument) {
6465
if (is_array($argument)) {
6566
$this->validateReferences($argument);
67+
} elseif ($argument instanceof ArgumentInterface) {
68+
$this->validateReferences($argument->getValues());
6669
} elseif ($argument instanceof Reference) {
6770
$targetDefinition = $this->getDefinition((string) $argument);
6871

Compiler/InlineServiceDefinitionsPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1415
use Symfony\Component\DependencyInjection\Definition;
1516
use Symfony\Component\DependencyInjection\Reference;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -67,6 +68,8 @@ private function inlineArguments(ContainerBuilder $container, array $arguments,
6768
}
6869
if (is_array($argument)) {
6970
$arguments[$k] = $this->inlineArguments($container, $argument);
71+
} elseif ($argument instanceof ArgumentInterface) {
72+
$argument->setValues($this->inlineArguments($container, $argument->getValues()));
7073
} elseif ($argument instanceof Reference) {
7174
if (!$container->hasDefinition($id = (string) $argument)) {
7275
continue;

Compiler/ReplaceAliasByActualDefinitionPass.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1617
use Symfony\Component\DependencyInjection\Reference;
@@ -98,6 +99,10 @@ private function updateArgumentReferences(array $replacements, $definitionId, ar
9899
$arguments[$k] = $this->updateArgumentReferences($replacements, $definitionId, $argument);
99100
continue;
100101
}
102+
if ($argument instanceof ArgumentInterface) {
103+
$argument->setValues($this->updateArgumentReferences($replacements, $definitionId, $argument->getValues()));
104+
continue;
105+
}
101106
// Skip arguments that don't need replacement
102107
if (!$argument instanceof Reference) {
103108
continue;

0 commit comments

Comments
 (0)