diff --git a/src/Exception/CircularReferenceException.php b/src/Exception/CircularReferenceException.php new file mode 100644 index 00000000..73c34fe1 --- /dev/null +++ b/src/Exception/CircularReferenceException.php @@ -0,0 +1,14 @@ +hasAlias($cName)) { + if (isset($stack[$cName])) { + throw new Exception\CircularReferenceException(sprintf( + 'Circular alias reference: %s -> %s', + implode(' -> ', $stack), + $cName + )); + } + + $stack[$cName] = $cName; + $cName = $this->aliases[$cName]; + } + + return $cName; + } + /** * Retrieve a registered instance * @@ -448,12 +474,9 @@ public function get($name, $usePeeringServiceManagers = true) $isAlias = false; - if (isset($this->aliases[$cName])) { + if ($this->hasAlias($cName)) { $isAlias = true; - - do { - $cName = $this->aliases[$cName]; - } while ($this->hasAlias($cName)); + $cName = $this->resolveAlias($cName); } $instance = null; @@ -731,6 +754,38 @@ public function canCreateFromAbstractFactory($cName, $rName) return false; } + /** + * Ensure the alias definition will not result in a circular reference + * + * @param string $alias + * @param string $nameOrAlias + * @throws Exception\CircularReferenceException + * @return self + */ + protected function checkForCircularAliasReference($alias, $nameOrAlias) + { + $aliases = $this->aliases; + $aliases[$alias] = $nameOrAlias; + $stack = array(); + + while (isset($aliases[$alias])) { + if (isset($stack[$alias])) { + throw new Exception\CircularReferenceException(sprintf( + 'The alias definition "%s" : "%s" results in a circular reference: "%s" -> "%s"', + $alias, + $nameOrAlias, + implode('" -> "', $stack), + $alias + )); + } + + $stack[$alias] = $alias; + $alias = $aliases[$alias]; + } + + return $this; + } + /** * @param string $alias * @param string $nameOrAlias @@ -759,6 +814,10 @@ public function setAlias($alias, $nameOrAlias) )); } + if ($this->hasAlias($alias)) { + $this->checkForCircularAliasReference($cAlias, $nameOrAlias); + } + $this->aliases[$cAlias] = $nameOrAlias; return $this; } diff --git a/test/ServiceManagerTest.php b/test/ServiceManagerTest.php index e4357a71..0176de14 100644 --- a/test/ServiceManagerTest.php +++ b/test/ServiceManagerTest.php @@ -810,4 +810,46 @@ public function testUsesMultipleDelegates() $this->assertInstanceOf('stdClass', array_shift($fooDelegator->instances)); $this->assertSame($fooDelegator, array_shift($barDelegator->instances)); } + + /** + * @covers Zend\ServiceManager\ServiceManager::resolveAlias + */ + public function testSetCircularAliasReferenceThrowsException() + { + $this->setExpectedException('Zend\ServiceManager\Exception\CircularReferenceException'); + + // Only affects service managers that allow overwriting definitions + $this->serviceManager->setAllowOverride(true); + $this->serviceManager->setInvokableClass('foo-service', 'stdClass'); + $this->serviceManager->setAlias('foo-alias', 'foo-service'); + $this->serviceManager->setAlias('bar-alias', 'foo-alias'); + $this->serviceManager->setAlias('baz-alias', 'bar-alias'); + + // This will now cause a cyclic reference and should throw an exception + $this->serviceManager->setAlias('foo-alias', 'bar-alias'); + } + + /** + * @covers Zend\ServiceManager\ServiceManager::checkForCircularAliasReference + */ + public function testResolveCircularAliasReferenceThrowsException() + { + $this->setExpectedException('Zend\ServiceManager\Exception\CircularReferenceException'); + + // simulate an inconsistent state of $servicemanager->aliases as it could be + // caused by derived classes + $cyclicAliases = array( + 'fooalias' => 'bazalias', + 'baralias' => 'fooalias', + 'bazalias' => 'baralias' + ); + + $reflection = new \ReflectionObject($this->serviceManager); + $propertyReflection = $reflection->getProperty('aliases'); + $propertyReflection->setAccessible(true); + $propertyReflection->setValue($this->serviceManager, $cyclicAliases); + + // This should throw the exception + $this->serviceManager->get('baz-alias'); + } }