Skip to content

Commit b2dadc1

Browse files
Merge branch '4.3' into 4.4
* 4.3: [DI] Fix dumping Doctrine-like service graphs (bis) Failing test case for complex near-circular situation + lazy
2 parents f453503 + 5196254 commit b2dadc1

File tree

7 files changed

+282
-43
lines changed

7 files changed

+282
-43
lines changed

src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ protected function processValue($value, $isRoot = false)
136136
$this->lazy = false;
137137

138138
$byConstructor = $this->byConstructor;
139-
$this->byConstructor = true;
139+
$this->byConstructor = $isRoot || $byConstructor;
140140
$this->processValue($value->getFactory());
141141
$this->processValue($value->getArguments());
142142

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ public function dump(array $options = [])
187187
}
188188
$this->container->getCompiler()->getServiceReferenceGraph()->clear();
189189
$checkedNodes = [];
190+
$this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences);
190191

191192
$this->docStar = $options['debug'] ? '*' : '';
192193

@@ -339,10 +340,10 @@ private function getProxyDumper(): ProxyDumper
339340
return $this->proxyDumper;
340341
}
341342

342-
private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = [])
343+
private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = [], $byConstructor = true)
343344
{
344345
$checkedNodes[$sourceId] = true;
345-
$currentPath[$sourceId] = $sourceId;
346+
$currentPath[$sourceId] = $byConstructor;
346347

347348
foreach ($edges as $edge) {
348349
$node = $edge->getDestNode();
@@ -351,44 +352,52 @@ private function analyzeCircularReferences($sourceId, array $edges, &$checkedNod
351352
if (!$node->getValue() instanceof Definition || $sourceId === $id || $edge->isLazy() || $edge->isWeak()) {
352353
// no-op
353354
} elseif (isset($currentPath[$id])) {
354-
$currentId = $id;
355-
foreach (array_reverse($currentPath) as $parentId) {
356-
$this->circularReferences[$parentId][$currentId] = $currentId;
357-
if ($parentId === $id) {
358-
break;
359-
}
360-
$currentId = $parentId;
361-
}
355+
$this->addCircularReferences($id, $currentPath, $edge->isReferencedByConstructor());
362356
} elseif (!isset($checkedNodes[$id])) {
363-
$this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath);
357+
$this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath, $edge->isReferencedByConstructor());
364358
} elseif (isset($this->circularReferences[$id])) {
365-
$this->connectCircularReferences($id, $currentPath);
359+
$this->connectCircularReferences($id, $currentPath, $edge->isReferencedByConstructor());
366360
}
367361
}
368362
unset($currentPath[$sourceId]);
369363
}
370364

