8000 Smart caching system for autowiring to only clear container cache whe… · symfony/symfony@d0b600a · GitHub
[go: up one dir, main page]

Skip to content

Commit d0b600a

Browse files
committed
Smart caching system for autowiring to only clear container cache when it's *actually* needed
1 parent c5c63dc commit d0b600a

File tree

4 files changed

+297
-1
lines changed

4 files changed

+297
-1
lines changed

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Config\AutowireServiceRes 10000 ource;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -49,6 +50,39 @@ public function process(ContainerBuilder $container)
4950
$this->ambiguousServiceTypes = array();
5051
}
5152

53+
/**
54+
* Creates a resource to help know if this service has changed.
55+
*
56+
* @param \ReflectionClass $reflectionClass
57+
*
58+
* @return AutowireServiceResource
59+
*/
60+
public static function createResourceForClass(\ReflectionClass $reflectionClass)
61+
{
62+
$metadata = array();
63+
64+
if ($constructor = $reflectionClass->getConstructor()) {
65+
$metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
66+
}
67+
68+
// todo - when #17608 is merged, could refactor to private function to remove duplication
69+
// of determining valid "setter" methods
70+
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
71+
$name = $reflectionMethod->getName();
72+
if (isset($methodsCalled[$name]) || $reflectionMethod->isStatic() || 1 !== $reflectionMethod->getNumberOfParameters() || 0 !== strpos($name, 'set')) {
73+
continue;
74+
}
75+
76+
$metadata[$name] = self::getResourceMetadataForMethod($reflectionMethod);
77+
}
78+
79+
return new AutowireServiceResource(
80+
$reflectionClass->name,
81+
$reflectionClass->getFileName(),
82+
$metadata
83+
);
84+
}
85+
5286
/**
5387
* Wires the given definition.
5488
*
@@ -63,7 +97,7 @@ private function completeDefinition($id, Definition $definition)
6397
return;
6498
}
6599

66-
$this->container->addClassResource($reflectionClass);
100+
$this->container->addResource(static::createResourceForClass($reflectionClass));
67101

68102
if (!$constructor = $reflectionClass->getConstructor()) {
69103
return;
@@ -278,4 +312,14 @@ private function addServiceToAmbiguousType($id, $type)
278312
}
279313
$this->ambiguousServiceTypes[$type][] = $id;
280314
}
315+
316+
static private function getResourceMetadataForMethod(\ReflectionMethod $method)
317+
{
318+
$methodArgumentsMetadata = array();
319+
foreach ($method->getParameters() as $parameter) {
320+
$methodArgumentsMetadata[] = (string) $parameter;
321+
}
322+
323+
return $methodArgumentsMetadata;
324+
}
281325
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\DependencyInjection\Config;
13+
14+
use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
15+
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
16+
17+
class AutowireServiceResource implements SelfCheckingResourceInterface, \Serializable
18+
{
19+
private $class;
20+
private $filePath;
21+
private $autowiringMetadata = array();
22+
23+
public function __construct($class, $path, array $autowiringMetadata)
24+
{
25+
$this->class = $class;
26+
$this->filePath = $path;
27+
$this->autowiringMetadata = $autowiringMetadata;
28+
}
29+
30+
public function isFresh($timestamp)
31+
{
32+
if (!file_exists($this->filePath)) {
33+
return false;
34+
}
35+
36+
// has the file *not* been modified? Definitely fresh
37+
if (@filemtime($this->filePath) <= $timestamp) {
38+
return true;
39+
}
40+
41+
try {
42+
$reflectionClass = new \ReflectionClass($this->class);
43+
} catch (\ReflectionException $e) {
44+
// the class does not exist anymore!
45+
46+
return false;
47+
}
48+
49+
$newResource = AutowirePass::createResourceForClass($reflectionClass);
50+
51+
return $newResource == $this;
52+
}
53+
54+
public function __toString()
55+
{
56+
return 'service.autowire.'.$this->class;
57+
}
58+
59+
public function serialize()
60+
{
61+
return serialize(array(
62+
$this->class,
63+
$this->filePath,
64+
$this->autowiringMetadata,
65+
));
66+
}
67+
68+
public function unserialize($serialized)
69+
{
70+
list(
71+
$this->class,
72+
$this->filePath,
73+
$this->autowiringMetadata
74+
) = unserialize($serialized);
75+
}
76+
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,39 @@ public function testOptionalScalarArgsNotPassedIfLast()
413413
$definition->getArguments()
414414
);
415415
}
416+
417+
/**
418+
* @dataProvider getCreateResourceTests
419+
*/
420+
public function testCreateResourceForClass($className, $isEqual)
421+
{
422+
$startingResource = AutowirePass::createResourceForClass(
423+
new \ReflectionClass(__NAMESPACE__.'\ClassForResource')
424+
);
425+
$newResource = AutowirePass::createResourceForClass(
426+
new \ReflectionClass(__NAMESPACE__.'\\'.$className)
427+
);
428+
429+
// hack so the objects don't differ by the class name
430+
$startingReflObject = new \ReflectionObject($startingResource);
431+
$reflProp = $startingReflObject->getProperty('class');
432+
$reflProp->setAccessible(true);
433+
$reflProp->setValue($startingResource, __NAMESPACE__.'\\'.$className);
434+
435+
if ($isEqual) {
436+
$this->assertEquals($startingResource, $newResource);
437+
} else {
438+
$this->assertNotEquals($startingResource, $newResource);
439+
}
440+
}
441+
442+
public function getCreateResourceTests()
443+
{
444+
return array(
445+
['IdenticalClassResource', true],
446+
['ClassChangedConstructorArgs', false],
447+
);
448+
}
416449
}
417450

