bug #21665 [DependencyInjection] Fix autowiring collisions detection (nicolas-grekas, GuilhemN)

This PR was merged into the 2.8 branch.

Discussion
----------

[DependencyInjection] Fix autowiring collisions detection

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        |

Fixes https://github.com/symfony/symfony/pull/21658 by implementing the second proposal of https://github.com/symfony/symfony/pull/21658#issuecomment-280858452:
> Another idea: store the types used previously and check that new services registered don't implement them.

Commits
-------

bb70472dce [DependencyInjection] Fix autowiring collisions detection
6f578ee514 [DI] Bug in autowiring collisions detection
This commit is contained in:
Fabien Potencier 2017-02-19 08:09:57 -08:00
commit 3cfd04bd18
2 changed files with 52 additions and 0 deletions

View File

@ -28,6 +28,7 @@ class AutowirePass implements CompilerPassInterface
private $definedTypes = array();
private $types;
private $notGuessableTypes = array();
private $usedTypes = array();
/**
* {@inheritdoc}
@ -44,6 +45,15 @@ class AutowirePass implements CompilerPassInterface
$this->completeDefinition($id, $definition);
}
}
foreach ($this->usedTypes as $type => $id) {
if (isset($this->usedTypes[$type]) && isset($this->notGuessableTypes[$type])) {
$classOrInterface = class_exists($type) ? 'class' : 'interface';
$matchingServices = implode(', ', $this->types[$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));
}
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
@ -56,6 +66,7 @@ class AutowirePass implements CompilerPassInterface
$this->definedTypes = array();
$this->types = null;
$this->notGuessableTypes = array();
$this->usedTypes = array();
if (isset($e)) {
throw $e;
@ -109,9 +120,11 @@ class AutowirePass implements CompilerPassInterface
if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
$value = new Reference($this->types[$typeHint->name]);
$this->usedTypes[$typeHint->name] = $id;
} else {
try {
$value = $this->createAutowiredDefinition($typeHint, $id);
$this->usedTypes[$typeHint->name] = $id;
} catch (RuntimeException $e) {
if ($parameter->allowsNull()) {
$value = null;

View File

@ -459,6 +459,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
@ -540,6 +565,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
{
}