From 53b01903ceb91e1b0fb17c0f1a66faae3d68c51d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 18 Jul 2017 13:08:22 +0200 Subject: [PATCH] [Config] Make ClassExistenceResource throw on invalid parents --- .../Resource/ClassExistenceResource.php | 25 ++++++++++++++++--- .../Compiler/AutowirePass.php | 12 ++++----- .../Compiler/FactoryReturnTypePass.php | 2 +- .../ResolveInstanceofConditionalsPass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 8 +++++- .../Tests/Compiler/AutowirePassTest.php | 6 ++--- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index 8a86a42099..ada9a89b94 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -25,6 +25,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ private $exists; private static $autoloadLevel = 0; + private static $autoloadedClass; private static $existsCache = array(); /** @@ -57,6 +58,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ /** * {@inheritdoc} + * + * @throws \ReflectionException when a parent class/interface/trait is not found */ public function isFresh($timestamp) { @@ -68,12 +71,13 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ if (!self::$autoloadLevel++) { spl_autoload_register(__CLASS__.'::throwOnRequiredClass'); } + $autoloadedClass = self::$autoloadedClass; + self::$autoloadedClass = $this->resource; try { $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); - } catch (\ReflectionException $e) { - $exists = false; } finally { + self::$autoloadedClass = $autoloadedClass; if (!--self::$autoloadLevel) { spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass'); } @@ -112,7 +116,10 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ */ private static function throwOnRequiredClass($class) { - $e = new \ReflectionException("Class $class does not exist"); + if (self::$autoloadedClass === $class) { + return; + } + $e = new \ReflectionException("Class $class not found"); $trace = $e->getTrace(); $autoloadFrame = array( 'function' => 'spl_autoload_call', @@ -138,6 +145,18 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ case 'is_callable': return; } + + $props = array( + 'file' => $trace[$i]['file'], + 'line' => $trace[$i]['line'], + 'trace' => array_slice($trace, 0, 1 + $i), + ); + + foreach ($props as $p => $v) { + $r = new \ReflectionProperty('Exception', $p); + $r->setAccessible(true); + $r->setValue($e, $v); + } } throw $e; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 8382d30385..055347b6f8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -124,8 +124,8 @@ class AutowirePass extends AbstractRecursivePass if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { return $value; } - if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) { - $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" does not exist.', $this->currentId, $value->getClass())); + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); return $value; } @@ -388,7 +388,7 @@ class AutowirePass extends AbstractRecursivePass unset($this->ambiguousServiceTypes[$type]); } - if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass())) { + if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) { return; } @@ -444,7 +444,7 @@ class AutowirePass extends AbstractRecursivePass */ private function createAutowiredDefinition($type) { - if (!($typeHint = $this->container->getReflectionClass($type)) || !$typeHint->isInstantiable()) { + if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) { return; } @@ -478,8 +478,8 @@ class AutowirePass extends AbstractRecursivePass private function createTypeNotFoundMessage(TypedReference $reference, $label) { - if (!$r = $this->container->getReflectionClass($type = $reference->getType())) { - $message = sprintf('has type "%s" but this class does not exist.', $type); + if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { + $message = sprintf('has type "%s" but this class cannot be loaded.', $type); } else { $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $this->createTypeAlternatives($reference)); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php index 3ba4a8caa0..85b5872b0e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php @@ -81,7 +81,7 @@ class FactoryReturnTypePass implements CompilerPassInterface $class = $factory[0]; } - if (!$m = $container->getReflectionClass($class)) { + if (!$m = $container->getReflectionClass($class, false)) { return; } try { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index 82f13ed2d0..32d7ad2911 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -66,7 +66,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $instanceofTags = array(); foreach ($conditionals as $interface => $instanceofDefs) { - if ($interface !== $class && (!$container->getReflectionClass($class))) { + if ($interface !== $class && (!$container->getReflectionClass($class, false))) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 14dd957e9e..c824cc959d 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -337,12 +337,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Retrieves the requested reflection class and registers it for resource tracking. * * @param string $class + * @param bool $throw * * @return \ReflectionClass|null * + * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true + * * @final */ - public function getReflectionClass($class) + public function getReflectionClass($class, $throw = true) { if (!$class = $this->getParameterBag()->resolveValue($class)) { return; @@ -357,6 +360,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); } } catch (\ReflectionException $e) { + if ($throw) { + throw $e; + } $classReflector = false; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 41ad563413..76cf290283 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -359,7 +359,7 @@ class AutowirePassTest extends TestCase /** * @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. + * @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 cannot be loaded. */ public function testClassNotFoundThrowsException() { @@ -374,7 +374,7 @@ class AutowirePassTest extends TestCase /** * @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. + * @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 cannot be loaded. */ public function testParentClassNotFoundThrowsException() { @@ -744,7 +744,7 @@ class AutowirePassTest extends TestCase public function provideNotWireableCalls() { return array( - array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.'), + array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'), array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'), array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'), );