From a37f3e08078d32da90ed5fece75c5e30cd5620bd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 6 Feb 2019 19:04:48 +0100 Subject: [PATCH] [DI] Fix dumping Doctrine-like service graphs (bis) --- .../Compiler/AnalyzeServiceReferencesPass.php | 2 +- .../DependencyInjection/Dumper/PhpDumper.php | 112 +++++++++++------- .../php/services_almost_circular_private.php | 16 ++- .../php/services_almost_circular_public.php | 16 ++- 4 files changed, 98 insertions(+), 48 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index fc4047f902e1..8070920ff7ff 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -122,7 +122,7 @@ protected function processValue($value, $isRoot = false) $this->lazy = false; $byConstructor = $this->byConstructor; - $this->byConstructor = true; + $this->byConstructor = $isRoot || $byConstructor; $this->processValue($value->getFactory()); $this->processValue($value->getArguments()); $this->byConstructor = $byConstructor; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 73c868f1e54c..a18d1665c539 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -302,10 +302,10 @@ private function getProxyDumper() return $this->proxyDumper; } - private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = []) + private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = [], $byConstructor = true) { $checkedNodes[$sourceId] = true; - $currentPath[$sourceId] = $sourceId; + $currentPath[$sourceId] = $byConstructor; foreach ($edges as $edge) { $node = $edge->getDestNode(); @@ -314,44 +314,52 @@ private function analyzeCircularReferences($sourceId, array $edges, &$checkedNod 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 ($parentId === $id) { - break; - } - $currentId = $parentId; - } + $this->addCircularReferences($id, $currentPath, $edge->isReferencedByConstructor()); } elseif (!isset($checkedNodes[$id])) { - $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath); + $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath, $edge->isReferencedByConstructor()); } elseif (isset($this->circularReferences[$id])) { - $this->connectCircularReferences($id, $currentPath); + $this->connectCircularReferences($id, $currentPath, $edge->isReferencedByConstructor()); } } unset($currentPath[$sourceId]); } - private function connectCircularReferences($sourceId, &$currentPath, &$subPath = []) + private function connectCircularReferences($sourceId, &$currentPath, $byConstructor, &$subPath = []) { - $subPath[$sourceId] = $sourceId; - $currentPath[$sourceId] = $sourceId; + $currentPath[$sourceId] = $subPath[$sourceId] = $byConstructor; - foreach ($this->circularReferences[$sourceId] as $id) { + foreach ($this->circularReferences[$sourceId] as $id => $byConstructor) { if (isset($currentPath[$id])) { - $currentId = $id; - foreach (array_reverse($currentPath) as $parentId) { - $this->circularReferences[$parentId][$currentId] = $currentId; - if ($parentId === $id) { - break; - } - $currentId = $parentId; - } + $this->addCircularReferences($id, $currentPath, $byConstructor); } elseif (!isset($subPath[$id]) && isset($this->circularReferences[$id])) { - $this->connectCircularReferences($id, $currentPath, $subPath); + $this->connectCircularReferences($id, $currentPath, $byConstructor, $subPath); } } - unset($currentPath[$sourceId]); - unset($subPath[$sourceId]); + unset($currentPath[$sourceId], $subPath[$sourceId]); + } + + private function addCircularReferences($id, $currentPath, $byConstructor) + { + $currentPath[$id] = $byConstructor; + $circularRefs = []; + + foreach (array_reverse($currentPath) as $parentId => $v) { + $byConstructor = $byConstructor && $v; + $circularRefs[] = $parentId; + + if ($parentId === $id) { + break; + } + } + + $currentId = $id; + foreach ($circularRefs as $parentId) { + if (empty($this->circularReferences[$parentId][$currentId])) { + $this->circularReferences[$parentId][$currentId] = $byConstructor; + } + + $currentId = $parentId; + } } private function collectLineage($class, array &$lineage) @@ -661,7 +669,6 @@ private function addService($id, Definition $definition, &$file = null) $autowired = $definition->isAutowired() ? ' autowired' : ''; if ($definition->isLazy()) { - unset($this->circularReferences[$id]); $lazyInitialization = '$lazyLoad = true'; } else { $lazyInitialization = ''; @@ -736,12 +743,12 @@ 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); } + list($callCount, $behavior) = $this->serviceCalls[$targetId]; + if ($id === $targetId) { return $this->addInlineService($id, $definition, $definition); } @@ -750,9 +757,13 @@ private function addInlineReference($id, Definition $definition, $targetId, $for return ''; } - $hasSelfRef = isset($this->circularReferences[$id][$targetId]); - $forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]); - $code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : ''; + $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]); + + if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { + $code = $this->addInlineService($id, $definition, $definition); + } else { + $code = ''; + } if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) { return $code; @@ -785,15 +796,23 @@ private function addInlineReference($id, Definition $definition, $targetId, $for private function addInlineService($id, Definition $definition, Definition $inlineDef = null, $forConstructor = true) { - $isSimpleInstance = $isRootInstance = null === $inlineDef; + $code = ''; + + if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { + foreach ($this->serviceCalls as $targetId => list($callCount, $behavior, $byConstructor)) { + if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) { + $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); + } + } + } if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) { - return ''; + return $code; } $arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()]; - $code = $this->addInlineVariables($id, $definition, $arguments, $forConstructor); + $code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor); if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) { $isSimpleInstance = false; @@ -1550,7 +1569,7 @@ private function getServiceConditionals($value) return implode(' && ', $conditions); } - private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = []) + private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], $byConstructor = null) { if (null === $definitions) { $definitions = new \SplObjectStorage(); @@ -1558,12 +1577,16 @@ private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage foreach ($arguments as $argument) { if (\is_array($argument)) { - $this->getDefinitionsFromArguments($argument, $definitions, $calls); + $this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor); } elseif ($argument instanceof Reference) { $id = $this->container->normalizeId($argument); + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + if (!isset($calls[$id])) { - $calls[$id] = [0, $argument->getInvalidBehavior()]; + $calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor]; } else { $calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior()); } @@ -1575,8 +1598,10 @@ private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions[$argument] = 1 + $definitions[$argument]; } else { $definitions[$argument] = 1; - $arguments = [$argument->getArguments(), $argument->getFactory(), $argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()]; - $this->getDefinitionsFromArguments($arguments, $definitions, $calls); + $arguments = [$argument->getArguments(), $argument->getFactory()]; + $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor); + $arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()]; + $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor); } } @@ -1717,6 +1742,11 @@ private function dumpValue($value, $interpolate = true) return '$'.$value; } elseif ($value instanceof Reference) { $id = $this->container->normalizeId($value); + + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) { return $this->dumpValue($this->referenceVariables[$id], $interpolate); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 55ddf616c1d9..5f9bf8cee630 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -369,10 +369,15 @@ protected function getManager2Service() */ protected function getManager3Service($lazyLoad = true) { - $a = new \stdClass(); - $a->listener = [0 => ${($_ = isset($this->services['listener3']) ? $this->services['listener3'] : $this->getListener3Service()) && false ?: '_'}]; + $a = ${($_ = isset($this->services['listener3']) ? $this->services['listener3'] : $this->getListener3Service()) && false ?: '_'}; + + if (isset($this->services['manager3'])) { + return $this->services['manager3']; + } + $b = new \stdClass(); + $b->listener = [0 => $a]; - return $this->services['manager3'] = new \stdClass($a); + return $this->services['manager3'] = new \stdClass($b); } /** @@ -489,9 +494,12 @@ protected function getLevel6Service() protected function getManager4Service($lazyLoad = true) { $a = new \stdClass(); + + $this->services['manager4'] = $instance = new \stdClass($a); + $a->listener = [0 => ${($_ = isset($this->services['listener4']) ? $this->services['listener4'] : $this->getListener4Service()) && false ?: '_'}]; - return $this->services['manager4'] = new \stdClass($a); + return $instance; } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 09ca4591bf80..f41f831b3c5d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -497,7 +497,13 @@ protected function getManager2Service() */ protected function getManager3Service($lazyLoad = true) { - return $this->services['manager3'] = new \stdClass(${($_ = isset($this->services['connection3']) ? $this->services['connection3'] : $this->getConnection3Service()) && false ?: '_'}); + $a = ${($_ = isset($this->services['connection3']) ? $this->services['connection3'] : $this->getConnection3Service()) && false ?: '_'}; + + if (isset($this->services['manager3'])) { + return $this->services['manager3']; + } + + return $this->services['manager3'] = new \stdClass($a); } /** @@ -613,7 +619,13 @@ protected function getLevel6Service() */ protected function getManager4Service($lazyLoad = true) { - return $this->services['manager4'] = new \stdClass(${($_ = isset($this->services['connection4']) ? $this->services['connection4'] : $this->getConnection4Service()) && false ?: '_'}); + $a = ${($_ = isset($this->services['connection4']) ? $this->services['connection4'] : $this->getConnection4Service()) && false ?: '_'}; + + if (isset($this->services['manager4'])) { + return $this->services['manager4']; + } + + return $this->services['manager4'] = new \stdClass($a); } /**