From 28c5017e3ae7c3b95beb5c4dcda1dcf4991a39d1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 6 Nov 2018 09:48:20 +0100 Subject: [PATCH 1/8] [DI] fix GraphvizDumper ignoring inline definitions --- Dumper/GraphvizDumper.php | 8 +++++++ Tests/Dumper/GraphvizDumperTest.php | 26 ++++++++++++++++----- Tests/Fixtures/graphviz/services_inline.dot | 10 ++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 Tests/Fixtures/graphviz/services_inline.dot diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 9a087991c..2105d1d40 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -149,6 +149,14 @@ private function findEdges($id, array $arguments, $required, $name, $lazy = fals $edges[] = array('name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge); } elseif ($argument instanceof ArgumentInterface) { $edges = array_merge($edges, $this->findEdges($id, $argument->getValues(), $required, $name, true)); + } elseif ($argument instanceof Definition) { + $edges = array_merge($edges, + $this->findEdges($id, $argument->getArguments(), $required, ''), + $this->findEdges($id, $argument->getProperties(), false, '') + ); + foreach ($argument->getMethodCalls() as $call) { + $edges = array_merge($edges, $this->findEdges($id, $call[1], false, $call[0].'()')); + } } elseif (\is_array($argument)) { $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name, $lazy)); } diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index ffdd0730c..3c40bb550 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -13,7 +13,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\GraphvizDumper; +use Symfony\Component\DependencyInjection\Reference; class GraphvizDumperTest extends TestCase { @@ -32,11 +34,11 @@ public function testDump() $container = include self::$fixturesPath.'/containers/container9.php'; $dumper = new GraphvizDumper($container); - $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services9.dot')), $dumper->dump(), '->dump() dumps services'); + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services9.dot', $dumper->dump(), '->dump() dumps services'); $container = include self::$fixturesPath.'/containers/container10.php'; $dumper = new GraphvizDumper($container); - $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services10.dot')), $dumper->dump(), '->dump() dumps services'); + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services10.dot', $dumper->dump(), '->dump() dumps services'); $container = include self::$fixturesPath.'/containers/container10.php'; $dumper = new GraphvizDumper($container); @@ -47,21 +49,21 @@ public function testDump() 'node.instance' => array('fillcolor' => 'green', 'style' => 'empty'), 'node.definition' => array('fillcolor' => 'grey'), 'node.missing' => array('fillcolor' => 'red', 'style' => 'empty'), - )), str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services10-1.dot')), '->dump() dumps services'); + )), file_get_contents(self::$fixturesPath.'/graphviz/services10-1.dot'), '->dump() dumps services'); } public function testDumpWithFrozenContainer() { $container = include self::$fixturesPath.'/containers/container13.php'; $dumper = new GraphvizDumper($container); - $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services13.dot')), $dumper->dump(), '->dump() dumps services'); + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services13.dot', $dumper->dump(), '->dump() dumps services'); } public function testDumpWithFrozenCustomClassContainer() { $container = include self::$fixturesPath.'/containers/container14.php'; $dumper = new GraphvizDumper($container); - $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services14.dot')), $dumper->dump(), '->dump() dumps services'); + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services14.dot', $dumper->dump(), '->dump() dumps services'); } public function testDumpWithUnresolvedParameter() @@ -69,6 +71,18 @@ public function testDumpWithUnresolvedParameter() $container = include self::$fixturesPath.'/containers/container17.php'; $dumper = new GraphvizDumper($container); - $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services17.dot')), $dumper->dump(), '->dump() dumps services'); + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services17.dot', $dumper->dump(), '->dump() dumps services'); + } + + public function testDumpWithInlineDefinition() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->addArgument( + (new Definition('stdClass'))->addArgument(new Reference('bar')) + ); + $container->register('bar', 'stdClass'); + $dumper = new GraphvizDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/graphviz/services_inline.dot', $dumper->dump(), '->dump() dumps nested references'); } } diff --git a/Tests/Fixtures/graphviz/services_inline.dot b/Tests/Fixtures/graphviz/services_inline.dot new file mode 100644 index 000000000..b430b186d --- /dev/null +++ b/Tests/Fixtures/graphviz/services_inline.dot @@ -0,0 +1,10 @@ +digraph sc { + ratio="compress" + node [fontsize="11" fontname="Arial" shape="record"]; + edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; + + node_service_container [label="service_container (Psr\Container\ContainerInterface, Symfony\Component\DependencyInjection\ContainerInterface)\nSymfony\\Component\\DependencyInjection\\ContainerInterface\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_foo [label="foo\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_bar [label="bar\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_foo -> node_bar [label="" style="filled"]; +} From 2b4c29b56f7a038f236f87c9048da0e2677228da Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 6 Nov 2018 14:23:19 +0100 Subject: [PATCH 2/8] [DI] dont track classes/interfaces used to compute autowiring error messages --- Compiler/AutowirePass.php | 13 +++++++++++-- ContainerBuilder.php | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Compiler/AutowirePass.php b/Compiler/AutowirePass.php index e542e30ea..4aabb0b29 100644 --- a/Compiler/AutowirePass.php +++ b/Compiler/AutowirePass.php @@ -452,7 +452,17 @@ private function createAutowiredDefinition($type) private function createTypeNotFoundMessage(TypedReference $reference, $label) { - if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { + $trackResources = $this->container->isTrackingResources(); + $this->container->setResourceTracking(false); + try { + if ($r = $this->container->getReflectionClass($type = $reference->getType(), false)) { + $alternatives = $this->createTypeAlternatives($reference); + } + } finally { + $this->container->setResourceTracking($trackResources); + } + + if (!$r) { // either $type does not exist or a parent class does not exist try { $resource = new ClassExistenceResource($type, false); @@ -465,7 +475,6 @@ private function createTypeNotFoundMessage(TypedReference $reference, $label) $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); } else { - $alternatives = $this->createTypeAlternatives($reference); $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); diff --git a/ContainerBuilder.php b/ContainerBuilder.php index 55d2c0112..de2abd3d0 100644 --- a/ContainerBuilder.php +++ b/ContainerBuilder.php @@ -364,7 +364,7 @@ public function getReflectionClass($class, $throw = true) try { if (isset($this->classReflectors[$class])) { $classReflector = $this->classReflectors[$class]; - } elseif ($this->trackResources) { + } elseif (class_exists(ClassExistenceResource::class)) { $resource = new ClassExistenceResource($class, false); $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); } else { From bb9ca55bc0af3c3c16201faf97556fe406cac88b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 16 Oct 2018 18:02:29 +0200 Subject: [PATCH 3/8] [DI] fix dumping inlined services --- Dumper/PhpDumper.php | 121 ++++++++--------- Tests/ContainerBuilderTest.php | 2 + Tests/Dumper/PhpDumperTest.php | 2 + Tests/Fixtures/FooForCircularWithAddCalls.php | 19 +++ .../containers/container_almost_circular.php | 30 +++++ Tests/Fixtures/php/services9_as_files.txt | 1 - Tests/Fixtures/php/services9_compiled.php | 1 - .../php/services_almost_circular_private.php | 122 ++++++++++++++++-- .../php/services_almost_circular_public.php | 104 ++++++++++++++- Tests/Fixtures/php/services_deep_graph.php | 2 +- .../Fixtures/php/services_inline_self_ref.php | 10 +- Tests/Fixtures/php/services_tsantos.php | 16 +-- 12 files changed, 344 insertions(+), 86 deletions(-) create mode 100644 Tests/Fixtures/FooForCircularWithAddCalls.php diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index fb12f99da..416f954cf 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -306,9 +306,13 @@ private function analyzeCircularReferences(array $edges, &$checkedNodes, &$curre if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) { // no-op } elseif (isset($currentPath[$id])) { + $currentId = $id; foreach (array_reverse($currentPath) as $parentId) { - $this->circularReferences[$parentId][$id] = $id; - $id = $parentId; + $this->circularReferences[$parentId][$currentId] = $currentId; + if ($parentId === $id) { + break; + } + $currentId = $parentId; } } elseif (!isset($checkedNodes[$id])) { $checkedNodes[$id] = true; @@ -591,7 +595,7 @@ private function addService($id, Definition $definition, &$file = null) $this->definitionVariables = new \SplObjectStorage(); $this->referenceVariables = array(); $this->variableCount = 0; - $this->definitionVariables[$definition] = $this->referenceVariables[$id] = new Variable('instance'); + $this->referenceVariables[$id] = new Variable('instance'); $return = array(); @@ -663,22 +667,7 @@ protected function {$methodName}($lazyInitialization) $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); } - $head = $tail = ''; - $arguments = array($definition->getArguments(), $definition->getFactory()); - $this->addInlineVariables($head, $tail, $id, $arguments, true); - $code .= '' !== $head ? $head."\n" : ''; - - if ($arguments = array_filter(array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()))) { - $this->addInlineVariables($tail, $tail, $id, $arguments, false); - - $tail .= '' !== $tail ? "\n" : ''; - $tail .= $this->addServiceProperties($definition); - $tail .= $this->addServiceMethodCalls($definition); - $tail .= $this->addServiceConfigurator($definition); - } - - $code .= $this->addServiceInstance($id, $definition, '' === $tail) - .('' !== $tail ? "\n".$tail."\n return \$instance;\n" : ''); + $code .= $this->addInlineService($id, $definition); if ($asFile) { $code = implode("\n", array_map(function ($line) { return $line ? substr($line, 8) : $line; }, explode("\n", $code))); @@ -692,35 +681,41 @@ protected function {$methodName}($lazyInitialization) return $code; } - private function addInlineVariables(&$head, &$tail, $id, array $arguments, $forConstructor) + private function addInlineVariables($id, Definition $definition, array $arguments, $forConstructor) { - $hasSelfRef = false; + $code = ''; foreach ($arguments as $argument) { if (\is_array($argument)) { - $hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; + $code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor); } elseif ($argument instanceof Reference) { - $hasSelfRef = $this->addInlineReference($head, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef; + $code .= $this->addInlineReference($id, $definition, $this->container->normalizeId($argument), $forConstructor); } elseif ($argument instanceof Definition) { - $hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; + $code .= $this->addInlineService($id, $definition, $argument, $forConstructor); } } - return $hasSelfRef; + return $code; } - private function addInlineReference(&$code, $id, $targetId, $forConstructor) + private function addInlineReference($id, Definition $definition, $targetId, $forConstructor) { - $hasSelfRef = isset($this->circularReferences[$id][$targetId]); + if ($id === $targetId) { + return $this->addInlineService($id, $definition, $definition, $forConstructor); + } if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { - return $hasSelfRef; + return ''; } + $hasSelfRef = isset($this->circularReferences[$id][$targetId]); + $forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]); list($callCount, $behavior) = $this->serviceCalls[$targetId]; - if (2 > $callCount && (!$hasSelfRef || !$forConstructor)) { - return $hasSelfRef; + $code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition, $forConstructor) : ''; + + if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) { + return $code; } $name = $this->getNextVariableName(); @@ -730,7 +725,7 @@ private function addInlineReference(&$code, $id, $targetId, $forConstructor) $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); if (!$hasSelfRef || !$forConstructor) { - return $hasSelfRef; + return $code; } $code .= sprintf(<<<'EOTXT' @@ -745,46 +740,56 @@ private function addInlineReference(&$code, $id, $targetId, $forConstructor) $id ); - return false; + return $code; } - private function addInlineService(&$head, &$tail, $id, Definition $definition, $forConstructor) + private function addInlineService($id, Definition $definition, Definition $inlineDef = null, $forConstructor = true) { - if (isset($this->definitionVariables[$definition])) { - return false; + $isSimpleInstance = $isRootInstance = null === $inlineDef; + + if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) { + return ''; } - $arguments = array($definition->getArguments(), $definition->getFactory()); + $arguments = array($inlineDef->getArguments(), $inlineDef->getFactory()); - if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator()) { - return $this->addInlineVariables($head, $tail, $id, $arguments, $forConstructor); - } + $code = $this->addInlineVariables($id, $definition, $arguments, $forConstructor); - $name = $this->getNextVariableName(); - $this->definitionVariables[$definition] = new Variable($name); + if ($arguments = array_filter(array($inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()))) { + $isSimpleInstance = false; + } elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) { + return $code; + } - $code = ''; - if ($forConstructor) { - $hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor); + if (isset($this->definitionVariables[$inlineDef])) { + $isSimpleInstance = false; } else { - $hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, $forConstructor); - } - $code .= $this->addNewInstance($definition, '$'.$name, ' = ', $id); - $hasSelfRef && !$forConstructor ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code; + $name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName(); + $this->definitionVariables[$inlineDef] = new Variable($name); + $code .= '' !== $code ? "\n" : ''; - $code = ''; - $arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()); - $hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, false) || $hasSelfRef; + if ('instance' === $name) { + $code .= $this->addServiceInstance($id, $definition, $isSimpleInstance); + } else { + $code .= $this->addNewInstance($inlineDef, '$'.$name, ' = ', $id); + } - $code .= '' !== $code ? "\n" : ''; - $code .= $this->addServiceProperties($definition, $name); - $code .= $this->addServiceMethodCalls($definition, $name); - $code .= $this->addServiceConfigurator($definition, $name); - if ('' !== $code) { - $hasSelfRef ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= $code; + if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) { + $code .= "\n".$inline."\n"; + } elseif ($arguments && 'instance' === $name) { + $code .= "\n"; + } + + $code .= $this->addServiceProperties($inlineDef, $name); + $code .= $this->addServiceMethodCalls($inlineDef, $name); + $code .= $this->addServiceConfigurator($inlineDef, $name); + } + + if ($isRootInstance && !$isSimpleInstance) { + $code .= "\n return \$instance;\n"; } - return $hasSelfRef; + return $code; } /** diff --git a/Tests/ContainerBuilderTest.php b/Tests/ContainerBuilderTest.php index 7eccb7d74..0bf1befa3 100644 --- a/Tests/ContainerBuilderTest.php +++ b/Tests/ContainerBuilderTest.php @@ -1388,6 +1388,8 @@ public function testAlmostCircular($visibility) $foo6 = $container->get('foo6'); $this->assertEquals((object) array('bar6' => (object) array()), $foo6); + + $this->assertInstanceOf(\stdClass::class, $container->get('root')); } public function provideAlmostCircular() diff --git a/Tests/Dumper/PhpDumperTest.php b/Tests/Dumper/PhpDumperTest.php index 3ffcf0dc0..51c5fd21f 100644 --- a/Tests/Dumper/PhpDumperTest.php +++ b/Tests/Dumper/PhpDumperTest.php @@ -833,6 +833,8 @@ public function testAlmostCircular($visibility) $foo6 = $container->get('foo6'); $this->assertEquals((object) array('bar6' => (object) array()), $foo6); + + $this->assertInstanceOf(\stdClass::class, $container->get('root')); } public function provideAlmostCircular() diff --git a/Tests/Fixtures/FooForCircularWithAddCalls.php b/Tests/Fixtures/FooForCircularWithAddCalls.php new file mode 100644 index 000000000..a8331dc3e --- /dev/null +++ b/Tests/Fixtures/FooForCircularWithAddCalls.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +class FooForCircularWithAddCalls +{ + public function call(\stdClass $argument) + { + } +} diff --git a/Tests/Fixtures/containers/container_almost_circular.php b/Tests/Fixtures/containers/container_almost_circular.php index 3286f3d02..4c9906f7a 100644 --- a/Tests/Fixtures/containers/container_almost_circular.php +++ b/Tests/Fixtures/containers/container_almost_circular.php @@ -4,6 +4,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls; $public = 'public' === $visibility; $container = new ContainerBuilder(); @@ -115,4 +116,33 @@ ->setPublic(true) ->setProperty('bar6', new Reference('bar6')); +// provided by Christian Schiffler + +$container + ->register('root', 'stdClass') + ->setArguments([new Reference('level2'), new Reference('multiuse1')]) + ->setPublic(true); + +$container + ->register('level2', FooForCircularWithAddCalls::class) + ->addMethodCall('call', [new Reference('level3')]); + +$container->register('multiuse1', 'stdClass'); + +$container + ->register('level3', 'stdClass') + ->addArgument(new Reference('level4')); + +$container + ->register('level4', 'stdClass') + ->setArguments([new Reference('multiuse1'), new Reference('level5')]); + +$container + ->register('level5', 'stdClass') + ->addArgument(new Reference('level6')); + +$container + ->register('level6', FooForCircularWithAddCalls::class) + ->addMethodCall('call', [new Reference('level5')]); + return $container; diff --git a/Tests/Fixtures/php/services9_as_files.txt b/Tests/Fixtures/php/services9_as_files.txt index fa89e0494..eb5ac4fe9 100644 --- a/Tests/Fixtures/php/services9_as_files.txt +++ b/Tests/Fixtures/php/services9_as_files.txt @@ -158,7 +158,6 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; $this->services['foo_with_inline'] = $instance = new \Foo(); $a = new \Bar(); - $a->pub = 'pub'; $a->setBaz(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->load('getBazService.php')) && false ?: '_'}); diff --git a/Tests/Fixtures/php/services9_compiled.php b/Tests/Fixtures/php/services9_compiled.php index 1af126fa3..d618a530d 100644 --- a/Tests/Fixtures/php/services9_compiled.php +++ b/Tests/Fixtures/php/services9_compiled.php @@ -264,7 +264,6 @@ protected function getFooWithInlineService() $this->services['foo_with_inline'] = $instance = new \Foo(); $a = new \Bar(); - $a->pub = 'pub'; $a->setBaz(${($_ = isset($this->services['baz']) ? $this->services['baz'] : $this->getBazService()) && false ?: '_'}); diff --git a/Tests/Fixtures/php/services_almost_circular_private.php b/Tests/Fixtures/php/services_almost_circular_private.php index 882f22843..9c54acc6b 100644 --- a/Tests/Fixtures/php/services_almost_circular_private.php +++ b/Tests/Fixtures/php/services_almost_circular_private.php @@ -34,13 +34,26 @@ public function __construct() 'foo5' => 'getFoo5Service', 'foo6' => 'getFoo6Service', 'foobar4' => 'getFoobar4Service', + 'level2' => 'getLevel2Service', + 'level3' => 'getLevel3Service', + 'level4' => 'getLevel4Service', + 'level5' => 'getLevel5Service', + 'level6' => 'getLevel6Service', 'logger' => 'getLoggerService', 'manager' => 'getManagerService', 'manager2' => 'getManager2Service', + 'multiuse1' => 'getMultiuse1Service', + 'root' => 'getRootService', 'subscriber' => 'getSubscriberService', ); $this->privates = array( 'bar6' => true, + 'level2' => true, + 'level3' => true, + 'level4' => true, + 'level5' => true, + 'level6' => true, + 'multiuse1' => true, ); $this->aliases = array(); @@ -62,7 +75,13 @@ public function getRemovedIds() 'foobar' => true, 'foobar2' => true, 'foobar3' => true, + 'level2' => true, + 'level3' => true, + 'level4' => true, + 'level5' => true, + 'level6' => true, 'logger2' => true, + 'multiuse1' => true, 'subscriber2' => true, ); } @@ -141,10 +160,10 @@ protected function getConnectionService() $this->services['connection'] = $instance = new \stdClass($a, $b); - $a->subscriber = ${($_ = isset($this->services['subscriber']) ? $this->services['subscriber'] : $this->getSubscriberService()) && false ?: '_'}; - $b->logger = ${($_ = isset($this->services['logger']) ? $this->services['logger'] : $this->getLoggerService()) && false ?: '_'}; + $a->subscriber = ${($_ = isset($this->services['subscriber']) ? $this->services['subscriber'] : $this->getSubscriberService()) && false ?: '_'}; + return $instance; } @@ -157,19 +176,19 @@ protected function getConnection2Service() { $a = new \stdClass(); - $c = new \stdClass(); + $b = new \stdClass(); - $this->services['connection2'] = $instance = new \stdClass($a, $c); + $this->services['connection2'] = $instance = new \stdClass($a, $b); - $b = ${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}; + $c = new \stdClass($instance); - $a->subscriber2 = new \stdClass($b); + $d = ${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}; - $d = new \stdClass($instance); + $c->handler2 = new \stdClass($d); - $d->handler2 = new \stdClass($b); + $b->logger2 = $c; - $c->logger2 = $d; + $a->subscriber2 = new \stdClass($d); return $instance; } @@ -216,7 +235,6 @@ protected function getFoo5Service() $this->services['foo5'] = $instance = new \stdClass(); $a = new \stdClass($instance); - $a->foo = $instance; $instance->bar = $a; @@ -306,6 +324,16 @@ protected function getManager2Service() return $this->services['manager2'] = new \stdClass($a); } + /** + * Gets the public 'root' shared service. + * + * @return \stdClass + */ + protected function getRootService() + { + return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}); + } + /** * Gets the public 'subscriber' shared service. * @@ -337,4 +365,78 @@ protected function getBar6Service() return $this->services['bar6'] = new \stdClass($a); } + + /** + * Gets the private 'level2' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls + */ + protected function getLevel2Service() + { + $this->services['level2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); + + $instance->call(${($_ = isset($this->services['level3']) ? $this->services['level3'] : $this->getLevel3Service()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the private 'level3' shared service. + * + * @return \stdClass + */ + protected function getLevel3Service() + { + return $this->services['level3'] = new \stdClass(${($_ = isset($this->services['level4']) ? $this->services['level4'] : $this->getLevel4Service()) && false ?: '_'}); + } + + /** + * Gets the private 'level4' shared service. + * + * @return \stdClass + */ + protected function getLevel4Service() + { + return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'}); + } + + /** + * Gets the private 'level5' shared service. + * + * @return \stdClass + */ + protected function getLevel5Service() + { + $a = ${($_ = isset($this->services['level6']) ? $this->services['level6'] : $this->getLevel6Service()) && false ?: '_'}; + + if (isset($this->services['level5'])) { + return $this->services['level5']; + } + + return $this->services['level5'] = new \stdClass($a); + } + + /** + * Gets the private 'level6' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls + */ + protected function getLevel6Service() + { + $this->services['level6'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); + + $instance->call(${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the private 'multiuse1' shared service. + * + * @return \stdClass + */ + protected function getMultiuse1Service() + { + return $this->services['multiuse1'] = new \stdClass(); + } } diff --git a/Tests/Fixtures/php/services_almost_circular_public.php b/Tests/Fixtures/php/services_almost_circular_public.php index 573cc9bef..0c9228e64 100644 --- a/Tests/Fixtures/php/services_almost_circular_public.php +++ b/Tests/Fixtures/php/services_almost_circular_public.php @@ -41,13 +41,26 @@ public function __construct() 'foobar2' => 'getFoobar2Service', 'foobar3' => 'getFoobar3Service', 'foobar4' => 'getFoobar4Service', + 'level2' => 'getLevel2Service', + 'level3' => 'getLevel3Service', + 'level4' => 'getLevel4Service', + 'level5' => 'getLevel5Service', + 'level6' => 'getLevel6Service', 'logger' => 'getLoggerService', 'manager' => 'getManagerService', 'manager2' => 'getManager2Service', + 'multiuse1' => 'getMultiuse1Service', + 'root' => 'getRootService', 'subscriber' => 'getSubscriberService', ); $this->privates = array( 'bar6' => true, + 'level2' => true, + 'level3' => true, + 'level4' => true, + 'level5' => true, + 'level6' => true, + 'multiuse1' => true, ); $this->aliases = array(); @@ -62,7 +75,13 @@ public function getRemovedIds() 'bar6' => true, 'config' => true, 'config2' => true, + 'level2' => true, + 'level3' => true, + 'level4' => true, + 'level5' => true, + 'level6' => true, 'logger2' => true, + 'multiuse1' => true, 'subscriber2' => true, ); } @@ -176,7 +195,6 @@ protected function getConnection2Service() $this->services['connection2'] = $instance = new \stdClass(${($_ = isset($this->services['dispatcher2']) ? $this->services['dispatcher2'] : $this->getDispatcher2Service()) && false ?: '_'}, $a); $b = new \stdClass($instance); - $b->handler2 = new \stdClass(${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}); $a->logger2 = $b; @@ -396,6 +414,16 @@ protected function getManager2Service() return $this->services['manager2'] = new \stdClass($a); } + /** + * Gets the public 'root' shared service. + * + * @return \stdClass + */ + protected function getRootService() + { + return $this->services['root'] = new \stdClass(${($_ = isset($this->services['level2']) ? $this->services['level2'] : $this->getLevel2Service()) && false ?: '_'}, ${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}); + } + /** * Gets the public 'subscriber' shared service. * @@ -421,4 +449,78 @@ protected function getBar6Service() return $this->services['bar6'] = new \stdClass($a); } + + /** + * Gets the private 'level2' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls + */ + protected function getLevel2Service() + { + $this->services['level2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); + + $instance->call(${($_ = isset($this->services['level3']) ? $this->services['level3'] : $this->getLevel3Service()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the private 'level3' shared service. + * + * @return \stdClass + */ + protected function getLevel3Service() + { + return $this->services['level3'] = new \stdClass(${($_ = isset($this->services['level4']) ? $this->services['level4'] : $this->getLevel4Service()) && false ?: '_'}); + } + + /** + * Gets the private 'level4' shared service. + * + * @return \stdClass + */ + protected function getLevel4Service() + { + return $this->services['level4'] = new \stdClass(${($_ = isset($this->services['multiuse1']) ? $this->services['multiuse1'] : $this->services['multiuse1'] = new \stdClass()) && false ?: '_'}, ${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'}); + } + + /** + * Gets the private 'level5' shared service. + * + * @return \stdClass + */ + protected function getLevel5Service() + { + $a = ${($_ = isset($this->services['level6']) ? $this->services['level6'] : $this->getLevel6Service()) && false ?: '_'}; + + if (isset($this->services['level5'])) { + return $this->services['level5']; + } + + return $this->services['level5'] = new \stdClass($a); + } + + /** + * Gets the private 'level6' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls + */ + protected function getLevel6Service() + { + $this->services['level6'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); + + $instance->call(${($_ = isset($this->services['level5']) ? $this->services['level5'] : $this->getLevel5Service()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the private 'multiuse1' shared service. + * + * @return \stdClass + */ + protected function getMultiuse1Service() + { + return $this->services['multiuse1'] = new \stdClass(); + } } diff --git a/Tests/Fixtures/php/services_deep_graph.php b/Tests/Fixtures/php/services_deep_graph.php index 03441e6a4..9a1d1ab8c 100644 --- a/Tests/Fixtures/php/services_deep_graph.php +++ b/Tests/Fixtures/php/services_deep_graph.php @@ -81,8 +81,8 @@ protected function getFooService() if (isset($this->services['foo'])) { return $this->services['foo']; } - $b = new \stdClass(); + $c = new \stdClass(); $c->p3 = new \stdClass(); diff --git a/Tests/Fixtures/php/services_inline_self_ref.php b/Tests/Fixtures/php/services_inline_self_ref.php index 5cb1002fe..fa8a4e690 100644 --- a/Tests/Fixtures/php/services_inline_self_ref.php +++ b/Tests/Fixtures/php/services_inline_self_ref.php @@ -64,14 +64,14 @@ public function isFrozen() */ protected function getFooService() { - $b = new \App\Bar(); - $a = new \App\Baz($b); + $a = new \App\Bar(); - $this->services['App\Foo'] = $instance = new \App\Foo($a); + $b = new \App\Baz($a); + $b->bar = $a; - $b->foo = $instance; + $this->services['App\Foo'] = $instance = new \App\Foo($b); - $a->bar = $b; + $a->foo = $instance; return $instance; } diff --git a/Tests/Fixtures/php/services_tsantos.php b/Tests/Fixtures/php/services_tsantos.php index c8b3f9d50..dbaa70956 100644 --- a/Tests/Fixtures/php/services_tsantos.php +++ b/Tests/Fixtures/php/services_tsantos.php @@ -67,22 +67,20 @@ protected function getTsantosSerializerService() { $a = new \TSantos\Serializer\NormalizerRegistry(); - $d = new \TSantos\Serializer\EventDispatcher\EventDispatcher(); - $d->addSubscriber(new \TSantos\SerializerBundle\EventListener\StopwatchListener(new \Symfony\Component\Stopwatch\Stopwatch(true))); - - $this->services['tsantos_serializer'] = $instance = new \TSantos\Serializer\EventEmitterSerializer(new \TSantos\Serializer\Encoder\JsonEncoder(), $a, $d); - $b = new \TSantos\Serializer\Normalizer\CollectionNormalizer(); - $b->setSerializer($instance); + $c = new \TSantos\Serializer\EventDispatcher\EventDispatcher(); + $c->addSubscriber(new \TSantos\SerializerBundle\EventListener\StopwatchListener(new \Symfony\Component\Stopwatch\Stopwatch(true))); - $c = new \TSantos\Serializer\Normalizer\JsonNormalizer(); + $this->services['tsantos_serializer'] = $instance = new \TSantos\Serializer\EventEmitterSerializer(new \TSantos\Serializer\Encoder\JsonEncoder(), $a, $c); - $c->setSerializer($instance); + $b->setSerializer($instance); + $d = new \TSantos\Serializer\Normalizer\JsonNormalizer(); + $d->setSerializer($instance); $a->add(new \TSantos\Serializer\Normalizer\ObjectNormalizer(new \TSantos\SerializerBundle\Serializer\CircularReferenceHandler())); $a->add($b); - $a->add($c); + $a->add($d); return $instance; } From dbefbc2b0cbfa620edc5a89419e30d4ba5b6e2c7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 10 Nov 2018 16:03:16 +0100 Subject: [PATCH 4/8] [DI] align IniFileLoader to PHP bugfix #76965 --- Loader/IniFileLoader.php | 4 +++- Tests/Fixtures/ini/types.ini | 3 ++- Tests/Loader/IniFileLoaderTest.php | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Loader/IniFileLoader.php b/Loader/IniFileLoader.php index ed3709104..2ee9ea8ff 100644 --- a/Loader/IniFileLoader.php +++ b/Loader/IniFileLoader.php @@ -70,7 +70,9 @@ public function supports($resource, $type = null) private function phpize($value) { // trim on the right as comments removal keep whitespaces - $value = rtrim($value); + if ($value !== $v = rtrim($value)) { + $value = '""' === substr_replace($v, '', 1, -1) ? substr($v, 1, -1) : $v; + } $lowercaseValue = strtolower($value); switch (true) { diff --git a/Tests/Fixtures/ini/types.ini b/Tests/Fixtures/ini/types.ini index 19cc5b3b3..75840907d 100644 --- a/Tests/Fixtures/ini/types.ini +++ b/Tests/Fixtures/ini/types.ini @@ -11,9 +11,10 @@ constant = PHP_VERSION 12 = 12 12_string = '12' + 12_quoted_number = "12" 12_comment = 12 ; comment 12_string_comment = '12' ; comment - 12_string_comment_again = "12" ; comment + 12_quoted_number_comment = "12" ; comment -12 = -12 0 = 0 1 = 1 diff --git a/Tests/Loader/IniFileLoaderTest.php b/Tests/Loader/IniFileLoaderTest.php index 65b5db93c..c2332f822 100644 --- a/Tests/Loader/IniFileLoaderTest.php +++ b/Tests/Loader/IniFileLoaderTest.php @@ -58,7 +58,6 @@ public function testTypeConversionsWithNativePhp($key, $value, $supported) $this->markTestSkipped(sprintf('Converting the value "%s" to "%s" is not supported by the IniFileLoader.', $key, $value)); } - $this->loader->load('types.ini'); $expected = parse_ini_file(__DIR__.'/../Fixtures/ini/types.ini', true, INI_SCANNER_TYPED); $this->assertSame($value, $expected['parameters'][$key], '->load() converts values to PHP types'); } @@ -78,9 +77,10 @@ public function getTypeConversions() array('constant', PHP_VERSION, true), array('12', 12, true), array('12_string', '12', true), + array('12_quoted_number', 12, false), // INI_SCANNER_RAW removes the double quotes array('12_comment', 12, true), array('12_string_comment', '12', true), - array('12_string_comment_again', '12', true), + array('12_quoted_number_comment', 12, false), // INI_SCANNER_RAW removes the double quotes array('-12', -12, true), array('1', 1, true), array('0', 0, true), From a2f40df187f0053bc361bcea3b27ff2b85744d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 11 Nov 2018 12:11:22 +0100 Subject: [PATCH 5/8] Bump phpunit XSD version to 5.2 Some attributes being used in the phpunit configuration files, namely failOnRisky and failOnWarning were introduced in phpunit 5.2.0. The Composer configuration shows that tests should run with old versions of phpunit, but phpunit only validates the configuration against the XSD since phpunit 7.2.0. These changes can be tested as follows: wget http://schema.phpunit.de/5.2/phpunit.xsd xargs xmllint --schema phpunit.xsd 1>/dev/null find src -name phpunit.xml.dist| xargs xmllint --schema phpunit.xsd 1>/dev/null See https://github.com/sebastianbergmann/phpunit/commit/7e06a82806be004cbfd30a02d92162e9ed4abc7c See https://github.com/symfony/symfony/blob/46e3745a03e199e64cc0fcf3284a96b5a25dcee9/composer.json#L98 --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 781f767d5..21dee2a80 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ Date: Thu, 15 Nov 2018 13:37:52 +0100 Subject: [PATCH 6/8] [DI] dont fail on missing classes when resource tracking is disabled --- ContainerBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ContainerBuilder.php b/ContainerBuilder.php index de2abd3d0..5fe6f2ae6 100644 --- a/ContainerBuilder.php +++ b/ContainerBuilder.php @@ -368,7 +368,7 @@ public function getReflectionClass($class, $throw = true) $resource = new ClassExistenceResource($class, false); $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); } else { - $classReflector = new \ReflectionClass($class); + $classReflector = class_exists($class) ? new \ReflectionClass($class) : false; } } catch (\ReflectionException $e) { if ($throw) { From 5ec6098a2ba4c84db4a023a12c1d4ec8933c33ae Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 18 Nov 2018 10:10:08 +0100 Subject: [PATCH 7/8] [DI] fix taking lazy services into account when dumping the container --- Compiler/AnalyzeServiceReferencesPass.php | 8 +++- Compiler/ServiceReferenceGraph.php | 6 ++- Compiler/ServiceReferenceGraphEdge.php | 15 ++++++- Dumper/PhpDumper.php | 42 ++++++++++++------- Tests/Fixtures/php/services_adawson.php | 8 +++- .../php/services_almost_circular_public.php | 34 +++++++++++---- 6 files changed, 84 insertions(+), 29 deletions(-) diff --git a/Compiler/AnalyzeServiceReferencesPass.php b/Compiler/AnalyzeServiceReferencesPass.php index e734aebb2..7c22d3c08 100644 --- a/Compiler/AnalyzeServiceReferencesPass.php +++ b/Compiler/AnalyzeServiceReferencesPass.php @@ -37,6 +37,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $hasProxyDumper; private $lazy; private $expressionLanguage; + private $byConstructor; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls @@ -64,6 +65,7 @@ public function process(ContainerBuilder $container) $this->graph = $container->getCompiler()->getServiceReferenceGraph(); $this->graph->clear(); $this->lazy = false; + $this->byConstructor = false; foreach ($container->getAliases() as $id => $alias) { $targetId = $this->getDefinitionId((string) $alias); @@ -100,7 +102,8 @@ protected function processValue($value, $isRoot = false) $targetDefinition, $value, $this->lazy || ($this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy()), - ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() + ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(), + $this->byConstructor ); return $value; @@ -118,8 +121,11 @@ protected function processValue($value, $isRoot = false) } $this->lazy = false; + $byConstructor = $this->byConstructor; + $this->byConstructor = true; $this->processValue($value->getFactory()); $this->processValue($value->getArguments()); + $this->byConstructor = $byConstructor; if (!$this->onlyConstructorArguments) { $this->processValue($value->getProperties()); diff --git a/Compiler/ServiceReferenceGraph.php b/Compiler/ServiceReferenceGraph.php index 4d36ae767..83486f053 100644 --- a/Compiler/ServiceReferenceGraph.php +++ b/Compiler/ServiceReferenceGraph.php @@ -91,11 +91,13 @@ public function clear() * @param string $reference * @param bool $lazy * @param bool $weak + * @param bool $byConstructor */ - public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false*/) + public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false, bool $byConstructor = false*/) { $lazy = \func_num_args() >= 6 ? func_get_arg(5) : false; $weak = \func_num_args() >= 7 ? func_get_arg(6) : false; + $byConstructor = \func_num_args() >= 8 ? func_get_arg(7) : false; if (null === $sourceId || null === $destId) { return; @@ -103,7 +105,7 @@ public function connect($sourceId, $sourceValue, $destId, $destValue = null, $re $sourceNode = $this->createNode($sourceId, $sourceValue); $destNode = $this->createNode($destId, $destValue); - $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor); $sourceNode->addOutEdge($edge); $destNode->addInEdge($edge); diff --git a/Compiler/ServiceReferenceGraphEdge.php b/Compiler/ServiceReferenceGraphEdge.php index 018905394..5b8c84b6d 100644 --- a/Compiler/ServiceReferenceGraphEdge.php +++ b/Compiler/ServiceReferenceGraphEdge.php @@ -25,6 +25,7 @@ class ServiceReferenceGraphEdge private $value; private $lazy; private $weak; + private $byConstructor; /** * @param ServiceReferenceGraphNode $sourceNode @@ -32,14 +33,16 @@ class ServiceReferenceGraphEdge * @param mixed $value * @param bool $lazy * @param bool $weak + * @param bool $byConstructor */ - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false, $weak = false) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false, $weak = false, $byConstructor = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; $this->value = $value; $this->lazy = $lazy; $this->weak = $weak; + $this->byConstructor = $byConstructor; } /** @@ -91,4 +94,14 @@ public function isWeak() { return $this->weak; } + + /** + * Returns true if the edge links with a constructor argument. + * + * @return bool + */ + public function isReferencedByConstructor() + { + return $this->byConstructor; + } } diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index 416f954cf..57b9e698e 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -154,12 +154,16 @@ public function dump(array $options = array()) } } - (new AnalyzeServiceReferencesPass(false))->process($this->container); + (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); $this->circularReferences = array(); - $checkedNodes = array(); - foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { - $currentPath = array($id => $id); - $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); + foreach (array(true, false) as $byConstructor) { + foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { + if (!$node->getValue() instanceof Definition) { + continue; + } + $currentPath = array($id => true); + $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor); + } } $this->container->getCompiler()->getServiceReferenceGraph()->clear(); @@ -297,27 +301,31 @@ private function getProxyDumper() return $this->proxyDumper; } - private function analyzeCircularReferences(array $edges, &$checkedNodes, &$currentPath) + private function analyzeCircularReferences(array $edges, &$currentPath, $sourceId, $byConstructor) { foreach ($edges as $edge) { + if ($byConstructor && !$edge->isReferencedByConstructor()) { + continue; + } $node = $edge->getDestNode(); $id = $node->getId(); - if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) { + if (!$node->getValue() instanceof Definition || $sourceId === $id || $edge->isLazy() || $edge->isWeak()) { // no-op } elseif (isset($currentPath[$id])) { $currentId = $id; foreach (array_reverse($currentPath) as $parentId) { - $this->circularReferences[$parentId][$currentId] = $currentId; + if (!isset($this->circularReferences[$parentId][$currentId])) { + $this->circularReferences[$parentId][$currentId] = $byConstructor; + } if ($parentId === $id) { break; } $currentId = $parentId; } - } elseif (!isset($checkedNodes[$id])) { - $checkedNodes[$id] = true; + } else { $currentPath[$id] = $id; - $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); + $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor); unset($currentPath[$id]); } } @@ -700,8 +708,14 @@ private function addInlineVariables($id, Definition $definition, array $argument private function addInlineReference($id, Definition $definition, $targetId, $forConstructor) { + list($callCount, $behavior) = $this->serviceCalls[$targetId]; + + while ($this->container->hasAlias($targetId)) { + $targetId = (string) $this->container->getAlias($targetId); + } + if ($id === $targetId) { - return $this->addInlineService($id, $definition, $definition, $forConstructor); + return $this->addInlineService($id, $definition, $definition); } if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { @@ -710,9 +724,7 @@ private function addInlineReference($id, Definition $definition, $targetId, $for $hasSelfRef = isset($this->circularReferences[$id][$targetId]); $forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]); - list($callCount, $behavior) = $this->serviceCalls[$targetId]; - - $code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition, $forConstructor) : ''; + $code = $hasSelfRef && $this->circularReferences[$id][$targetId] && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : ''; if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) { return $code; diff --git a/Tests/Fixtures/php/services_adawson.php b/Tests/Fixtures/php/services_adawson.php index 8e9e65ad9..37b95567c 100644 --- a/Tests/Fixtures/php/services_adawson.php +++ b/Tests/Fixtures/php/services_adawson.php @@ -133,7 +133,13 @@ protected function getHandler1Service() */ protected function getHandler2Service() { - return $this->services['App\Handler2'] = new \App\Handler2(${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'}, ${($_ = isset($this->services['App\Schema']) ? $this->services['App\Schema'] : $this->getSchemaService()) && false ?: '_'}, ${($_ = isset($this->services['App\Processor']) ? $this->services['App\Processor'] : $this->getProcessorService()) && false ?: '_'}); + $a = ${($_ = isset($this->services['App\Processor']) ? $this->services['App\Processor'] : $this->getProcessorService()) && false ?: '_'}; + + if (isset($this->services['App\Handler2'])) { + return $this->services['App\Handler2']; + } + + return $this->services['App\Handler2'] = new \App\Handler2(${($_ = isset($this->services['App\Db']) ? $this->services['App\Db'] : $this->getDbService()) && false ?: '_'}, ${($_ = isset($this->services['App\Schema']) ? $this->services['App\Schema'] : $this->getSchemaService()) && false ?: '_'}, $a); } /** diff --git a/Tests/Fixtures/php/services_almost_circular_public.php b/Tests/Fixtures/php/services_almost_circular_public.php index 0c9228e64..bd08c4d15 100644 --- a/Tests/Fixtures/php/services_almost_circular_public.php +++ b/Tests/Fixtures/php/services_almost_circular_public.php @@ -174,11 +174,16 @@ protected function getBaz6Service() */ protected function getConnectionService() { - $a = new \stdClass(); + $a = ${($_ = isset($this->services['dispatcher']) ? $this->services['dispatcher'] : $this->getDispatcherService()) && false ?: '_'}; + + if (isset($this->services['connection'])) { + return $this->services['connection']; + } + $b = new \stdClass(); - $this->services['connection'] = $instance = new \stdClass(${($_ = isset($this->services['dispatcher']) ? $this->services['dispatcher'] : $this->getDispatcherService()) && false ?: '_'}, $a); + $this->services['connection'] = $instance = new \stdClass($a, $b); - $a->logger = ${($_ = isset($this->services['logger']) ? $this->services['logger'] : $this->getLoggerService()) && false ?: '_'}; + $b->logger = ${($_ = isset($this->services['logger']) ? $this->services['logger'] : $this->getLoggerService()) && false ?: '_'}; return $instance; } @@ -190,14 +195,19 @@ protected function getConnectionService() */ protected function getConnection2Service() { - $a = new \stdClass(); + $a = ${($_ = isset($this->services['dispatcher2']) ? $this->services['dispatcher2'] : $this->getDispatcher2Service()) && false ?: '_'}; - $this->services['connection2'] = $instance = new \stdClass(${($_ = isset($this->services['dispatcher2']) ? $this->services['dispatcher2'] : $this->getDispatcher2Service()) && false ?: '_'}, $a); + if (isset($this->services['connection2'])) { + return $this->services['connection2']; + } + $b = new \stdClass(); - $b = new \stdClass($instance); - $b->handler2 = new \stdClass(${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}); + $this->services['connection2'] = $instance = new \stdClass($a, $b); - $a->logger2 = $b; + $c = new \stdClass($instance); + $c->handler2 = new \stdClass(${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}); + + $b->logger2 = $c; return $instance; } @@ -431,7 +441,13 @@ protected function getRootService() */ protected function getSubscriberService() { - return $this->services['subscriber'] = new \stdClass(${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}); + $a = ${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}; + + if (isset($this->services['subscriber'])) { + return $this->services['subscriber']; + } + + return $this->services['subscriber'] = new \stdClass($a); } /** From e1a4f858cf3ae1f811e0df07a55f72cdec7e0d24 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Nov 2018 17:10:26 +0100 Subject: [PATCH 8/8] fix cs --- Definition.php | 2 +- Dumper/PhpDumper.php | 2 +- Loader/Configurator/Traits/LazyTrait.php | 2 +- Tests/Fixtures/php/services10.php | 2 +- Tests/Fixtures/php/services12.php | 2 +- Tests/Fixtures/php/services19.php | 2 +- Tests/Fixtures/php/services26.php | 2 +- Tests/Fixtures/php/services8.php | 2 +- Tests/Fixtures/php/services9_as_files.txt | 2 +- Tests/Fixtures/php/services9_compiled.php | 2 +- Tests/Fixtures/php/services_array_params.php | 2 +- Tests/Fixtures/php/services_base64_env.php | 2 +- Tests/Fixtures/php/services_env_in_id.php | 2 +- Tests/Fixtures/php/services_inline_requires.php | 2 +- Tests/Fixtures/php/services_rot13_env.php | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Definition.php b/Definition.php index e05b81b50..849d617a1 100644 --- a/Definition.php +++ b/Definition.php @@ -400,7 +400,7 @@ public function getMethodCalls() /** * Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class. * - * @param $instanceof ChildDefinition[] + * @param ChildDefinition[] $instanceof * * @return $this */ diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index 57b9e698e..fd7eec057 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -1367,7 +1367,7 @@ public function getParameterBag() /*{$this->docStar} * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string \$name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Loader/Configurator/Traits/LazyTrait.php b/Loader/Configurator/Traits/LazyTrait.php index d7ea8b27f..01d36ec2a 100644 --- a/Loader/Configurator/Traits/LazyTrait.php +++ b/Loader/Configurator/Traits/LazyTrait.php @@ -16,7 +16,7 @@ trait LazyTrait /** * Sets the lazy flag of this service. * - * @param bool $lazy + * @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class * * @return $this */ diff --git a/Tests/Fixtures/php/services10.php b/Tests/Fixtures/php/services10.php index a7b43ceef..aa078ab85 100644 --- a/Tests/Fixtures/php/services10.php +++ b/Tests/Fixtures/php/services10.php @@ -115,7 +115,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services12.php b/Tests/Fixtures/php/services12.php index 9947d36ac..4266ad8e5 100644 --- a/Tests/Fixtures/php/services12.php +++ b/Tests/Fixtures/php/services12.php @@ -122,7 +122,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services19.php b/Tests/Fixtures/php/services19.php index 1092363fd..c8b57a7cc 100644 --- a/Tests/Fixtures/php/services19.php +++ b/Tests/Fixtures/php/services19.php @@ -132,7 +132,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services26.php b/Tests/Fixtures/php/services26.php index 16b99007a..d6256008f 100644 --- a/Tests/Fixtures/php/services26.php +++ b/Tests/Fixtures/php/services26.php @@ -138,7 +138,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services8.php b/Tests/Fixtures/php/services8.php index 56e2a98cc..285942eb1 100644 --- a/Tests/Fixtures/php/services8.php +++ b/Tests/Fixtures/php/services8.php @@ -102,7 +102,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services9_as_files.txt b/Tests/Fixtures/php/services9_as_files.txt index eb5ac4fe9..4b6e2f59e 100644 --- a/Tests/Fixtures/php/services9_as_files.txt +++ b/Tests/Fixtures/php/services9_as_files.txt @@ -441,7 +441,7 @@ class ProjectServiceContainer extends Container /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services9_compiled.php b/Tests/Fixtures/php/services9_compiled.php index d618a530d..37cc5be35 100644 --- a/Tests/Fixtures/php/services9_compiled.php +++ b/Tests/Fixtures/php/services9_compiled.php @@ -432,7 +432,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services_array_params.php b/Tests/Fixtures/php/services_array_params.php index 4776d98c3..5ef6cb688 100644 --- a/Tests/Fixtures/php/services_array_params.php +++ b/Tests/Fixtures/php/services_array_params.php @@ -125,7 +125,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services_base64_env.php b/Tests/Fixtures/php/services_base64_env.php index f1b92397d..6b6be9569 100644 --- a/Tests/Fixtures/php/services_base64_env.php +++ b/Tests/Fixtures/php/services_base64_env.php @@ -104,7 +104,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services_env_in_id.php b/Tests/Fixtures/php/services_env_in_id.php index 6bc714a20..91114fd97 100644 --- a/Tests/Fixtures/php/services_env_in_id.php +++ b/Tests/Fixtures/php/services_env_in_id.php @@ -145,7 +145,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services_inline_requires.php b/Tests/Fixtures/php/services_inline_requires.php index 2db58bdda..08a474eea 100644 --- a/Tests/Fixtures/php/services_inline_requires.php +++ b/Tests/Fixtures/php/services_inline_requires.php @@ -174,7 +174,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter * diff --git a/Tests/Fixtures/php/services_rot13_env.php b/Tests/Fixtures/php/services_rot13_env.php index aab87ec7a..90836aa90 100644 --- a/Tests/Fixtures/php/services_rot13_env.php +++ b/Tests/Fixtures/php/services_rot13_env.php @@ -133,7 +133,7 @@ public function getParameterBag() /** * Computes a dynamic parameter. * - * @param string The name of the dynamic parameter to load + * @param string $name The name of the dynamic parameter to load * * @return mixed The value of the dynamic parameter *