Browse Source

Fixes #16487: Added circular reference detection in DI container

3.0
Andrii Vasyliev 6 years ago committed by Alexander Makarov
parent
commit
22c3126c1c
  1. 1
      framework/CHANGELOG.md
  2. 39
      framework/di/CircularReferenceException.php
  3. 13
      framework/di/Container.php
  4. 21
      tests/framework/di/ContainerTest.php

1
framework/CHANGELOG.md

@ -54,6 +54,7 @@ Yii Framework 2 Change Log
- Enh #16126: Allows to configure `Connection::dsn` by config array (leandrogehlen)
- Chg #11397: `yii\i18n\MessageFormatter` polyfills and `yii\i18n\MessageFormatter::parse()` method were removed resulting in performance boost. See UPGRADE for compatibility notes (samdark)
- Chg #16247: Cloning components will now clone their behaviors as well (brandonkelly)
- Enh #16487: Added circular reference detection in DI container (hiqsol)
2.0.14.2 under development
------------------------

39
framework/di/CircularReferenceException.php

@ -0,0 +1,39 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\di;
use yii\base\InvalidConfigException;
/**
* NotInstantiableException is thrown when container tries to resolve
* object having circular reference in configuration.
*
* @author Andrii Vasyliev <sol@hiqdev.com>
* @since 3.0.0
*/
class CircularReferenceException extends InvalidConfigException
{
/**
* {@inheritdoc}
*/
public function __construct($class, $message = null, $code = 0, \Exception $previous = null)
{
if ($message === null) {
$message = "Circular reference in $class.";
}
parent::__construct($message, $code, $previous);
}
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return 'Circular reference';
}
}

13
framework/di/Container.php

@ -121,7 +121,10 @@ class Container extends Component
* is associated with a list of constructor parameter types or default values.
*/
private $_dependencies = [];
/**
* @var array used to collect classes instantiated during get to detect circular references
*/
private $_getting = [];
/**
* Returns an instance of the requested class.
@ -157,6 +160,11 @@ class Container extends Component
return $this->build($class, $params, $config);
}
if (isset($this->_getting[$class])) {
throw new CircularReferenceException($class);
}
$this->_getting[$class] = 1;
$definition = $this->_definitions[$class];
if (is_callable($definition, true)) {
@ -175,7 +183,7 @@ class Container extends Component
$object = $this->get($concrete, $params, $config);
}
} elseif (is_object($definition)) {
return $this->_singletons[$class] = $definition;
$object = $this->_singletons[$class] = $definition;
} else {
throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition));
}
@ -184,6 +192,7 @@ class Container extends Component
// singleton
$this->_singletons[$class] = $object;
}
unset($this->_getting[$class]);
return $object;
}

21
tests/framework/di/ContainerTest.php

@ -10,6 +10,7 @@ namespace yiiunit\framework\di;
use Yii;
use yii\di\Container;
use yii\di\Instance;
use yii\di\CircularReferenceException;
use yii\validators\NumberValidator;
use yiiunit\data\ar\Cat;
use yiiunit\data\ar\Order;
@ -319,4 +320,24 @@ class ContainerTest extends TestCase
$this->assertTrue(true, 'Should be not exception above');
}
public function testCircularReference()
{
$container = new Container();
$container->set(Bar::class, Qux::class);
$container->set(Qux::class, Bar::class);
$this->expectException(CircularReferenceException::class);
$container->get(Qux::class);
}
public function testCircularReferenceWithInstance()
{
$container = new Container();
$container->set(Bar::class, 'qux');
$container->set('qux', Qux::class, [Instance::of(Bar::class)]);
$this->expectException(CircularReferenceException::class);
$container->get(Bar::class);
}
}

Loading…
Cancel
Save