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

Skip to content

Commit ea23c0e

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

File tree

3 files changed

+245
-1
lines changed

3 files changed

+245
-1
lines changed

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

Lines changed: 28 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\AutowireServiceResource;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -23,6 +24,9 @@
2324
*/
2425
class AutowirePass implements CompilerPassInterface
2526
{
27+
/**
28+
* @var ContainerBuilder
29+
*/
2630
private $container;
2731
private $reflectionClasses = array();
2832
private $definedTypes = array();
@@ -49,6 +53,29 @@ public function process(ContainerBuilder $container)
4953
$this->ambiguousServiceTypes = array();
5054
}
5155

56+
/**
57+
* Creates a resource to help know if this service has changed.
58+
*
59+
* @param \ReflectionClass $reflectionClass
60+
*
61+
* @return AutowireServiceResource
62+
*/
63+
public static function createResourceForClass(\ReflectionClass $reflectionClass)
64+
{
65+
$constructor = $reflectionClass->getConstructor();
66+
$constructorParams = $constructor ? $constructor->getParameters() : array();
67+
$constructorArgumentsForResource = array();
68+
foreach ($constructorParams as $parameter) {
69+
$constructorArgumentsForResource[] = $parameter->getClass();
70+
}
71+
72+
return new AutowireServiceResource(
73+
$reflectionClass->name,
74+
$reflectionClass->getFileName(),
75+
$constructorArgumentsForResource
76+
);
77+
}
78+
5279
/**
5380
* Wires the given definition.
5481
*
@@ -63,7 +90,7 @@ private function completeDefinition($id, Definition $definition)
6390
return;
6491
}
6592

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

6895
if (!$constructor = $reflectionClass->getConstructor()) {
6996
return;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 $constructorArguments;
22+
private $createResourceCallback;
23+
24+
public function __construct($class, $path)
25+
{
26+
$this->class = $class;
27+
$this->filePath = $path;
28+
}
29+
30+
/**
31+
* An array of arguments: 0 => type-hinted class (or null of there is no type-hint).
32+
*
33+
* @param array $constructorArguments
34+
*/
35+
public function setConstructorArguments(array $constructorArguments)
36+
{
37+
$this->constructorArguments = $constructorArguments;
38+
}
39+
40+
public function isFresh($timestamp)
41+
{
42+
if (!file_exists($this->filePath)) {
43+
return false;
44+
}
45+
46+
// has the file *not* been modified? Definitely fresh
47+
if (@filemtime($this->filePath) <= $timestamp) {
48+
return true;
49+
}
50+
51+
if ($this->createResourceCallback) {
52+
// this override is used for testing
53+
$newResource = call_user_func($this->createResourceCallback, new \ReflectionClass($this->class));
54+
} else {
55+
$newResource = AutowirePass::createResourceForClass(new \ReflectionClass($this->class));
56+
}
57+
58+
59+
return $newResource == $this;
60+
}
61+
62+
/**
63+
* @internal
64+
*/
65+
public function setCreateResourceCallback($createResourceCallback)
66+
{
67+
$this->createResourceCallback = $createResourceCallback;
68+
}
69+
70+
public function __toString()
71+
{
72+
return 'service.autowire.'.$this->class;
73+
}
74+
75+
public function serialize()
76+
{
77+
return serialize(array(
78+
$this->class,
79+
$this->filePath,
80+
$this->constructorArguments,
81+
));
82+
}
83+
84+
public function unserialize($serialized)
85+
{
86+
list(
87+
$this->class,
88+
$this->filePath,
89+
$this->constructorArguments
90+
) = unserialize($serialized);
91+
}
92+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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\Config\AutowireServiceResource;
15+
16+
class AutowireServiceResourceTest extends \PHPUnit_Framework_TestCase
17+
{
18+
/**
19+
* @var AutowireServiceResource
20+
*/
21+
private $resource;
22+
private $file;
23+
private $class;
24+
private $time;
25+
private $constructorArgs;
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 = 'Symfony\Component\DependencyInjection\Tests\Config\FooReplicator';
34+
$this->resource = new AutowireServiceResource(
35+
$this->class,
36+
$this->file
37+
);
38+
$this->constructorArgs = array(
39+
null, // no type-hint on this argument
40+
'AppBundle\Entity\Foo',
41+
);
42+
$this->resource->setConstructorArguments(array(
43+
null, // no type-hint on this argument
44+
'AppBundle\Entity\Foo',
45+
));
46+
}
47+
48+
public function testToString()
49+
{
50+
$this->assertSame('service.autowire.'.$this->class, (string) $this->resource);
51+
}
52+
53+
public function testSerializeUnserialize()
54+
{
55+
$unserialized = unserialize(serialize($this->resource));
56+
57+
$this->assertEquals($this->resource, $unserialized);
58+
}
59+
60+
public function testIsFresh()
61+
{
62+
$this->assertTrue($this->resource->isFresh($this->time), '->isFresh() returns true if the resource has not changed in same second');
63+
$this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed');
64+
$this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated');
65+
}
66+
67+
public function testIsFreshForDeletedResources()
68+
{
69+
unlink($this->file);
70+
71+
$this->assertFalse($this->resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist');
72+
}
73+
74+
public function testIsFreshChangedConstructorArgs()
75+
{
76+
$newResource = new AutowireServiceResource(
77+
$this->class,
78+
$this->file
79+
);
80+
// a new 3rd argument was added!
81+
$args = $this->constructorArgs;
82+
$args[] = 'AppBundle\Entity\Bar';
83+
$newResource->setConstructorArguments($args);
84+
85+
// "fake" the return value
86+
$callback = function(\ReflectionClass $reflectionClass) use ($newResource) {
87+
return $newResource;
88+
};
89+
$this->resource->setCreateResourceCallback($callback);
90+
91+
$this->assertFalse($this->resource->isFresh($this->time - 10), '->isFresh() returns false if the constructor arguments have changed');
92+
}
93+
94+
public function testIsFreshSameConstructorArgs()
95+
{
96+
$newResource = new AutowireServiceResource(
97+
$this->class,
98+
$this->file
99+
);
100+
101+
$newResource->setConstructorArguments($this->constructorArgs);
102+
103+
// "fake" the return value
104+
$callback = function(\ReflectionClass $reflectionClass) use ($newResource) {
105+
return $newResource;
106+
};
107+
108+
$this->resource->setCreateResourceCallback($callback);
109+
$this->assertFalse($this->resource->isFresh($this->time - 10), '->isFresh() returns false if the constructor arguments have changed');
110+
}
111+
112+
protected function tearDown()
113+
{
114+
if (!file_exists($this->file)) {
115+
return;
116+
}
117+
118+
unlink($this->file);
119+
}
120+
}
121+
122+
// exists just so that some reflection in the tests doesn't fail
123+
class FooReplicator
124+
{
125+
}

0 commit comments

Comments
 (0)
0