From 2a59c8e3e6294d01d458038d9264aacb6374f8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20ALFAIATE?= Date: Sun, 16 Sep 2018 19:04:45 +0700 Subject: [PATCH] [DI] Detect circular references with ChildDefinition parent --- .../Compiler/ResolveChildDefinitionsPass.php | 13 +++++++++++++ .../ResolveChildDefinitionsPassTest.php | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php index 8cbccdaa3b..d647eda237 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; /** * This replaces all ChildDefinition instances with their equivalent fully @@ -25,6 +26,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class ResolveChildDefinitionsPass extends AbstractRecursivePass { + private $currentPath; + protected function processValue($value, $isRoot = false) { if (!$value instanceof Definition) { @@ -36,6 +39,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass $value = $this->container->getDefinition($this->currentId); } if ($value instanceof ChildDefinition) { + $this->currentPath = array(); $value = $this->resolveDefinition($value); if ($isRoot) { $this->container->setDefinition($this->currentId, $value); @@ -56,6 +60,8 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass { try { return $this->doResolveDefinition($definition); + } catch (ServiceCircularReferenceException $e) { + throw $e; } catch (ExceptionInterface $e) { $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(true); @@ -71,6 +77,13 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); } + $searchKey = array_search($parent, $this->currentPath); + $this->currentPath[] = $parent; + + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey)); + } + $parentDef = $this->container->findDefinition($parent); if ($parentDef instanceof ChildDefinition) { $id = $this->currentId; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php index d30728a48b..1575bd7b07 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php @@ -431,4 +431,21 @@ class ResolveChildDefinitionsPassTest extends TestCase $pass = new ResolveChildDefinitionsPass(); $pass->process($container); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessageRegExp /^Circular reference detected for service "c", path: "c -> b -> a -> c"./ + */ + public function testProcessDetectsChildDefinitionIndirectCircularReference() + { + $container = new ContainerBuilder(); + + $container->register('a'); + + $container->setDefinition('b', new ChildDefinition('a')); + $container->setDefinition('c', new ChildDefinition('b')); + $container->setDefinition('a', new ChildDefinition('c')); + + $this->process($container); + } }