From 6fe97f9b7ca5b2218a20ce149fb846464abec2a1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 21 Feb 2016 19:27:26 -0500 Subject: [PATCH] [DependencyInjection] Improving autowiring error messages to say *why* something cannot be autowired --- .../Compiler/AutowirePass.php | 28 +++++++++++++++---- .../Tests/Compiler/AutowirePassTest.php | 21 ++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index ec7880be89b95..aa6f0bf8ad740 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -27,7 +27,7 @@ class AutowirePass implements CompilerPassInterface private $reflectionClasses = array(); private $definedTypes = array(); private $types; - private $notGuessableTypes = array(); + private $ambiguousServiceTypes = array(); /** * {@inheritdoc} @@ -46,7 +46,7 @@ public function process(ContainerBuilder $container) $this->reflectionClasses = array(); $this->definedTypes = array(); $this->types = null; - $this->notGuessableTypes = array(); + $this->ambiguousServiceTypes = array(); } /** @@ -197,17 +197,25 @@ private function extractAncestors($id, \ReflectionClass $reflectionClass) */ private function set($type, $id) { - if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypes[$type])) { + if (isset($this->definedTypes[$type])) { return; } + // check to make sure the type doesn't match multiple services if (isset($this->types[$type])) { if ($this->types[$type] === $id) { return; } + // keep an array of all services matching this type + if (!isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type] = array( + $this->types[$type], + ); + } + $this->ambiguousServiceTypes[$type][] = $id; + unset($this->types[$type]); - $this->notGuessableTypes[$type] = true; return; } @@ -227,8 +235,16 @@ private function set($type, $id) */ private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) { - if (isset($this->notGuessableTypes[$typeHint->name]) || !$typeHint->isInstantiable()) { - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s".', $typeHint->name, $id)); + if (isset($this->ambiguousServiceTypes[$typeHint->name])) { + $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; + $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]); + + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices)); + } + + if (!$typeHint->isInstantiable()) { + $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s.', $typeHint->name, $id, $classOrInterface)); } $argumentId = sprintf('autowired.%s', $typeHint->name); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index f1ed72e94a4cd..a0b11ce9d9fd3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -102,7 +102,7 @@ public function testCompleteExistingDefinitionWithNotDefinedArguments() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". Multiple services exist for this interface (c1, c2). */ public function testTypeCollision() { @@ -119,7 +119,7 @@ public function testTypeCollision() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "a". + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "a". Multiple services exist for this class (a1, a2). */ public function testTypeNotGuessable() { @@ -136,7 +136,7 @@ public function testTypeNotGuessable() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\A" for the service "a". + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\A" for the service "a". Multiple services exist for this class (a1, a2). */ public function testTypeNotGuessableWithSubclass() { @@ -151,6 +151,21 @@ public function testTypeNotGuessableWithSubclass() $pass->process($container); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". No services were found matching this interface. + */ + public function testTypeNotGuessableNoServicesFound() + { + $container = new ContainerBuilder(); + + $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired'); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + public function testTypeNotGuessableWithTypeSet() { $container = new ContainerBuilder();