From 22c3126c1c42143940a16bc627854b36abf87a79 Mon Sep 17 00:00:00 2001 From: Andrii Vasyliev Date: Fri, 6 Jul 2018 17:05:03 +0300 Subject: [PATCH] Fixes #16487: Added circular reference detection in DI container --- framework/CHANGELOG.md | 1 + framework/di/CircularReferenceException.php | 39 +++++++++++++++++++++++++++++ framework/di/Container.php | 13 ++++++++-- tests/framework/di/ContainerTest.php | 21 ++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 framework/di/CircularReferenceException.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index f352e8c..34027b1 100644 --- a/framework/CHANGELOG.md +++ b/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 ------------------------ diff --git a/framework/di/CircularReferenceException.php b/framework/di/CircularReferenceException.php new file mode 100644 index 0000000..cb5ca56 --- /dev/null +++ b/framework/di/CircularReferenceException.php @@ -0,0 +1,39 @@ + + * @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'; + } +} diff --git a/framework/di/Container.php b/framework/di/Container.php index c47c32a..8310106 100644 --- a/framework/di/Container.php +++ b/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; } diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php index e2f6a3d..c83adfe 100644 --- a/tests/framework/di/ContainerTest.php +++ b/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); + } }