371-
private function connectCircularReferences($sourceId, &$currentPath, &$subPath = [])
365+
private function connectCircularReferences($sourceId, &$currentPath, $byConstructor, &$subPath = [])
372366
{
373-
$subPath[$sourceId] = $sourceId;
374-
$currentPath[$sourceId] = $sourceId;
367+
$currentPath[$sourceId] = $subPath[$sourceId] = $byConstructor;
375368

376-
foreach ($this->circularReferences[$sourceId] as $id) {
369+
foreach ($this->circularReferences[$sourceId] as $id => $byConstructor) {
377370
if (isset($currentPath[$id])) {
378-
$currentId = $id;
379-
foreach (array_reverse($currentPath) as $parentId) {
380-
$this->circularReferences[$parentId][$currentId] = $currentId;
381-
if ($parentId === $id) {
382-
break;
383-
}
384-
$currentId = $parentId;
385-
}
371+
$this->addCircularReferences($id, $currentPath, $byConstructor);
386372
} elseif (!isset($subPath[$id]) && isset($this->circularReferences[$id])) {
387-
$this->connectCircularReferences($id, $currentPath, $subPath);
373+
$this->connectCircularReferences($id, $currentPath, $byConstructor, $subPath);
388374
}
389375
}
390-
unset($currentPath[$sourceId]);
391-
unset($subPath[$sourceId]);
376+
unset($currentPath[$sourceId], $subPath[$sourceId]);
377+
}
378+
379+
private function addCircularReferences($id, $currentPath, $byConstructor)
380+
{
381+
$currentPath[$id] = $byConstructor;
382+
$circularRefs = [];
383+
384+
foreach (array_reverse($currentPath) as $parentId => $v) {
385+
$byConstructor = $byConstructor && $v;
386+
$circularRefs[] = $parentId;
387+
388+
if ($parentId === $id) {
389+
break;
390+
}
391+
}
392+
393+
$currentId = $id;
394+
foreach ($circularRefs as $parentId) {
395+
if (empty($this->circularReferences[$parentId][$currentId])) {
396+
$this->circularReferences[$parentId][$currentId] = $byConstructor;
397+
}
398+
399+
$currentId = $parentId;
400+
}
392401
}
393402

394403
private function collectLineage($class, array &$lineage)
@@ -679,7 +688,6 @@ private function addService(string $id, Definition $definition): array
679688
$autowired = $definition->isAutowired() ? ' autowired' : '';
680689

681690
if ($definition->isLazy()) {
682-
unset($this->circularReferences[$id]);
683691
$lazyInitialization = '$lazyLoad = true';
684692
} else {
685693
$lazyInitialization = '';
@@ -755,12 +763,12 @@ private function addInlineVariables(string $id, Definition $definition, array $a
755763

756764
private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string
757765
{
758-
list($callCount, $behavior) = $this->serviceCalls[$targetId];
759-
760766
while ($this->container->hasAlias($targetId)) {
761767
$targetId = (string) $this->container->getAlias($targetId);
762768
}
763769

770+
list($callCount, $behavior) = $this->serviceCalls[$targetId];
771+
764772
if ($id === $targetId) {
765773
return $this->addInlineService($id, $definition, $definition);
766774
}
@@ -769,9 +777,13 @@ private function addInlineReference(string $id, Definition $definition, string $
769777
return '';
770778
}
771779

772-
$hasSelfRef = isset($this->circularReferences[$id][$targetId]);
773-
$forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]);
774-
$code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : '';
780+
$hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]);
781+
782+
if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) {
783+
$code = $this->addInlineService($id, $definition, $definition);
784+
} else {
785+
$code = '';
786+
}
775787

776788
if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) {
777789
return $code;
@@ -804,15 +816,23 @@ private function addInlineReference(string $id, Definition $definition, string $
804816

805817
private function addInlineService(string $id, Definition $definition, Definition $inlineDef = null, bool $forConstructor = true): string
806818
{
807-
$isSimpleInstance = $isRootInstance = null === $inlineDef;
819+
$code = '';
820+
821+
if ($isSimpleInstance = $isRootInstance = null === $inlineDef) {
822+
foreach ($this->serviceCalls as $targetId => list($callCount, $behavior, $byConstructor)) {
823+
if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) {
824+
$code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor);
825+
}
826+
}
827+
}
808828

809829
if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) {
810-
return '';
830+
return $code;
811831
}
812832

813833
$arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()];
814834

815-
$code = $this->addInlineVariables($id, $definition, $arguments, $forConstructor);
835+
$code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor);
816836

817837
if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) {
818838
$isSimpleInstance = false;
@@ -1473,20 +1493,24 @@ private function getServiceConditionals($value): string
14731493
return implode(' && ', $conditions);
14741494
}
14751495

