diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 88b21e732d..77282817bf 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -37,6 +37,7 @@ class AutowirePass extends AbstractRecursivePass private $definedTypes = array(); private $types; private $ambiguousServiceTypes = array(); + private $usedTypes = array(); /** * {@inheritdoc} @@ -45,11 +46,27 @@ class AutowirePass extends AbstractRecursivePass { try { parent::process($container); + + foreach ($this->usedTypes as $type => $id) { + if (!isset($this->usedTypes[$type]) || !isset($this->ambiguousServiceTypes[$type])) { + continue; + } + + if ($container->has($type) && !$container->findDefinition($type)->isAbstract()) { + continue; + } + + $classOrInterface = class_exists($type) ? 'class' : 'interface'; + $matchingServices = implode(', ', $this->ambiguousServiceTypes[$type]); + + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $type, $id, $classOrInterface, $matchingServices)); + } } finally { // Free memory $this->definedTypes = array(); $this->types = null; $this->ambiguousServiceTypes = array(); + $this->usedTypes = array(); } } @@ -271,10 +288,12 @@ class AutowirePass extends AbstractRecursivePass if (isset($this->types[$typeName])) { $value = new Reference($this->types[$typeName]); $didAutowire = true; + $this->usedTypes[$typeName] = $this->currentId; } elseif ($typeHint = $this->container->getReflectionClass($typeName, true)) { try { $value = $this->createAutowiredDefinition($typeHint); $didAutowire = true; + $this->usedTypes[$typeName] = $this->currentId; } catch (RuntimeException $e) { if ($parameter->allowsNull()) { $value = null; @@ -354,6 +373,10 @@ class AutowirePass extends AbstractRecursivePass try { $value = $this->createAutowiredDefinition($returnType); } catch (RuntimeException $e) { + if (1 === $e->getCode()) { + throw $e; + } + continue; } } else { @@ -361,6 +384,7 @@ class AutowirePass extends AbstractRecursivePass } $overridenGetters[$lcMethod] = $value; + $this->usedTypes[$typeName] = $this->currentId; } return $overridenGetters; @@ -394,6 +418,7 @@ class AutowirePass extends AbstractRecursivePass foreach ($definition->getAutowiringTypes(false) as $type) { $this->definedTypes[$type] = true; $this->types[$type] = $id; + unset($this->ambiguousServiceTypes[$type]); } if (!$reflectionClass = $this->container->getReflectionClass($definition->getClass(), true)) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index a05c7acbe2..ba057a85a5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -192,6 +192,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase $container = new ContainerBuilder(); $container->register('a1', __NAMESPACE__.'\Foo'); + $container->register('a2', __NAMESPACE__.'\Foo'); $container->register(Foo::class, Foo::class); $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument'); $aDefinition->setAutowired(true); @@ -542,6 +543,26 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase ), $overridenGetters); } + /** + * @requires PHP 7.1 + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "getter_overriding". Multiple services exist for this class (a1, a2). + */ + public function testGetterOverridingWithAmbiguousServices() + { + $container = new ContainerBuilder(); + $container->register('a1', Foo::class); + $container->register('a2', Foo::class); + + $container + ->register('getter_overriding', GetterOverriding::class) + ->setAutowiredCalls(array('getFoo')) + ; + + $pass = new AutowirePass(); + $pass->process($container); + } + /** * @dataProvider getCreateResourceTests * @group legacy @@ -637,6 +658,31 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(new Reference('a'), '', new Reference('lille')), $container->getDefinition('foo')->getArguments()); } + + /** + * @dataProvider provideAutodiscoveredAutowiringOrder + * + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMEssage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". Multiple services exist for this interface (autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA, autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB). + */ + public function testAutodiscoveredAutowiringOrder($class) + { + $container = new ContainerBuilder(); + + $container->register('a', __NAMESPACE__.'\\'.$class) + ->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + + public function provideAutodiscoveredAutowiringOrder() + { + return array( + array('CannotBeAutowiredForwardOrder'), + array('CannotBeAutowiredReverseOrder'), + ); + } } class Foo @@ -718,6 +764,20 @@ class CannotBeAutowired } } +class CannotBeAutowiredForwardOrder +{ + public function __construct(CollisionA $a, CollisionInterface $b, CollisionB $c) + { + } +} + +class CannotBeAutowiredReverseOrder +{ + public function __construct(CollisionA $a, CollisionB $c, CollisionInterface $b) + { + } +} + class Lille { }