From 2d3e44e11ea053d2506408e49191e9b1797a9c14 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 31 May 2017 09:45:25 -0400 Subject: [PATCH] Fixing a bug where an autowiring exception was thrown even when that service was removed The specific report was for a service with a private constructor. This also clarifies that the AutowirePass throws AutowiringFailedException for all situations. And a bug was fixed in the constructor of AutowiringFailedException --- .../Compiler/AutowirePass.php | 10 +++- .../Exception/AutowiringFailedException.php | 2 +- .../Tests/Compiler/AutowirePassTest.php | 49 +++++++++++++------ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index b79a656aa8..bf7fc844c7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -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) { diff --git a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php index f5c50d4dee..145cd8cbdc 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php @@ -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; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index e2ba9ec4e4..ca297f2174 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -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() + { + } +}