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 b79a656aa8eaf..bf7fc844c732b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -133,7 +133,13 @@ private function doProcessValue($value, $isRoot = false) $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 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC * * @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 f5c50d4dee336..145cd8cbdcf24 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 e2ba9ec4e4b97..ca297f217495d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -151,7 +151,21 @@ public function testExceptionsAreStored() } /** - * @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 @@ public function testTypeCollision() } /** - * @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 @@ public function testTypeNotGuessable() } /** - * @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 @@ public function testTypeNotGuessableWithSubclass() } /** - * @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 @@ public function testDontTriggerAutowiring() } /** - * @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 @@ public function testClassNotFoundThrowsException() } /** - * @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 @@ public function testParentClassNotFoundThrowsException() /** * @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 @@ public function testSomeSpecificArgumentsAreSet() } /** - * @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 @@ public function testIgnoreServiceWithClassNotExisting() } /** - * @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 @@ public function testSetterInjectionCollisionThrowsException() /** * @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 @@ public function testWithFactory() /** * @dataProvider provideNotWireableCalls - * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException */ public function testNotWireableCalls($method, $expectedMsg) { @@ -717,7 +731,7 @@ public function provideNotWireableCalls() /** * @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 @@ public function testByIdAlternative() } /** - * @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 @@ public function testExceptionWhenAliasExists() } /** - * @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 @@ protected function setProtectedMethod(A $a) { } } + +class PrivateConstructor +{ + private function __construct() + { + } +}