bug #28480 [DI] Detect circular references with ChildDefinition parent (Seb33300)

This PR was submitted for the master branch but it was squashed and merged into the 3.4 branch instead (closes #28480).

Discussion
----------

[DI] Detect circular references with ChildDefinition parent

| Q             | A
| ------------- | ---
| Branch?       | 3.4 (be careful when merging)
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #28312
| License       | MIT
| Doc PR        |

I will provide a test case if the fix looks good for you :)

Commits
-------

2a59c8e3e6 [DI] Detect circular references with ChildDefinition parent
This commit is contained in:
Nicolas Grekas 2018-09-18 11:39:32 +02:00
commit c4c29814d5
2 changed files with 30 additions and 0 deletions

View File

@ -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;

View File

@ -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);
}
}