1476-
private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = []): \SplObjectStorage
1496+
private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], bool $byConstructor = null): \SplObjectStorage
14771497
{
14781498
if (null === $definitions) {
14791499
$definitions = new \SplObjectStorage();
14801500
}
14811501

14821502
foreach ($arguments as $argument) {
14831503
if (\is_array($argument)) {
1484-
$this->getDefinitionsFromArguments($argument, $definitions, $calls);
1504+
$this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor);
14851505
} elseif ($argument instanceof Reference) {
14861506
$id = (string) $argument;
14871507

1508+
while ($this->container->hasAlias($id)) {
1509+
$id = (string) $this->container->getAlias($id);
1510+
}
1511+
14881512
if (!isset($calls[$id])) {
1489-
$calls[$id] = [0, $argument->getInvalidBehavior()];
1513+
$calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor];
14901514
} else {
14911515
$calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior());
14921516
}
@@ -1498,8 +1522,10 @@ private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage
14981522
$definitions[$argument] = 1 + $definitions[$argument];
14991523
} else {
15001524
$definitions[$argument] = 1;
1501-
$arguments = [$argument->getArguments(), $argument->getFactory(), $argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()];
1502-
$this->getDefinitionsFromArguments($arguments, $definitions, $calls);
1525+
$arguments = [$argument->getArguments(), $argument->getFactory()];
1526+
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor);
1527+
$arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()];
1528+
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor);
15031529
}
15041530
}
15051531

@@ -1620,6 +1646,11 @@ private function dumpValue($value, bool $interpolate = true): string
16201646
return '$'.$value;
16211647
} elseif ($value instanceof Reference) {
16221648
$id = (string) $value;
1649+
1650+
while ($this->container->hasAlias($id)) {
1651+
$id = (string) $this->container->getAlias($id);
1652+
}
1653+
16231654
if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) {
16241655
return $this->dumpValue($this->referenceVariables[$id], $interpolate);
16251656
}

src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,13 @@ public function testAlmostCircular($visibility)
14301430
$this->assertEquals((object) ['bar6' => (object) []], $foo6);
14311431

14321432
$this->assertInstanceOf(\stdClass::class, $container->get('root'));
1433+
1434+
$manager3 = $container->get('manager3');
1435+
$listener3 = $container->get('listener3');
1436+
$this->assertSame($manager3, $listener3->manager, 'Both should identically be the manager3 service');
1437+
1438+
$listener4 = $container->get('listener4');
1439+
$this->assertInstanceOf('stdClass', $listener4);
14331440
}
14341441

14351442
public function provideAlmostCircular()

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,13 @@ public function testAlmostCircular($visibility)
996996
$this->assertEquals((object) ['bar6' => (object) []], $foo6);
997997

998998
$this->assertInstanceOf(\stdClass::class, $container->get('root'));
999+
1000+
$manager3 = $container->get('manager3');
1001+
$listener3 = $container->get('listener3');
1002+
$this->assertSame($manager3, $listener3->manager);
1003+
1004+
$listener4 = $container->get('listener4');
1005+
$this->assertInstanceOf('stdClass', $listener4);
9991006
}
10001007

10011008
public function provideAlmostCircular()

src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
use Symfony\Component\DependencyInjection\ContainerBuilder;
44
use Symfony\Component\DependencyInjection\Definition;
5-
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
65
use Symfony\Component\DependencyInjection\Reference;
76
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls;
87

@@ -102,6 +101,35 @@
102101
$container->register('subscriber2', 'stdClass')->setPublic(false)
103102
->addArgument(new Reference('manager2'));
104103

104+
// doctrine-like event system with listener
105+
106+
$container->register('manager3', 'stdClass')
107+
->setLazy(true)
108+
->setPublic(true)
109+
->addArgument(new Reference('connection3'));
110+
111+
$container->register('connection3', 'stdClass')
112+
->setPublic($public)
113+
->setProperty('listener', [new Reference('listener3')]);
114+
115+
$container->register('listener3', 'stdClass')
116+
->setPublic(true)
117+
->setProperty('manager', new Reference('manager3'));
118+
119+
// doctrine-like event system with small differences
120+
121+
$container->register('manager4', 'stdClass')
122+
->setLazy(true)
123+
->addArgument(new Reference('connection4'));
124+
125+
$container->register('connection4', 'stdClass')
126+
->setPublic($public)
127+
->setProperty('listener', [new Reference('listener4')]);
128+
129+
$container->register('listener4', 'stdClass')
130+
->setPublic(true)
131+
->addArgument(new Reference('manager4'));
132+
105133
// private service involved in a loop
106134

107135
$container->register('foo6', 'stdClass')

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy