bug #22991 [DI] Don't throw Autowire exception for removed service with private __construct (weaverryan)

This PR was merged into the 3.3 branch.

Discussion
----------

[DI] Don't throw Autowire exception for removed service with private __construct

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none (was talking to a user)
| License       | MIT
| Doc PR        | n/a

Suppose you have:

```php
class A
{
    private function construct() {}
}
```

This service will fail to be autowired. But, like other autowiring failures, if this service will ultimately be removed from the container, this exception should be ignored. This fixes that. Unless someone is using the `AutowirePass` directly inside a `try/catch`, there is no BC break (the behavior change is that the exception is now stored, instead of being thrown).

This also clarifies (in the test & phpdoc) that `AutowirePass` always throws `AutowiringFailedException`s.

Thanks!

Commits
-------

2d3e44e11e Fixing a bug where an autowiring exception was thrown even when that service was removed
This commit is contained in:
Fabien Potencier 2017-05-31 10:51:11 -07:00
commit 44f34825a6
3 changed files with 44 additions and 17 deletions

View File

@ -133,7 +133,13 @@ class AutowirePass extends AbstractRecursivePass
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
$methodCalls = $value->getMethodCalls();
if ($constructor = $this->getConstructor($value, false)) {
try {
$constructor = $this->getConstructor($value, false);
} catch (RuntimeException $e) {
throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
}
if ($constructor) {
array_unshift($methodCalls, array($constructor, $value->getArguments()));
}
@ -242,7 +248,7 @@ class AutowirePass extends AbstractRecursivePass
*
* @return array The autowired arguments
*
* @throws RuntimeException
* @throws AutowiringFailedException
*/
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments)
{

View File

@ -18,7 +18,7 @@ class AutowiringFailedException extends RuntimeException
{
private $serviceId;
public function __construct($serviceId, $message = '', $code = 0, Exception $previous = null)
public function __construct($serviceId, $message = '', $code = 0, \Exception $previous = null)
{
$this->serviceId = $serviceId;

View File

@ -151,7 +151,21 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Unable to resolve service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.
*/
public function testPrivateConstructorThrowsAutowireException()
{
$container = new ContainerBuilder();
$container->autowire('private_service', __NAMESPACE__.'\PrivateConstructor');
$pass = new AutowirePass(true);
$pass->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2", "c3".
*/
public function testTypeCollision()
@ -169,7 +183,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$k" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotGuessableArgument::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but no such service exists. You should maybe alias this class to one of these existing services: "a1", "a2".
*/
public function testTypeNotGuessable()
@ -186,7 +200,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$k" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotGuessableArgumentForSubclass::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\A" but no such service exists. You should maybe alias this class to one of these existing services: "a1", "a2".
*/
public function testTypeNotGuessableWithSubclass()
@ -203,7 +217,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists.
*/
public function testTypeNotGuessableNoServicesFound()
@ -322,7 +336,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.
*/
public function testClassNotFoundThrowsException()
@ -337,7 +351,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class does not exist.
*/
public function testParentClassNotFoundThrowsException()
@ -354,7 +368,7 @@ class AutowirePassTest extends TestCase
/**
* @group legacy
* @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "foo" service to "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" instead.
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessageInSymfony4 Cannot autowire service "bar": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\Bar::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but this service is abstract. You should maybe alias this class to the existing "foo" service.
*/
public function testDontUseAbstractServices()
@ -399,7 +413,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" must have a type-hint or be given a value explicitly.
*/
public function testScalarArgsCannotBeAutowired()
@ -607,7 +621,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "setter_injection_collision": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionCollision::setMultipleInstancesForOneArg()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2".
*/
public function testSetterInjectionCollisionThrowsException()
@ -626,7 +640,7 @@ class AutowirePassTest extends TestCase
/**
* @group legacy
* @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "foo" service to "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" instead.
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessageInSymfony4 Cannot autowire service "bar": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\Bar::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but no such service exists. You should maybe alias this class to the existing "foo" service.
*/
public function testProcessDoesNotTriggerDeprecations()
@ -677,7 +691,7 @@ class AutowirePassTest extends TestCase
/**
* @dataProvider provideNotWireableCalls
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
*/
public function testNotWireableCalls($method, $expectedMsg)
{
@ -717,7 +731,7 @@ class AutowirePassTest extends TestCase
/**
* @group legacy
* @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "i" service to "Symfony\Component\DependencyInjection\Tests\Compiler\I" instead.
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessageInSymfony4 Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. Try changing the type-hint to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead.
*/
public function testByIdAlternative()
@ -734,7 +748,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. Try changing the type-hint to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead.
*/
public function testExceptionWhenAliasExists()
@ -754,7 +768,7 @@ class AutowirePassTest extends TestCase
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
* @expectedExceptionMessage Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. You should maybe alias this class to one of these existing services: "i", "i2".
*/
public function testExceptionWhenAliasDoesNotExist()
@ -1091,3 +1105,10 @@ class NotWireable
{
}
}
class PrivateConstructor
{
private function __construct()
{
}
}