Skip to content

Commit d287508

Browse files
[DI][Config] Add & use ReflectionClassResource
1 parent b9b6ebd commit d287508

21 files changed

+551
-154
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+3-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
use Symfony\Component\Finder\Finder;
2929
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
3030
use Symfony\Component\Config\FileLocator;
31-
use Symfony\Component\Config\Resource\ClassExistenceResource;
3231
use Symfony\Component\PropertyAccess\PropertyAccessor;
3332
use Symfony\Component\Serializer\Encoder\YamlEncoder;
3433
use Symfony\Component\Serializer\Encoder\CsvEncoder;
@@ -85,8 +84,7 @@ public function load(array $configs, ContainerBuilder $container)
8584

8685
$loader->load('fragment_renderer.xml');
8786

88-
$container->addResource(new ClassExistenceResource(Application::class));
89-
if (class_exists(Application::class)) {
87+
if ($container->getClassExists(Application::class)) {
9088
$loader->load('console.xml');
9189
}
9290

@@ -525,7 +523,7 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con
525523
$definition->replaceArgument(4, $debug);
526524
$definition->replaceArgument(6, $debug);
527525

528-
if ($debug && class_exists(DebugProcessor::class)) {
526+
if ($debug && $container->getClassExists(DebugProcessor::class)) {
529527
$definition = new Definition(DebugProcessor::class);
530528
$definition->setPublic(false);
531529
$container->setDefinition('debug.log_processor', $definition);
@@ -1251,7 +1249,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
12511249
$container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument(
12521250
1, new Reference($config['cache'])
12531251
);
1254-
} elseif (!$container->getParameter('kernel.debug') && class_exists(CacheClassMetadataFactory::class)) {
1252+
} elseif (!$container->getParameter('kernel.debug') && $container->getClassExists(CacheClassMetadataFactory::class)) {
12551253
$cacheMetadataFactory = new Definition(
12561254
CacheClassMetadataFactory::class,
12571255
array(

src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\Debug\Exception\FlattenException;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1618

1719
/**
1820
* Registers the Twig exception listener if Twig is registered as a templating engine.
@@ -28,7 +30,7 @@ public function process(ContainerBuilder $container)
2830
}
2931

3032
// register the exception controller only if Twig is enabled and required dependencies do exist
31-
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
33+
if (!$container->getClassExists(FlattenException::class) || !$container->getClassExists(EventSubscriberInterface::class)) {
3234
$container->removeDefinition('twig.exception_listener');
3335
} elseif ($container->hasParameter('templating.engines')) {
3436
$engines = $container->getParameter('templating.engines');

src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php

+12-12
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
1313

14-
use Symfony\Component\Config\Resource\ClassExistenceResource;
14+
use Symfony\Component\Asset\Packages;
1515
use Symfony\Component\DependencyInjection\Alias;
1616
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
1818
use Symfony\Component\DependencyInjection\Reference;
1919
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
20+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2021
use Symfony\Component\Stopwatch\Stopwatch;
22+
use Symfony\Component\Translation\TranslatorInterface;
2123
use Symfony\Component\Yaml\Parser as YamlParser;
2224

2325
/**
@@ -27,22 +29,23 @@ class ExtensionPass implements CompilerPassInterface
2729
{
2830
public function process(ContainerBuilder $container)
2931
{
30-
if (!class_exists('Symfony\Component\Asset\Packages')) {
32+
if (!$container->getClassExists(Packages::class)) {
3133
$container->removeDefinition('twig.extension.assets');
3234
}
3335

34-
if (!class_exists('Symfony\Component\ExpressionLanguage\Expression')) {
36+
if (!$container->getClassExists(ExpressionLanguage::class)) {
3537
$container->removeDefinition('twig.extension.expression');
3638
}
3739

38-
if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) {
40+
if (!$container->getClassExists(UrlGeneratorInterface::class)) {
3941
$container->removeDefinition('twig.extension.routing');
4042
}
41-
if (!interface_exists('Symfony\Component\Translation\TranslatorInterface')) {
43+
44+
if (!$container->getClassExists(TranslatorInterface::class)) {
4245
$container->removeDefinition('twig.extension.trans');
4346
}
4447

45-
if (!class_exists('Symfony\Component\Yaml\Yaml')) {
48+
if (!$container->getClassExists(YamlParser::class)) {
4649
$container->removeDefinition('twig.extension.yaml');
4750
}
4851

@@ -101,18 +104,15 @@ public function process(ContainerBuilder $container)
101104
$container->getDefinition('twig.extension.assets')->addTag('twig.extension');
102105
}
103106

104-
$container->addResource(new ClassExistenceResource(YamlParser::class));
105-
if (class_exists(YamlParser::class)) {
107+
if ($container->hasDefinition('twig.extension.yaml')) {
106108
$container->getDefinition('twig.extension.yaml')->addTag('twig.extension');
107109
}
108110

109-
$container->addResource(new ClassExistenceResource(Stopwatch::class));
110-
if (class_exists(Stopwatch::class)) {
111+
if ($container->getClassExists(Stopwatch::class)) {
111112
$container->getDefinition('twig.extension.debug.stopwatch')->addTag('twig.extension');
112113
}
113114

114-
$container->addResource(new ClassExistenceResource(ExpressionLanguage::class));
115-
if (class_exists(ExpressionLanguage::class)) {
115+
if ($container->hasDefinition('twig.extension.expression')) {
116116
$container->getDefinition('twig.extension.expression')->addTag('twig.extension');
117117
}
118118
}

src/Symfony/Bundle/TwigBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
],
1818
"require": {
1919
"php": ">=5.5.9",
20-
"symfony/config": "~3.2",
20+
"symfony/config": "~3.3",
2121
"symfony/twig-bridge": "^3.2.1",
2222
"symfony/http-foundation": "~2.8|~3.0",
2323
"symfony/http-kernel": "~2.8.16|~3.1.9|^3.2.2",
@@ -26,7 +26,7 @@
2626
"require-dev": {
2727
"symfony/asset": "~2.8|~3.0",
2828
"symfony/stopwatch": "~2.8|~3.0",
29-
"symfony/dependency-injection": "~2.8|~3.0",
29+
"symfony/dependency-injection": "~3.3",
3030
"symfony/expression-language": "~2.8|~3.0",
3131
"symfony/finder": "~2.8|~3.0",
3232
"symfony/form": "~2.8|~3.0",

src/Symfony/Component/Config/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
CHANGELOG
22
=========
33

4+
3.3.0
5+
-----
6+
7+
* added `ReflectionClassResource` class
8+
* added second `$exists` constructor argument to `ClassExistenceResource`
9+
* made `ClassExistenceResource` also work with interfaces and traits
10+
411
3.0.0
512
-----
613

src/Symfony/Component/Config/Resource/ClassExistenceResource.php

+37-4
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@
2121
*/
2222
class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable
2323
{
24+
const EXISTS_OK = 1;
25+
const EXISTS_KO = 0;
26+
const EXISTS_KO_WITH_THROWING_AUTOLOADER = -1;
27+
2428
private $resource;
2529
private $exists;
2630

31+
private static $checkingLevel = 0;
32+
private static $throwingAutoloader;
33+
private static $existsCache = array();
34+
2735
/**
28-
* @param string $resource The fully-qualified class name
36+
* @param string $resource The fully-qualified class name
37+
* @param int|null $exists One of the self::EXISTS_* const if the existency check has already been done
2938
*/
30-
public function __construct($resource)
39+
public function __construct($resource, $exists = null)
3140
{
3241
$this->resource = $resource;
33-
$this->exists = class_exists($resource);
42+
$this->exists = null !== $exists ? (int) $exists : (class_exists($resource) || interface_exists($resource, false) || trait_exists($resource, false));
3443
}
3544

3645
/**
@@ -54,7 +63,31 @@ public function getResource()
5463
*/
5564
public function isFresh($timestamp)
5665
{
57-
return class_exists($this->resource) === $this->exists;
66+
if (isset(self::$existsCache[$this->resource])) {
67+
// no-op
68+
} elseif (self::EXISTS_KO_WITH_THROWING_AUTOLOADER === $this->exists) {
69+
if (null === self::$throwingAutoloader) {
70+
$signalingException = new \ReflectionException();
71+
self::$throwingAutoloader = function () use ($signalingException) { throw $signalingException; };
72+
}
73+
if (!self::$checkingLevel++) {
74+
spl_autoload_register(self::$throwingAutoloader);
75+
}
76+
77+
try {
78+
self::$existsCache[$this->resource] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
79+
} catch (\ReflectionException $e) {
80+
self::$existsCache[$this->resource] = false;
81+
} finally {
82+
if (!--self::$checkingLevel) {
83+
spl_autoload_unregister(self::$throwingAutoloader);
84+
}
85+
}
86+
} else {
87+
self::$existsCache[$this->resource] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
88+
}
89+
90+
return self::EXISTS_OK == $this->exists xor !self::$existsCache[$this->resource];
5891
}
5992

6093
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Resource;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*/
17+
class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable
18+
{
19+
private $file;
20+
private $className;
21+
private $classReflector;
22+
private $hash;
23+
24+
public function __construct(\ReflectionClass $classReflector)
25+
{
26+
$this->file = $classReflector->getFileName();
27+
if ($this->file && !file_exists($this->file)) {
28+
$this->file = false;
29+
}
30+
$this->className = $classReflector->name;
31+
$this->classReflector = $classReflector;
32+
$this->hash = $this->computeHash();
33+
}
34+
35+
public function isFresh($timestamp)
36+
{
37+
if ($this->file) {
38+
if (!file_exists($this->file)) {
39+
return false;
40+
}
41+
42+
if (@filemtime($this->file) <= $timestamp) {
43+
return true;
44+
}
45+
}
46+
47+
return $this->hash === $this->computeHash();
48+
}
49+
50+
public function __toString()
51+
{
52+
return 'reflection.'.$this->className;
53+
}
54+
55+
public function serialize()
56+
{
57+
return serialize(array($this->file, $this->className, $this->hash));
58+
}
59+
60+
public function unserialize($serialized)
61+
{
62+
list($this->file, $this->className, $this->hash) = unserialize($serialized);
63+
}
64+
65+
private function computeHash()
66+
{
67+
if (null === $this->classReflector) {
68+
try {
69+
$this->classReflector = new \ReflectionClass($this->className);
70+
} catch (\ReflectionException $e) {
71+
// the class does not exist anymore
72+
return false;
73+
}
74+
}
75+
$hash = hash_init('md5');
76+
77+
foreach ($this->generateSignature($this->classReflector) as $info) {
78+
hash_update($hash, $info);
79+
}
80+
81+
return hash_final($hash);
82+
}
83+
84+
private function generateSignature(\ReflectionClass $class)
85+
{
86+
yield $class->getDocComment().$class->getModifiers();
87+
yield print_r(class_parents($class->name), true);
88+
yield print_r(class_implements($class->name), true);
89+
yield print_r($class->getConstants(), true);
90+
91+
$defaults = $class->getDefaultProperties();
92+
93+
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
94+
yield $p->getDocComment().$p;
95+
yield print_r($defaults[$p->name], true);
96+
}
97+
98+
if (defined('HHVM_VERSION')) {
99+
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
100+
// workaround HHVM bug with variadics, see https://github.com/facebook/hhvm/issues/5762
101+
yield preg_replace('/^ @@.*/m', '', new ReflectionMethodHhvmWrapper($m->class, $m->name));
102+
}
103+
} else {
104+
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
105+
yield preg_replace('/^ @@.*/m', '', $m);
106+
107+
$defaults = array();
108+
foreach ($m->getParameters() as $p) {
109+
$defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null;
110+
}
111+
yield print_r($defaults, true);
112+
}
113+
}
114+
}
115+
}
116+
117+
/**
118+
* @internal
119+
*/
120+
class ReflectionMethodHhvmWrapper extends \ReflectionMethod
121+
{
122+
public function getParameters()
123+
{
124+
$params = array();
125+
126+
foreach (parent::getParameters() as $i => $p) {
127+
$params[] = new ReflectionParameterHhvmWrapper(array($this->class, $this->name), $i);
128+
}
129+
130+
return $params;
131+
}
132+
}
133+
134+
/**
135+
* @internal
136+
*/
137+
class ReflectionParameterHhvmWrapper extends \ReflectionParameter
138+
{
139+
public function getDefaultValue()
140+
{
141+
return array($this->isVariadic(), $this->isDefaultValueAvailable() ? parent::getDefaultValue() : null);
142+
}
143+
}

src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function testIsFreshWhenClassDoesNotExist()
3636
eval(<<<EOF
3737
namespace Symfony\Component\Config\Tests\Fixtures;
3838
39-
class BarClass
39+
interface BarClass
4040
{
4141
}
4242
EOF
@@ -50,5 +50,21 @@ public function testIsFreshWhenClassExists()
5050
$res = new ClassExistenceResource('Symfony\Component\Config\Tests\Resource\ClassExistenceResourceTest');
5151

5252
$this->assertTrue($res->isFresh(time()));
53+
$this->assertTrue(class_exists('Symfony\Component\Config\Tests\Resource\ClassExistenceResourceTest', false));
54+
}
55+
56+
public function testExistsKo()
57+
{
58+
spl_autoload_register(function ($class) use (&$loadedClass) { $loadedClass = $class; });
59+
60+
$res = new ClassExistenceResource('MissingFooClass');
61+
62+
$this->assertSame('MissingFooClass', $loadedClass);
63+
64+
$loadedClass = 123;
65+
66+
$res = new ClassExistenceResource('MissingFooClass', ClassExistenceResource::EXISTS_KO);
67+
68+
$this->assertSame(123, $loadedClass);
5369
}
5470
}

0 commit comments

Comments
 (0)