minor #18691 [DX][DI] Make failed autowiring error messages more explicit (lemoinem)

This PR was merged into the 2.8 branch.

Discussion
----------

[DX][DI] Make failed autowiring error messages more explicit

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | no (better DX integration)
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #18658
| License       | MIT
| Doc PR        | N/A

This is the PR improving the auto wiring error messages.
Two errors messages have augmented:

If a type-hint does not match any existing type and a service for this type cannot be automatically created, the error message now says so, instead of simply saying the type cannot be autowired.

If a type-hint matches multiple services and none of them provides an  autowiringType for it, the error message now says so and list the candidate services, instead of simply saying the type cannot be autowired.

Commits
-------

2ac81f9 Make failed autowiring error messages more explicit
This commit is contained in:
Fabien Potencier 2016-05-03 14:27:17 +02:00
commit 2452e354df
2 changed files with 46 additions and 16 deletions

View File

@ -105,7 +105,7 @@ class AutowirePass implements CompilerPassInterface
$this->populateAvailableTypes();
}
if (isset($this->types[$typeHint->name])) {
if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
$value = new Reference($this->types[$typeHint->name]);
} else {
try {
@ -190,22 +190,26 @@ class AutowirePass implements CompilerPassInterface
*/
private function set($type, $id)
{
if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypes[$type])) {
if (isset($this->definedTypes[$type])) {
return;
}
if (isset($this->types[$type])) {
if ($this->types[$type] === $id) {
return;
}
if (!isset($this->types[$type])) {
$this->types[$type] = $id;
unset($this->types[$type]);
return;
}
if ($this->types[$type] === $id) {
return;
}
if (!isset($this->notGuessableTypes[$type])) {
$this->notGuessableTypes[$type] = true;
return;
$this->types[$type] = (array) $this->types[$type];
}
$this->types[$type] = $id;
$this->types[$type][] = $id;
}
/**
@ -220,8 +224,14 @@ class AutowirePass implements CompilerPassInterface
*/
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->notGuessableTypes[$typeHint->name])) {
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Several services implementing this type have been declared: "%s".', $typeHint->name, $id, implode('", "', $this->types[$typeHint->name])));
}
$noAvailableDefinitionMessage = sprintf('Unable to autowire argument of type "%s" for the service "%s". This type cannot be instantiated automatically and no service implementing this type is declared.', $typeHint->name, $id);
if (!$typeHint->isInstantiable()) {
throw new RuntimeException($noAvailableDefinitionMessage);
}
$argumentId = sprintf('autowired.%s', $typeHint->name);
@ -230,7 +240,12 @@ class AutowirePass implements CompilerPassInterface
$argumentDefinition->setPublic(false);
$this->populateAvailableType($argumentId, $argumentDefinition);
$this->completeDefinition($argumentId, $argumentDefinition);
try {
$this->completeDefinition($argumentId, $argumentDefinition);
} catch (RuntimeException $e) {
throw new RuntimeException($noAvailableDefinitionMessage, 0, $e);
}
return new Reference($argumentId);
}

View File

@ -103,7 +103,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
/**
* @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". Several services implementing this type have been declared: "c1", "c2".
*/
public function testTypeCollision()
{
@ -120,7 +120,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
/**
* @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". Several services implementing this type have been declared: "a1", "a2".
*/
public function testTypeNotGuessable()
{
@ -137,7 +137,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
/**
* @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". Several services implementing this type have been declared: "a1", "a2".
*/
public function testTypeNotGuessableWithSubclass()
{
@ -207,6 +207,21 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(__NAMESPACE__.'\Lille', $lilleDefinition->getClass());
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". This type cannot be instantiated automatically and no service implementing this type is declared.
*/
public function testCreateNonInstanciable()
{
$container = new ContainerBuilder();
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
$aDefinition->setAutowired(true);
$pass = new AutowirePass();
$pass->process($container);
}
public function testResolveParameter()
{
$container = new ContainerBuilder();