418451
class Foo
@@ -562,3 +595,26 @@ public function __construct(A $a, $foo = 'default_val', Lille $lille)
562595
{
563596
}
564597
}
598+
599+
/*
600+
* Classes used for testing createResourceForClass
601+
*/
602+
class ClassForResource
603+
{
604+
public function __construct($foo, Bar $bar = null)
605+
{
606+
}
607+
608+
public function setBar(Bar $bar)
609+
{
610+
}
611+
}
612+
class IdenticalClassResource extends ClassForResource
613+
{
614+
}
615+
class ClassChangedConstructorArgs extends ClassForResource
616+
{
617+
public function __construct($foo, Bar $bar, $baz)
618+
{
619+
}
620+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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\DependencyInjection\Tests\Config;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
15+
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
16+
17+
class AutowireServiceResourceTest extends \PHPUnit_Framework_TestCase
18+
{
19+
/**
20+
* @var AutowireServiceResource
21+
*/
22+
private $resource;
23+
private $file;
24+
private $class;
25+
private $time;
26+
27+
protected function setUp()
28+
{
29+
$this->file = realpath(sys_get_temp_dir()).'/tmp.php';
30+
$this->time = time();
31+
touch($this->file, $this->time);
32+
33+
$this->class = __NAMESPACE__.'\Foo';
34+
$this->resource = new AutowireServiceResource(
35+
$this->class,
36+
$this->file,
37+
array()
38+
);
39+
}
40+
41+
public function testToString()
42+
{
43+
$this->assertSame('service.autowire.'.$this->class, (string) $this->resource);
44+
}
45+
46+
public function testSerializeUnserialize()
47+
{
48+
$unserialized = unserialize(serialize($this->resource));
49+
50+
$this->assertEquals($this->resource, $unserialized);
51+
}
52+
53+
public function testIsFresh()
54+
{
55+
$this->assertTrue($this->resource->isFresh($this->time), '->isFresh() returns true if the resource has not changed in same second');
56+
$this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed');
57+
$this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated');
58+
}
59+
60+
public function testIsFreshForDeletedResources()
61+
{
62+
unlink($this->file);
63+
64+
$this->assertFalse($this->resource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the resource does not exist');
65+
}
66+
67+
public function testIsNotFreshChangedResource()
68+
{
69+
$oldResource = new AutowireServiceResource(
70+
$this->class,
71+
$this->file,
72+
array('will_be_different')
73+
);
74+
75+
// test with a stale file *and* a resource that *will* be different than the actual
76+
$this->assertFalse($oldResource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the constructor arguments have changed');
77+
}
78+
79+
public function testIsFreshSameConstructorArgs()
80+
{
81+
$oldResource = AutowirePass::createResourceForClass(
82+
new \ReflectionClass(__NAMESPACE__.'\Foo')
83+
);
84+
85+
// test with a stale file *but* the resource will not be changed
86+
$this->assertTrue($oldResource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the constructor arguments have changed');
87+
}
88+
89+
public function testNotFreshIfClassNotFound()
90+
{
91+
$resource = new AutowireServiceResource(
92+
'Some\Non\Existent\Class',
93+
$this->file,
94+
array()
95+
);
96+
97+
$this->assertFalse($resource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the class no longer exists');
98+
}
99+
100+
protected function tearDown()
101+
{
102+
if (!file_exists($this->file)) {
103+
return;
104+
}
105+
106+
unlink($this->file);
107+
}
108+
109+
private function getStaleFileTime()
110+
{
111+
return $this->time - 10;
112+
}
113+
}
114+
115+
class Foo
116+
{
117+
public function __construct($foo)
118+
{
119+
}
120+
}

0 commit comments

Comments
 (0)
0