[DI][Config] Add & use ReflectionClassResource

This commit is contained in:
Nicolas Grekas 2017-01-26 16:09:57 +01:00
parent 35a49fb577
commit 37e44939ef
26 changed files with 617 additions and 177 deletions

View File

@ -13,7 +13,10 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
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 <a.aitboudad@gmail.com>
@ -31,7 +34,10 @@ class LoggingTranslatorPass implements CompilerPassInterface
$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'));
}

View File

@ -54,6 +54,11 @@ class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase
->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);
}

View File

@ -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",

View File

@ -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
-----

View File

@ -21,16 +21,27 @@ namespace Symfony\Component\Config\Resource;
*/
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 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
*/
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 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
*/
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 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
*/
public function unserialize($serialized)
{
list($this->resource, $this->exists) = unserialize($serialized);
list($this->resource, $this->existsStatus) = unserialize($serialized);
}
}

View File

@ -0,0 +1,171 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <p@tchwork.com>
*/
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);
}
}

View File

@ -51,4 +51,24 @@ EOF
$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);
}
}
}

View File

@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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, '<?php class ReflectionClassResourceTestClass {}');
require $tmp;
$res = new ReflectionClassResource(new \ReflectionClass('ReflectionClassResourceTestClass'));
$this->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
{
}

View File

@ -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 @@ class AddConsoleCommandPass implements CompilerPassInterface
}
$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;
}

View File

@ -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": [

View File

@ -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

View File

@ -24,7 +24,6 @@ use Symfony\Component\DependencyInjection\Reference;
*/
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 @@ class AutowirePass extends AbstractRecursivePass
* @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 @@ class AutowirePass extends AbstractRecursivePass
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 @@ class AutowirePass extends AbstractRecursivePass
}
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 @@ class AutowirePass extends AbstractRecursivePass
}
}
} 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 @@ class AutowirePass extends AbstractRecursivePass
$this->types[$type] = $id;
}
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
if (!$reflectionClass = $this->container->getReflectionClass($definition->getClass(), true)) {
return;
}
@ -437,40 +421,6 @@ class AutowirePass extends AbstractRecursivePass
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 @@ class AutowirePass extends AbstractRecursivePass
$this->ambiguousServiceTypes[$type][] = $id;
}
/**
* @deprecated since version 3.3, to be removed in 4.0.
*/
private static function getResourceMetadataForMethod(\ReflectionMethod $method)
{
$methodArgumentsMetadata = array();

View File

@ -82,8 +82,11 @@ class FactoryReturnTypePass implements CompilerPassInterface
$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;
}

View File

@ -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;

View File

@ -25,9 +25,11 @@ use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceExce
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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
/**
* 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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* @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)
{
if (!$this->trackResources) {
return $this;
@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;
}
do {
if (is_file($class->getFileName())) {
$this->addResource(new FileResource($class->getFileName()));
if ($this->trackResources) {
if (!$classReflector) {
$this->addResource($resource ?: new ClassExistenceResource($class, ClassExistenceResource::EXISTS_KO));
} elseif (!$classReflector->isInternal()) {
$this->addResource(new ReflectionClassResource($classReflector));
}
} while ($class = $class->getParentClass());
$this->classReflectors[$class] = $classReflector;
}
return $this;
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 $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;
}
/**
@ -574,8 +674,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
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 @@ EOF;
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.
*

View File

@ -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 @@ class PhpDumper extends Dumper
$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 @@ class PhpDumper extends Dumper
$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 @@ class PhpDumper extends Dumper
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 @@ EOF;
$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 @@ EOF;
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 @@ EOF;
$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) {
}
}

View File

@ -14,7 +14,6 @@ namespace Symfony\Component\DependencyInjection\Extension;
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 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn
*/
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();
}
}

View File

@ -518,6 +518,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider getCreateResourceTests
* @group legacy
*/
public function testCreateResourceForClass($className, $isEqual)
{

View File

@ -14,6 +14,9 @@ namespace Symfony\Component\DependencyInjection\Tests\Config;
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
/**
* @group legacy
*/
class AutowireServiceResourceTest extends \PHPUnit_Framework_TestCase
{
/**

View File

@ -622,6 +622,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource()));
}
/**
* @group legacy
*/
public function testAddClassResource()
{
$container = new ContainerBuilder();
@ -645,6 +648,32 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$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 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$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 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$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;
}

View File

@ -323,7 +323,7 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
$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 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
$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 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
$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()

View File

@ -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": {

View File

@ -103,6 +103,7 @@ class RegisterListenersPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$container->addObjectResource($class);
$r = new \ReflectionClass($class);
$extractingDispatcher->addSubscriber($r->newInstanceWithoutConstructor());

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\DependencyInjection;
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 @@ class FragmentRendererPass implements CompilerPassInterface
}
$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) {

View File

@ -73,7 +73,7 @@ class FragmentRendererPassTest extends \PHPUnit_Framework_TestCase
->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 @@ class FragmentRendererPassTest extends \PHPUnit_Framework_TestCase
->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);
}

View File

@ -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": "",