diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php index 74d586bf3b852..7d47dd2f9b132 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php @@ -13,7 +13,10 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Translation\TranslatorBagInterface; /** * @author Abdellatif Ait boudad @@ -31,7 +34,10 @@ public function process(ContainerBuilder $container) $definition = $container->getDefinition((string) $translatorAlias); $class = $container->getParameterBag()->resolveValue($definition->getClass()); - if (is_subclass_of($class, 'Symfony\Component\Translation\TranslatorInterface') && is_subclass_of($class, 'Symfony\Component\Translation\TranslatorBagInterface')) { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); + } + if ($r->isSubclassOf(TranslatorInterface::class) && $r->isSubclassOf(TranslatorBagInterface::class)) { $container->getDefinition('translator.logging')->setDecoratedService('translator'); $container->getDefinition('translation.warmer')->replaceArgument(0, new Reference('translator.logging.inner')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index 5de27ba4916d5..5383e5ad4b3a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -54,6 +54,11 @@ public function testProcess() ->method('getParameterBag') ->will($this->returnValue($parameterBag)); + $container->expects($this->once()) + ->method('getReflectionClass') + ->with('Symfony\Bundle\FrameworkBundle\Translation\Translator') + ->will($this->returnValue(new \ReflectionClass('Symfony\Bundle\FrameworkBundle\Translation\Translator'))); + $pass = new LoggingTranslatorPass(); $pass->process($container); } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 1a564194bf4f0..e658a3dab6742 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,7 +22,7 @@ "symfony/dependency-injection": "~3.3", "symfony/config": "~3.3", "symfony/event-dispatcher": "~3.3", - "symfony/http-foundation": "~3.1", + "symfony/http-foundation": "~3.3", "symfony/http-kernel": "~3.3", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~2.8|~3.0", diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index b752df6fe2348..4126bfa1eb96d 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +3.3.0 +----- + + * added `ReflectionClassResource` class + * added second `$exists` constructor argument to `ClassExistenceResource` + * made `ClassExistenceResource` work with interfaces and traits + 3.0.0 ----- diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index 8a9df906a643d..7add2ea220d36 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -21,16 +21,27 @@ */ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable { + const EXISTS_OK = 1; + const EXISTS_KO = 0; + const EXISTS_KO_WITH_THROWING_AUTOLOADER = -1; + private $resource; - private $exists; + private $existsStatus; + + private static $checkingLevel = 0; + private static $throwingAutoloader; + private static $existsCache = array(); /** - * @param string $resource The fully-qualified class name + * @param string $resource The fully-qualified class name + * @param int|null $existsStatus One of the self::EXISTS_* const if the existency check has already been done */ - public function __construct($resource) + public function __construct($resource, $existsStatus = null) { $this->resource = $resource; - $this->exists = class_exists($resource); + if (null !== $existsStatus) { + $this->existsStatus = (int) $existsStatus; + } } /** @@ -54,7 +65,35 @@ public function getResource() */ public function isFresh($timestamp) { - return class_exists($this->resource) === $this->exists; + if (null !== $exists = &self::$existsCache[$this->resource]) { + $exists = $exists || class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + } elseif (self::EXISTS_KO_WITH_THROWING_AUTOLOADER === $this->existsStatus) { + if (null === self::$throwingAutoloader) { + $signalingException = new \ReflectionException(); + self::$throwingAutoloader = function () use ($signalingException) { throw $signalingException; }; + } + if (!self::$checkingLevel++) { + spl_autoload_register(self::$throwingAutoloader); + } + + try { + $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + } catch (\ReflectionException $e) { + $exists = false; + } finally { + if (!--self::$checkingLevel) { + spl_autoload_unregister(self::$throwingAutoloader); + } + } + } else { + $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + } + + if (null === $this->existsStatus) { + $this->existsStatus = $exists ? self::EXISTS_OK : self::EXISTS_KO; + } + + return self::EXISTS_OK === $this->existsStatus xor !$exists; } /** @@ -62,7 +101,11 @@ public function isFresh($timestamp) */ public function serialize() { - return serialize(array($this->resource, $this->exists)); + if (null === $this->existsStatus) { + $this->isFresh(0); + } + + return serialize(array($this->resource, $this->existsStatus)); } /** @@ -70,6 +113,6 @@ public function serialize() */ public function unserialize($serialized) { - list($this->resource, $this->exists) = unserialize($serialized); + list($this->resource, $this->existsStatus) = unserialize($serialized); } } diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php new file mode 100644 index 0000000000000..23aef37c0c182 --- /dev/null +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * @author Nicolas Grekas + */ +class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable +{ + private $files = array(); + private $className; + private $classReflector; + private $hash; + + public function __construct(\ReflectionClass $classReflector) + { + $this->className = $classReflector->name; + $this->classReflector = $classReflector; + } + + public function isFresh($timestamp) + { + if (null === $this->hash) { + $this->hash = $this->computeHash(); + $this->loadFiles($this->classReflector); + } + + foreach ($this->files as $file => $v) { + if (!file_exists($file)) { + return false; + } + + if (@filemtime($file) > $timestamp) { + return $this->hash === $this->computeHash(); + } + } + + return true; + } + + public function __toString() + { + return 'reflection.'.$this->className; + } + + public function serialize() + { + if (null === $this->hash) { + $this->hash = $this->computeHash(); + $this->loadFiles($this->classReflector); + } + + return serialize(array($this->files, $this->className, $this->hash)); + } + + public function unserialize($serialized) + { + list($this->files, $this->className, $this->hash) = unserialize($serialized); + } + + private function loadFiles(\ReflectionClass $class) + { + foreach ($class->getInterfaces() as $v) { + $this->loadFiles($v); + } + do { + $file = $class->getFileName(); + if (false !== $file && file_exists($file)) { + $this->files[$file] = null; + } + foreach ($class->getTraits() as $v) { + $this->loadFiles($v); + } + } while ($class = $class->getParentClass()); + } + + private function computeHash() + { + if (null === $this->classReflector) { + try { + $this->classReflector = new \ReflectionClass($this->className); + } catch (\ReflectionException $e) { + // the class does not exist anymore + return false; + } + } + $hash = hash_init('md5'); + + foreach ($this->generateSignature($this->classReflector) as $info) { + hash_update($hash, $info); + } + + return hash_final($hash); + } + + private function generateSignature(\ReflectionClass $class) + { + yield $class->getDocComment().$class->getModifiers(); + + if ($class->isTrait()) { + yield print_r(class_uses($class->name), true); + } else { + yield print_r(class_parents($class->name), true); + yield print_r(class_implements($class->name), true); + yield print_r($class->getConstants(), true); + } + + if (!$class->isInterface()) { + $defaults = $class->getDefaultProperties(); + + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { + yield $p->getDocComment().$p; + yield print_r($defaults[$p->name], true); + } + } + + if (defined('HHVM_VERSION')) { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { + // workaround HHVM bug with variadics, see https://github.com/facebook/hhvm/issues/5762 + yield preg_replace('/^ @@.*/m', '', new ReflectionMethodHhvmWrapper($m->class, $m->name)); + } + } else { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { + yield preg_replace('/^ @@.*/m', '', $m); + + $defaults = array(); + foreach ($m->getParameters() as $p) { + $defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null; + } + yield print_r($defaults, true); + } + } + } +} + +/** + * @internal + */ +class ReflectionMethodHhvmWrapper extends \ReflectionMethod +{ + public function getParameters() + { + $params = array(); + + foreach (parent::getParameters() as $i => $p) { + $params[] = new ReflectionParameterHhvmWrapper(array($this->class, $this->name), $i); + } + + return $params; + } +} + +/** + * @internal + */ +class ReflectionParameterHhvmWrapper extends \ReflectionParameter +{ + public function getDefaultValue() + { + return array($this->isVariadic(), $this->isDefaultValueAvailable() ? parent::getDefaultValue() : null); + } +} diff --git a/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php index 20d5b3308d3fc..aa86ff037f6a1 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php @@ -51,4 +51,24 @@ public function testIsFreshWhenClassExists() $this->assertTrue($res->isFresh(time())); } + + public function testExistsKo() + { + spl_autoload_register($autoloader = function ($class) use (&$loadedClass) { $loadedClass = $class; }); + + try { + $res = new ClassExistenceResource('MissingFooClass'); + $this->assertTrue($res->isFresh(0)); + + $this->assertSame('MissingFooClass', $loadedClass); + + $loadedClass = 123; + + $res = new ClassExistenceResource('MissingFooClass', ClassExistenceResource::EXISTS_KO); + + $this->assertSame(123, $loadedClass); + } finally { + spl_autoload_unregister($autoloader); + } + } } diff --git a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php new file mode 100644 index 0000000000000..29a4c4c98f990 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Resource; + +use Symfony\Component\Config\Resource\ReflectionClassResource; + +class ReflectionClassResourceTest extends \PHPUnit_Framework_TestCase +{ + public function testToString() + { + $res = new ReflectionClassResource(new \ReflectionClass('ErrorException')); + + $this->assertSame('reflection.ErrorException', (string) $res); + } + + public function testSerializeUnserialize() + { + $res = new ReflectionClassResource(new \ReflectionClass(DummyInterface::class)); + $ser = unserialize(serialize($res)); + + $this->assertTrue($res->isFresh(0)); + $this->assertTrue($ser->isFresh(0)); + + $this->assertSame((string) $res, (string) $ser); + } + + public function testIsFresh() + { + $res = new ReflectionClassResource(new \ReflectionClass(__CLASS__)); + $mtime = filemtime(__FILE__); + + $this->assertTrue($res->isFresh($mtime), '->isFresh() returns true if the resource has not changed in same second'); + $this->assertTrue($res->isFresh($mtime + 10), '->isFresh() returns true if the resource has not changed'); + $this->assertTrue($res->isFresh($mtime - 86400), '->isFresh() returns true if the resource has not changed'); + } + + public function testIsFreshForDeletedResources() + { + $now = time(); + $tmp = sys_get_temp_dir().'/tmp.php'; + file_put_contents($tmp, 'assertTrue($res->isFresh($now)); + + unlink($tmp); + $this->assertFalse($res->isFresh($now), '->isFresh() returns false if the resource does not exist'); + } + + /** + * @dataProvider provideHashedSignature + */ + public function testHashedSignature($changeExpected, $changedLine, $changedCode) + { + $code = <<<'EOPHP' +/* 0*/ +/* 1*/ class %s extends ErrorException +/* 2*/ { +/* 3*/ const FOO = 123; +/* 4*/ +/* 5*/ public $pub = array(); +/* 6*/ +/* 7*/ protected $prot; +/* 8*/ +/* 9*/ private $priv; +/*10*/ +/*11*/ public function pub($arg = null) {} +/*12*/ +/*13*/ protected function prot($a = array()) {} +/*14*/ +/*15*/ private function priv() {} +/*16*/ } +EOPHP; + + static $expectedSignature, $generateSignature; + + if (null === $expectedSignature) { + eval(sprintf($code, $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); + $r = new \ReflectionClass(ReflectionClassResource::class); + $generateSignature = $r->getMethod('generateSignature')->getClosure($r->newInstanceWithoutConstructor()); + $expectedSignature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); + } + + $code = explode("\n", $code); + $code[$changedLine] = $changedCode; + eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); + $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); + + if ($changeExpected) { + $this->assertTrue($expectedSignature !== $signature); + } else { + $this->assertSame($expectedSignature, $signature); + } + } + + public function provideHashedSignature() + { + yield array(0, 0, "// line change\n\n"); + yield array(1, 0, '/** class docblock */'); + yield array(1, 1, 'abstract class %s'); + yield array(1, 1, 'final class %s'); + yield array(1, 1, 'class %s extends Exception'); + yield array(1, 1, 'class %s implements '.DummyInterface::class); + yield array(1, 3, 'const FOO = 456;'); + yield array(1, 3, 'const BAR = 123;'); + yield array(1, 4, '/** pub docblock */'); + yield array(1, 5, 'protected $pub = array();'); + yield array(1, 5, 'public $pub = array(123);'); + yield array(1, 6, '/** prot docblock */'); + yield array(1, 7, 'private $prot;'); + yield array(0, 8, '/** priv docblock */'); + yield array(0, 9, 'private $priv = 123;'); + yield array(1, 10, '/** pub docblock */'); + if (PHP_VERSION_ID >= 50600) { + yield array(1, 11, 'public function pub(...$arg) {}'); + } + if (PHP_VERSION_ID >= 70000) { + yield array(1, 11, 'public function pub($arg = null): Foo {}'); + } + yield array(0, 11, "public function pub(\$arg = null) {\nreturn 123;\n}"); + yield array(1, 12, '/** prot docblock */'); + yield array(1, 13, 'protected function prot($a = array(123)) {}'); + yield array(0, 14, '/** priv docblock */'); + yield array(0, 15, ''); + } +} + +interface DummyInterface +{ +} diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php index ec31f8db385dd..632fb2e9e9c2f 100644 --- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Console\DependencyInjection; +use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * Registers console commands. @@ -34,9 +36,14 @@ public function process(ContainerBuilder $container) } $class = $container->getParameterBag()->resolveValue($definition->getClass()); - if (!is_subclass_of($class, 'Symfony\\Component\\Console\\Command\\Command')) { - throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "Symfony\\Component\\Console\\Command\\Command".', $id)); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "%s".', $id, Command::class)); } + $container->setAlias($serviceId = 'console.command.'.strtolower(str_replace('\\', '_', $class)), $id); $serviceIds[] = $definition->isPublic() ? $id : $serviceId; } diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index 68c82e146f005..362d55d1af8e9 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -23,7 +23,7 @@ "require-dev": { "symfony/http-kernel": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", "symfony/filesystem": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", "psr/log": "~1.0" @@ -34,6 +34,9 @@ "symfony/process": "", "psr/log": "For using the console logger" }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index c5ce7e49c6f47..947a6f3de8799 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 3.3.0 ----- + * added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info + * deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead * added `ContainerBuilder::fileExists()` for checking and tracking file or directory existence * deprecated autowiring-types, use aliases instead * [EXPERIMENTAL] added support for getter-injection diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 83cb204822f5e..a5765f4a6e723 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -24,7 +24,6 @@ */ class AutowirePass extends AbstractRecursivePass { - private $reflectionClasses = array(); private $definedTypes = array(); private $types; private $ambiguousServiceTypes = array(); @@ -34,16 +33,10 @@ class AutowirePass extends AbstractRecursivePass */ public function process(ContainerBuilder $container) { - $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); }; - spl_autoload_register($throwingAutoloader); - try { parent::process($container); } finally { - spl_autoload_unregister($throwingAutoloader); - // Free memory and remove circular reference to container - $this->reflectionClasses = array(); $this->definedTypes = array(); $this->types = null; $this->ambiguousServiceTypes = array(); @@ -56,9 +49,13 @@ public function process(ContainerBuilder $container) * @param \ReflectionClass $reflectionClass * * @return AutowireServiceResource + * + * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead. */ public static function createResourceForClass(\ReflectionClass $reflectionClass) { + @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED); + $metadata = array(); foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { @@ -79,14 +76,10 @@ protected function processValue($value, $isRoot = false) return parent::processValue($value, $isRoot); } - if (!$reflectionClass = $this->getReflectionClass($isRoot ? $this->currentId : null, $value)) { + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) { return parent::processValue($value, $isRoot); } - if ($this->container->isTrackingResources()) { - $this->container->addResource(static::createResourceForClass($reflectionClass)); - } - $autowiredMethods = $this->getMethodsToAutowire($reflectionClass, $autowiredMethods); $methodCalls = $value->getMethodCalls(); @@ -257,20 +250,9 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu } if (isset($this->types[$typeName])) { - $arguments[$index] = new Reference($this->types[$typeName]); + $value = new Reference($this->types[$typeName]); $didAutowire = true; - - continue; - } - - try { - $typeHint = new \ReflectionClass($typeName); - } catch (\ReflectionException $e) { - // Typehint against a non-existing class - $typeHint = false; - } - - if ($typeHint) { + } elseif ($typeHint = $this->container->getReflectionClass($typeName, true)) { try { $value = $this->createAutowiredDefinition($typeHint); $didAutowire = true; @@ -289,9 +271,11 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu } } } else { + // Typehint against a non-existing class + if (!$parameter->isDefaultValueAvailable()) { if ($mustAutowire) { - throw new RuntimeException(sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": %s.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $e->getMessage()), 0, $e); + throw new RuntimeException(sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": Class %s does not exist.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $typeName)); } return array(); @@ -344,7 +328,7 @@ private function populateAvailableType($id, Definition $definition) $this->types[$type] = $id; } - if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + if (!$reflectionClass = $this->container->getReflectionClass($definition->getClass(), true)) { return; } @@ -437,40 +421,6 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint) return new Reference($argumentId); } - /** - * Retrieves the reflection class associated with the given service. - * - * @param string|null $id - * @param Definition $definition - * - * @return \ReflectionClass|false - */ - private function getReflectionClass($id, Definition $definition) - { - if (null !== $id && isset($this->reflectionClasses[$id])) { - return $this->reflectionClasses[$id]; - } - - // Cannot use reflection if the class isn't set - if (!$class = $definition->getClass()) { - return false; - } - - $class = $this->container->getParameterBag()->resolveValue($class); - - try { - $reflector = new \ReflectionClass($class); - } catch (\ReflectionException $e) { - $reflector = false; - } - - if (null !== $id) { - $this->reflectionClasses[$id] = $reflector; - } - - return $reflector; - } - private function addServiceToAmbiguousType($id, $type) { // keep an array of all services matching this type @@ -482,6 +432,9 @@ private function addServiceToAmbiguousType($id, $type) $this->ambiguousServiceTypes[$type][] = $id; } + /** + * @deprecated since version 3.3, to be removed in 4.0. + */ private static function getResourceMetadataForMethod(\ReflectionMethod $method) { $methodArgumentsMetadata = array(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php index 1629097bc2e8c..eaeead071796f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php @@ -82,8 +82,11 @@ private function updateDefinition(ContainerBuilder $container, $id, Definition $ $class = $factory[0]; } + if (!$m = $container->getReflectionClass($class)) { + return; + } try { - $m = new \ReflectionMethod($class, $factory[1]); + $m = $m->getMethod($factory[1]); } catch (\ReflectionException $e) { return; } diff --git a/src/Symfony/Component/DependencyInjection/Config/AutowireServiceResource.php b/src/Symfony/Component/DependencyInjection/Config/AutowireServiceResource.php index 36449d8d3042a..afeaf18cbdfd0 100644 --- a/src/Symfony/Component/DependencyInjection/Config/AutowireServiceResource.php +++ b/src/Symfony/Component/DependencyInjection/Config/AutowireServiceResource.php @@ -11,9 +11,14 @@ namespace Symfony\Component\DependencyInjection\Config; +@trigger_error('The '.__NAMESPACE__.'\AutowireServiceResource class is deprecated since version 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED); + use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; use Symfony\Component\DependencyInjection\Compiler\AutowirePass; +/** + * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead. + */ class AutowireServiceResource implements SelfCheckingResourceInterface, \Serializable { private $class; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 33e844494c470..2d3829c7a09c8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -25,9 +25,11 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; @@ -246,14 +248,39 @@ public function setResources(array $resources) /** * Adds the object class hierarchy as resources. * - * @param object $object An object instance + * @param object|string $object An object instance or class name * * @return $this */ public function addObjectResource($object) { if ($this->trackResources) { - $this->addClassResource(new \ReflectionClass($object)); + if (is_object($object)) { + $object = get_class($object); + } + if (!isset($this->classReflectors[$object])) { + $this->classReflectors[$object] = new \ReflectionClass($object); + } + $class = $this->classReflectors[$object]; + + foreach ($class->getInterfaceNames() as $name) { + if (null === $interface = &$this->classReflectors[$name]) { + $interface = new \ReflectionClass($name); + } + $file = $interface->getFileName(); + if (false !== $file && file_exists($file)) { + $this->addResource(new FileResource($file)); + } + } + do { + $file = $class->getFileName(); + if (false !== $file && file_exists($file)) { + $this->addResource(new FileResource($file)); + } + foreach ($class->getTraitNames() as $name) { + $this->addObjectResource($name); + } + } while ($class = $class->getParentClass()); } return $this; @@ -265,20 +292,93 @@ public function addObjectResource($object) * @param \ReflectionClass $class * * @return $this + * + * @deprecated since version 3.3, to be removed in 4.0. Use addObjectResource() or getReflectionClass() instead. */ public function addClassResource(\ReflectionClass $class) { + @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 and will be removed in 4.0. Use the addObjectResource() or the getReflectionClass() method instead.', E_USER_DEPRECATED); + + return $this->addObjectResource($class->name); + } + + /** + * Retrieves the requested reflection class and registers it for resource tracking. + * + * @param string $class + * @param bool $koWithThrowingAutoloader Whether autoload should be protected against bad parents or not + * + * @return \ReflectionClass|null + * + * @final + */ + public function getReflectionClass($class, $koWithThrowingAutoloader = false) + { + if (!$class = $this->getParameterBag()->resolveValue($class)) { + return; + } + $resource = null; + + try { + if (isset($this->classReflectors[$class])) { + $classReflector = $this->classReflectors[$class]; + } elseif ($koWithThrowingAutoloader) { + $resource = new ClassExistenceResource($class, ClassExistenceResource::EXISTS_KO_WITH_THROWING_AUTOLOADER); + + $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class); + } else { + $classReflector = new \ReflectionClass($class); + } + } catch (\ReflectionException $e) { + $classReflector = false; + } + + if ($this->trackResources) { + if (!$classReflector) { + $this->addResource($resource ?: new ClassExistenceResource($class, ClassExistenceResource::EXISTS_KO)); + } elseif (!$classReflector->isInternal()) { + $this->addResource(new ReflectionClassResource($classReflector)); + } + $this->classReflectors[$class] = $classReflector; + } + + return $classReflector ?: null; + } + + /** + * Checks whether the requested file or directory exists and registers the result for resource tracking. + * + * @param string $path The file or directory path for which to check the existence + * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, + * it will be used as pattern for tracking contents of the requested directory + * + * @return bool + * + * @final + */ + public function fileExists($path, $trackContents = true) + { + $exists = file_exists($path); + if (!$this->trackResources) { - return $this; + return $exists; } - do { - if (is_file($class->getFileName())) { - $this->addResource(new FileResource($class->getFileName())); + if (!$exists) { + $this->addResource(new FileExistenceResource($path)); + + return $exists; + } + + if ($trackContents) { + if (is_file($path)) { + $this->addResource(new FileResource($path)); + } else { + $this->addResource(new DirectoryResource($path, is_string($trackContents) ? $trackContents : null)); } - } while ($class = $class->getParentClass()); + } - return $this; + return $exists; } /** @@ -574,8 +674,8 @@ public function compile() if (!$definition->isPublic()) { $this->privates[$id] = true; } - if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { - $this->addClassResource(new \ReflectionClass($class)); + if ($this->trackResources && $definition->isLazy()) { + $this->getReflectionClass($definition->getClass()); } } @@ -1323,42 +1423,6 @@ public static function getServiceConditionals($value) return $services; } - /** - * Checks whether the requested file or directory exists and registers the result for resource tracking. - * - * @param string $path The file or directory path for which to check the existence - * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, - * it will be used as pattern for tracking contents of the requested directory - * - * @return bool - * - * @final - */ - public function fileExists($path, $trackContents = true) - { - $exists = file_exists($path); - - if (!$this->trackResources) { - return $exists; - } - - if (!$exists) { - $this->addResource(new FileExistenceResource($path)); - - return $exists; - } - - if ($trackContents) { - if (is_file($path)) { - $this->addResource(new FileResource($path)); - } else { - $this->addResource(new DirectoryResource($path, is_string($trackContents) ? $trackContents : null)); - } - } - - return $exists; - } - /** * Retrieves the currently set proxy instantiator or instantiates one. * diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 0597bd8c426ad..69e2a4820188c 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -64,7 +64,6 @@ class PhpDumper extends Dumper private $docStar; private $serviceIdToMethodNameMap; private $usedMethodNames; - private $classResources = array(); private $baseClass; private $getterProxies = array(); private $useInstantiateProxy; @@ -127,7 +126,6 @@ public function dump(array $options = array()) $this->salt = substr(strtr(base64_encode(md5($options['namespace'].'\\'.$options['class'].'+'.$options['base_class'], true)), '+/', '__'), 0, -2); $this->getterProxies = array(); $this->useInstantiateProxy = false; - $this->classResources = array(); $this->initializeMethodNamesMap($options['base_class']); $this->baseClass = $options['base_class']; @@ -177,16 +175,6 @@ public function dump(array $options = array()) $this->targetDirRegex = null; $this->getterProxies = array(); - foreach ($this->classResources as $r) { - $this->container->addClassResource($r); - } - $this->classResources = array(); - - foreach ($this->classResources as $r) { - $this->container->addClassResource($r); - } - $this->classResources = array(); - $unusedEnvs = array(); foreach ($this->container->getEnvCounters() as $env => $use) { if (!$use) { @@ -508,10 +496,7 @@ private function addServiceMethodCalls($id, Definition $definition, $variableNam private function addServiceOverriddenGetters($id, Definition $definition) { - if (!isset($this->classResources[$class = $definition->getClass()])) { - $this->classResources[$class] = new \ReflectionClass($class); - } - $class = $this->classResources[$class]; + $class = $this->container->getReflectionClass($definition->getClass()); if ($class->isFinal()) { throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": class "%s" cannot be marked as final.', $id, $class->name)); } @@ -864,7 +849,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation, $getterProxy = sprintf("%s implements \\%s\n{\n private \$container%s;\n private \$getters%3\$s;\n%s}\n", $class, GetterProxyInterface::class, $this->salt, $this->addServiceOverriddenGetters($id, $definition)); $class = 'SymfonyProxy_'.md5($getterProxy); $this->getterProxies[$class] = $getterProxy; - $constructor = $this->classResources[$definition->getClass()]->getConstructor(); + $constructor = $this->container->getReflectionClass($definition->getClass())->getConstructor(); if ($constructor && $constructor->isFinal()) { $this->useInstantiateProxy = true; @@ -1607,10 +1592,7 @@ private function dumpValue($value, $interpolate = true) if (!method_exists($class, $method)) { throw new InvalidArgumentException(sprintf('Cannot create closure-proxy for service "%s": method "%s::%s" does not exist.', $reference, $class, $method)); } - if (!isset($this->classResources[$class])) { - $this->classResources[$class] = new \ReflectionClass($class); - } - $r = $this->classResources[$class]->getMethod($method); + $r = $this->container->getReflectionClass($class)->getMethod($method); if (!$r->isPublic()) { throw new InvalidArgumentException(sprintf('Cannot create closure-proxy for service "%s": method "%s::%s" must be public.', $reference, $class, $method)); } @@ -1731,12 +1713,10 @@ private function initializeMethodNamesMap($class) $this->serviceIdToMethodNameMap = array(); $this->usedMethodNames = array(); - try { - $reflectionClass = new \ReflectionClass($class); + if ($reflectionClass = $this->container->getReflectionClass($class)) { foreach ($reflectionClass->getMethods() as $method) { $this->usedMethodNames[strtolower($method->getName())] = true; } - } catch (\ReflectionException $e) { } } diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php index ced39f7281b6c..44c95c0ba6cc0 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php +++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php @@ -14,7 +14,6 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -78,17 +77,13 @@ public function getAlias() */ public function getConfiguration(array $config, ContainerBuilder $container) { - $reflected = new \ReflectionClass($this); - $namespace = $reflected->getNamespaceName(); + $class = get_class($this); + $class = substr_replace($class, '\Configuration', strrpos($class, '\\')); + $class = $container->getReflectionClass($class); + $constructor = $class ? $class->getConstructor() : null; - $class = $namespace.'\\Configuration'; - if (class_exists($class)) { - $r = new \ReflectionClass($class); - $container->addResource(new FileResource($r->getFileName())); - - if (!method_exists($class, '__construct')) { - return new $class(); - } + if (!$constructor || !$constructor->getNumberOfRequiredParameters()) { + return $class->newInstance(); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index e9a8c2fc68356..c8a98afa355e4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -518,6 +518,7 @@ public function testExplicitMethodInjection() /** * @dataProvider getCreateResourceTests + * @group legacy */ public function testCreateResourceForClass($className, $isEqual) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/AutowireServiceResourceTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/AutowireServiceResourceTest.php index 5c2704db23958..69749751714cc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Config/AutowireServiceResourceTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/AutowireServiceResourceTest.php @@ -14,6 +14,9 @@ use Symfony\Component\DependencyInjection\Compiler\AutowirePass; use Symfony\Component\DependencyInjection\Config\AutowireServiceResource; +/** + * @group legacy + */ class AutowireServiceResourceTest extends \PHPUnit_Framework_TestCase { /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index a97fd19cbd455..628faa1c588ba 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -622,6 +622,9 @@ public function testAddObjectResource() $this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource())); } + /** + * @group legacy + */ public function testAddClassResource() { $container = new ContainerBuilder(); @@ -645,6 +648,32 @@ public function testAddClassResource() $this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource())); } + public function testGetReflectionClass() + { + $container = new ContainerBuilder(); + + $container->setResourceTracking(false); + $r1 = $container->getReflectionClass('BarClass'); + + $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + + $container->setResourceTracking(true); + $r2 = $container->getReflectionClass('BarClass'); + $r3 = $container->getReflectionClass('BarClass'); + + $this->assertNull($container->getReflectionClass('BarMissingClass')); + + $this->assertEquals($r1, $r2); + $this->assertSame($r2, $r3); + + $resources = $container->getResources(); + + $this->assertCount(2, $resources, '2 resources were registered'); + + $this->assertSame('reflection.BarClass', (string) $resources[0]); + $this->assertSame('BarMissingClass', (string) end($resources)); + } + public function testCompilesClassDefinitionsOfLazyServices() { $container = new ContainerBuilder(); @@ -656,11 +685,10 @@ public function testCompilesClassDefinitionsOfLazyServices() $container->compile(); - $classesPath = realpath(__DIR__.'/Fixtures/includes/classes.php'); $matchingResources = array_filter( $container->getResources(), - function (ResourceInterface $resource) use ($classesPath) { - return $resource instanceof FileResource && $classesPath === realpath($resource->getResource()); + function (ResourceInterface $resource) { + return 'reflection.BarClass' === (string) $resource; } ); @@ -899,16 +927,13 @@ public function testLazyLoadedService() $container->compile(); - $class = new \BazClass(); - $reflectionClass = new \ReflectionClass($class); - $r = new \ReflectionProperty($container, 'resources'); $r->setAccessible(true); $resources = $r->getValue($container); $classInList = false; foreach ($resources as $resource) { - if ($resource->getResource() === $reflectionClass->getFileName()) { + if ('reflection.BazClass' === (string) $resource) { $classInList = true; break; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 7877a64436071..5b116751da12b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -323,7 +323,7 @@ public function testDumpOverridenGetters() $dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Overriden_Getters')); $this->assertStringEqualsFile(self::$fixturesPath.'/php/services29.php', $dump); $res = $container->getResources(); - $this->assertSame(realpath(self::$fixturesPath.'/containers/container29.php'), array_pop($res)->getResource()); + $this->assertSame('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Container29\Foo', (string) array_pop($res)); eval('?>'.$dump); @@ -529,7 +529,7 @@ public function testClosureProxy() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services31.php', $dumper->dump()); $res = $container->getResources(); - $this->assertSame(realpath(self::$fixturesPath.'/containers/container31.php'), array_pop($res)->getResource()); + $this->assertSame('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Container31\Foo', (string) array_pop($res)); } /** @@ -543,7 +543,7 @@ public function testClosureProxyPhp71() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services32.php', $dumper->dump()); $res = $container->getResources(); - $this->assertSame(realpath(self::$fixturesPath.'/containers/container32.php'), array_pop($res)->getResource()); + $this->assertSame('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Container32\Foo', (string) array_pop($res)); } public function testNormalizedId() diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index b658759e7b7d2..0d08d092be6de 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "symfony/yaml": "~3.2", - "symfony/config": "~2.8|~3.0", + "symfony/config": "~3.3", "symfony/expression-language": "~2.8|~3.0" }, "suggest": { @@ -30,6 +30,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { + "symfony/config": "<3.3", "symfony/yaml": "<3.2" }, "autoload": { diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php index 8f69de1b969cf..ec5e4e8747a02 100644 --- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -103,6 +103,7 @@ public function process(ContainerBuilder $container) throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); } + $container->addObjectResource($class); $r = new \ReflectionClass($class); $extractingDispatcher->addSubscriber($r->newInstanceWithoutConstructor()); diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php index 95cc38827db27..6583d0f7d40bc 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; /** * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. @@ -53,14 +54,12 @@ public function process(ContainerBuilder $container) } $class = $container->getParameterBag()->resolveValue($def->getClass()); - $interface = 'Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'; - if (!is_subclass_of($class, $interface)) { - if (!class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); - } - - throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(FragmentRendererInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); } foreach ($tags as $tag) { diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php index 85dad766ed19f..2db7f5bb03fd6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -73,7 +73,7 @@ public function testValidContentRenderer() ->will($this->returnValue(true)) ; - $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'getReflectionClass'))->getMock(); $builder->expects($this->any()) ->method('hasDefinition') ->will($this->returnValue(true)); @@ -87,6 +87,11 @@ public function testValidContentRenderer() ->method('getDefinition') ->will($this->onConsecutiveCalls($renderer, $definition)); + $builder->expects($this->atLeastOnce()) + ->method('getReflectionClass') + ->with('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') + ->will($this->returnValue(new \ReflectionClass('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService'))); + $pass = new FragmentRendererPass(); $pass->process($builder); } diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index eb72ca763287d..4df383ea2cbde 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -28,7 +28,7 @@ "symfony/config": "~2.8|~3.0", "symfony/console": "~2.8|~3.0", "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", "symfony/dom-crawler": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", @@ -41,7 +41,8 @@ "psr/cache": "~1.0" }, "conflict": { - "symfony/config": "<2.8" + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3" }, "suggest": { "symfony/browser-kit": "",