Skip to content

Commit 79cf689

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

14 files changed

+235
-78
lines changed

src/Symfony/Component/Config/CHANGELOG.md

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

4+
3.3.0
5+
-----
6+
7+
* added `ReflectionClassResource` class
8+
49
3.0.0
510
-----
611

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
$this->className = $classReflector->name;
28+
$this->classReflector = $classReflector;
29+
$this->hash = $this->computeHash();
30+
}
31+
32+
public function isFresh($timestamp)
33+
{
34+
if (false !== $this->file) {
35+
if (!file_exists($this->file)) {
36+
return false;
37+
}
38+
39+
if (@filemtime($this->file) <= $timestamp) {
40+
return true;
41+
}
42+
}
43+
44+
return $this->hash === $this->computeHash();
45+
}
46+
47+
public function __toString()
48+
{
49+
return 'reflection.'.$this->className;
50+
}
51+
52+
public function serialize()
53+
{
54+
return serialize(array($this->file, $this->className, $this->hash));
55+
}
56+
57+
public function unserialize($serialized)
58+
{
59+
list($this->file, $this->className, $this->hash) = unserialize($serialized);
60+
}
61+
62+
private function computeHash()
63+
{
64+
if (null === $this->classReflector) {
65+
try {
66+
$this->classReflector = new \ReflectionClass($this->className);
67+
} catch (\ReflectionException $e) {
68+
// the class does not exist anymore
69+
return false;
70+
}
71+
}
72+
73+
$class = $this->classReflector;
74+
$hash = hash_init('md5');
75+
76+
hash_update($hash, $class->getDocComment().$class->getModifiers());
77+
hash_update($hash, print_r(class_parents($class->name), true));
78+
hash_update($hash, print_r(class_implements($class->name), true));
79+
hash_update($hash, print_r($class->getConstants(), true));
80+
81+
$defaults = $class->getDefaultProperties();
82+
83+
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $r) {
84+
hash_update($hash, $r->getDocComment().$r);
85+
hash_update($hash, print_r($defaults[$r->name], true));
86+
}
87+
88+
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $r) {
89+
hash_update($hash, preg_replace('/^ @@.*/m', '', $r));
90+
91+
$defaults = array();
92+
foreach ($r->getParameters() as $r) {
93+
$defaults[$r->name] = $r->isDefaultValueAvailable() ? $r->getDefaultValue() : null;
94+
}
95+
96+
hash_update($hash, print_r($defaults, true));
97+
}
98+
99+
return hash_final($hash);
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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\Tests\Resource;
13+
14+
use Symfony\Component\Config\Resource\ReflectionClassResource;
15+
16+
class ReflectionClassResourceTest extends \PHPUnit_Framework_TestCase
17+
{
18+
public function testToString()
19+
{
20+
$res = new ReflectionClassResource(new \ReflectionClass('ErrorException'));
21+
22+
$this->assertSame('reflection.ErrorException', (string) $res);
23+
}
24+
25+
public function testSerializeUnserialize()
26+
{
27+
$res = new ReflectionClassResource(new \ReflectionClass('ErrorException'));
28+
$ser = unserialize(serialize($res));
29+
30+
$this->assertTrue($ser->isFresh(0));
31+
$this->assertEquals($res, $ser);
32+
}
33+
34+
public function testIsFresh()
35+
{
36+
$res = new ReflectionClassResource(new \ReflectionClass(__CLASS__));
37+
$mtime = filemtime(__FILE__);
38+
39+
$this->assertTrue($res->isFresh($mtime), '->isFresh() returns true if the resource has not changed in same second');
40+
$this->assertTrue($res->isFresh($mtime + 10), '->isFresh() returns true if the resource has not changed');
41+
$this->assertTrue($res->isFresh($mtime - 86400), '->isFresh() returns true if the resource has not changed');
42+
}
43+
44+
public function testIsFreshForDeletedResources()
45+
{
46+
$now = time();
47+
$tmp = sys_get_temp_dir().'/tmp.php';
48+
file_put_contents($tmp, '<?php class ReflectionClassResourceTestClass {}');
49+
require $tmp;
50+
51+
$res = new ReflectionClassResource(new \ReflectionClass('ReflectionClassResourceTestClass'));
52+
$this->assertTrue($res->isFresh($now));
53+
54+
unlink($tmp);
55+
$this->assertFalse($res->isFresh($now), '->isFresh() returns false if the resource does not exist');
56+
}
57+
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.3.0
55
-----
66

7+
* added method `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info
78
* added support for omitting the factory class name in a service definition if the definition class is set
89
* deprecated case insensitivity of service identifiers
910
* added "iterator" argument type for lazy iteration over a set of values and services

src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

+10-42
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\Config\Resource\ReflectionClassResource;
1415
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Definition;
@@ -24,7 +25,6 @@
2425
*/
2526
class AutowirePass extends AbstractRecursivePass implements CompilerPassInterface
2627
{
27-
private $reflectionClasses = array();
2828
private $definedTypes = array();
2929
private $types;
3030
private $ambiguousServiceTypes = array();
@@ -43,7 +43,6 @@ public function process(ContainerBuilder $container)
4343
spl_autoload_unregister($throwingAutoloader);
4444

4545
// Free memory and remove circular reference to container
46-
$this->reflectionClasses = array();
4746
$this->definedTypes = array();
4847
$this->types = null;
4948
$this->ambiguousServiceTypes = array();
@@ -56,9 +55,13 @@ public function process(ContainerBuilder $container)
5655
* @param \ReflectionClass $reflectionClass
5756
*
5857
* @return AutowireServiceResource
58+
*
59+
* @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.
5960
*/
6061
public static function createResourceForClass(\ReflectionClass $reflectionClass)
6162
{
63+
@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);
64+
6265
$metadata = array();
6366

6467
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
@@ -79,14 +82,10 @@ protected function processValue($value, $isRoot = false)
7982
return parent::processValue($value, $isRoot);
8083
}
8184

82-
if (!$reflectionClass = $this->getReflectionClass($isRoot ? $this->currentId : null, $value)) {
85+
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
8386
return parent::processValue($value, $isRoot);
8487
}
8588

86-
if ($this->container->isTrackingResources()) {
87-
$this->container->addResource(static::createResourceForClass($reflectionClass));
88-
}
89-
9089
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass, $autowiredMethods);
9190
$methodCalls = $value->getMethodCalls();
9291

@@ -321,7 +320,7 @@ private function populateAvailableType($id, Definition $definition)
321320
$this->types[$type] = $id;
322321
}
323322

324-
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
323+
if (!$reflectionClass = $this->container->getReflectionClass($definition->getClass())) {
325324
return;
326325
}
327326

@@ -414,40 +413,6 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
414413
return new Reference($argumentId);
415414
}
416415

417-
/**
418-
* Retrieves the reflection class associated with the given service.
419-
*
420-
* @param string|null $id
421-
* @param Definition $definition
422-
*
423-
* @return \ReflectionClass|false
424-
*/
425-
private function getReflectionClass($id, Definition $definition)
426-
{
427-
if (null !== $id && isset($this->reflectionClasses[$id])) {
428-
return $this->reflectionClasses[$id];
429-
}
430-
431-
// Cannot use reflection if the class isn't set
432-
if (!$class = $definition->getClass()) {
433-
return false;
434-
}
435-
436-
$class = $this->container->getParameterBag()->resolveValue($class);
437-
438-
try {
439-
$reflector = new \ReflectionClass($class);
440-
} catch (\ReflectionException $e) {
441-
$reflector = false;
442-
}
443-
444-
if (null !== $id) {
445-
$this->reflectionClasses[$id] = $reflector;
446-
}
447-
448-
return $reflector;
449-
}
450-
451416
private function addServiceToAmbiguousType($id, $type)
452417
{
453418
// keep an array of all services matching this type
@@ -459,6 +424,9 @@ private function addServiceToAmbiguousType($id, $type)
459424
$this->ambiguousServiceTypes[$type][] = $id;
460425
}
461426

427+
/**
428+
* @deprecated since version 3.3, to be removed in 4.0.
429+
*/
462430
private static function getResourceMetadataForMethod(\ReflectionMethod $method)
463431
{
464432
$methodArgumentsMetadata = array();

src/Symfony/Component/DependencyInjection/Config/AutowireServiceResource.php

+5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Config;
1313

14+
@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);
15+
1416
use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
1517
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
1618

19+
/**
20+
* @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.
21+
*/
1722
class AutowireServiceResource implements SelfCheckingResourceInterface, \Serializable
1823
{
1924
private $class;

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

+38-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2727
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
2828
use Symfony\Component\Config\Resource\FileResource;
29+
use Symfony\Component\Config\Resource\ReflectionClassResource;
2930
use Symfony\Component\Config\Resource\ResourceInterface;
3031
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
3132
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
@@ -103,6 +104,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
103104
*/
104105
private $envCounters = array();
105106

107+
/**
108+
* @var \ReflectionClass[]
109+
*/
110+
private $classReflectors = array();
111+
106112
/**
107113
* Sets the track resources flag.
108114
*
@@ -278,6 +284,36 @@ public function addClassResource(\ReflectionClass $class)
278284
return $this;
279285
}
280286

287+
/**
288+
* Retrieves the requested reflection class and registers it for resource tracking.
289+
*
290+
* @param string $class
291+
*
292+
* @return \ReflectionClass|false
293+
*/
294+
public function getReflectionClass($class)
295+
{
296+
if (!$class = $this->getParameterBag()->resolveValue($class)) {
297+
return false;
298+
}
299+
300+
if (isset($this->classReflectors[$class])) {
301+
return $this->classReflectors[$class];
302+
}
303+
304+
try {
305+
$classReflector = new \ReflectionClass($class);
306+
307+
if ($this->trackResources) {
308+
$this->addResource(new ReflectionClassResource($classReflector));
309+
}
310+
} catch (\ReflectionException $e) {
311+
$classReflector = false;
312+
}
313+
314+
return $this->classReflectors[$class] = $classReflector;
315+
}
316+
281317
/**
282318
* Loads the configuration for an extension.
283319
*
@@ -571,8 +607,8 @@ public function compile()
571607
if (!$definition->isPublic()) {
572608
$this->privates[$id] = true;
573609
}
574-
if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) {
575-
$this->addClassResource(new \ReflectionClass($class));
610+
if ($this->trackResources && $definition->isLazy()) {
611+
$this->getReflectionClass($definition->getClass());
576612
}
577613
}
578614

0 commit comments

Comments
 (0)