[DI] Only rebuild autowiring cache when actually needed
This commit is contained in:
parent
c5c63dc142
commit
3e976267c0
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
@ -49,6 +50,35 @@ class AutowirePass implements CompilerPassInterface
|
||||
$this->ambiguousServiceTypes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resource to help know if this service has changed.
|
||||
*
|
||||
* @param \ReflectionClass $reflectionClass
|
||||
*
|
||||
* @return AutowireServiceResource
|
||||
*/
|
||||
public static function createResourceForClass(\ReflectionClass $reflectionClass)
|
||||
{
|
||||
$metadata = array();
|
||||
|
||||
if ($constructor = $reflectionClass->getConstructor()) {
|
||||
$metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
|
||||
}
|
||||
|
||||
// todo - when #17608 is merged, could refactor to private function to remove duplication
|
||||
// of determining valid "setter" methods
|
||||
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
||||
$name = $reflectionMethod->getName();
|
||||
if ($reflectionMethod->isStatic() || 1 !== $reflectionMethod->getNumberOfParameters() || 0 !== strpos($name, 'set')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metadata[$name] = self::getResourceMetadataForMethod($reflectionMethod);
|
||||
}
|
||||
|
||||
return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wires the given definition.
|
||||
*
|
||||
@ -63,7 +93,9 @@ class AutowirePass implements CompilerPassInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$this->container->addClassResource($reflectionClass);
|
||||
if ($this->container->isTrackingResources()) {
|
||||
$this->container->addResource(static::createResourceForClass($reflectionClass));
|
||||
}
|
||||
|
||||
if (!$constructor = $reflectionClass->getConstructor()) {
|
||||
return;
|
||||
@ -278,4 +310,25 @@ class AutowirePass implements CompilerPassInterface
|
||||
}
|
||||
$this->ambiguousServiceTypes[$type][] = $id;
|
||||
}
|
||||
|
||||
static private function getResourceMetadataForMethod(\ReflectionMethod $method)
|
||||
{
|
||||
$methodArgumentsMetadata = array();
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
try {
|
||||
$class = $parameter->getClass();
|
||||
} catch (\ReflectionException $e) {
|
||||
// type-hint is against a non-existent class
|
||||
$class = false;
|
||||
}
|
||||
|
||||
$methodArgumentsMetadata[] = array(
|
||||
'class' => $class,
|
||||
'isOptional' => $parameter->isOptional(),
|
||||
'defaultValue' => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
|
||||
);
|
||||
}
|
||||
|
||||
return $methodArgumentsMetadata;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
<?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\DependencyInjection\Config;
|
||||
|
||||
use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
|
||||
|
||||
class AutowireServiceResource implements SelfCheckingResourceInterface, \Serializable
|
||||
{
|
||||
private $class;
|
||||
private $filePath;
|
||||
private $autowiringMetadata = array();
|
||||
|
||||
public function __construct($class, $path, array $autowiringMetadata)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->filePath = $path;
|
||||
$this->autowiringMetadata = $autowiringMetadata;
|
||||
}
|
||||
|
||||
public function isFresh($timestamp)
|
||||
{
|
||||
if (!file_exists($this->filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// has the file *not* been modified? Definitely fresh
|
||||
if (@filemtime($this->filePath) <= $timestamp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($this->class);
|
||||
} catch (\ReflectionException $e) {
|
||||
// the class does not exist anymore!
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return AutowirePass::createResourceForClass($reflectionClass);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return 'service.autowire.'.$this->class;
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
return serialize(array($this->class, $this->filePath, $this->autowiringMetadata));
|
||||
}
|
||||
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->class, $this->filePath, $this->autowiringMetadata) = unserialize($serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Implemented for compatibility with Symfony 2.8
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
}
|
@ -413,6 +413,39 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
||||
$definition->getArguments()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCreateResourceTests
|
||||
*/
|
||||
public function testCreateResourceForClass($className, $isEqual)
|
||||
{
|
||||
$startingResource = AutowirePass::createResourceForClass(
|
||||
new \ReflectionClass(__NAMESPACE__.'\ClassForResource')
|
||||
);
|
||||
$newResource = AutowirePass::createResourceForClass(
|
||||
new \ReflectionClass(__NAMESPACE__.'\\'.$className)
|
||||
);
|
||||
|
||||
// hack so the objects don't differ by the class name
|
||||
$startingReflObject = new \ReflectionObject($startingResource);
|
||||
$reflProp = $startingReflObject->getProperty('class');
|
||||
$reflProp->setAccessible(true);
|
||||
$reflProp->setValue($startingResource, __NAMESPACE__.'\\'.$className);
|
||||
|
||||
if ($isEqual) {
|
||||
$this->assertEquals($startingResource, $newResource);
|
||||
} else {
|
||||
$this->assertNotEquals($startingResource, $newResource);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCreateResourceTests()
|
||||
{
|
||||
return array(
|
||||
['IdenticalClassResource', true],
|
||||
['ClassChangedConstructorArgs', false],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Foo
|
||||
@ -562,3 +595,26 @@ class MultipleArgumentsOptionalScalarNotReallyOptional
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Classes used for testing createResourceForClass
|
||||
*/
|
||||
class ClassForResource
|
||||
{
|
||||
public function __construct($foo, Bar $bar = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function setBar(Bar $bar)
|
||||
{
|
||||
}
|
||||
}
|
||||
class IdenticalClassResource extends ClassForResource
|
||||
{
|
||||
}
|
||||
class ClassChangedConstructorArgs extends ClassForResource
|
||||
{
|
||||
public function __construct($foo, Bar $bar, $baz)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,120 @@
|
||||
<?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\DependencyInjection\Tests\Config;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
|
||||
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
|
||||
|
||||
class AutowireServiceResourceTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var AutowireServiceResource
|
||||
*/
|
||||
private $resource;
|
||||
private $file;
|
||||
private $class;
|
||||
private $time;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->file = realpath(sys_get_temp_dir()).'/tmp.php';
|
||||
$this->time = time();
|
||||
touch($this->file, $this->time);
|
||||
|
||||
$this->class = __NAMESPACE__.'\Foo';
|
||||
$this->resource = new AutowireServiceResource(
|
||||
$this->class,
|
||||
$this->file,
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
public function testToString()
|
||||
{
|
||||
$this->assertSame('service.autowire.'.$this->class, (string) $this->resource);
|
||||
}
|
||||
|
||||
public function testSerializeUnserialize()
|
||||
{
|
||||
$unserialized = unserialize(serialize($this->resource));
|
||||
|
||||
$this->assertEquals($this->resource, $unserialized);
|
||||
}
|
||||
|
||||
public function testIsFresh()
|
||||
{
|
||||
$this->assertTrue($this->resource->isFresh($this->time), '->isFresh() returns true if the resource has not changed in same second');
|
||||
$this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed');
|
||||
$this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated');
|
||||
}
|
||||
|
||||
public function testIsFreshForDeletedResources()
|
||||
{
|
||||
unlink($this->file);
|
||||
|
||||
$this->assertFalse($this->resource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the resource does not exist');
|
||||
}
|
||||
|
||||
public function testIsNotFreshChangedResource()
|
||||
{
|
||||
$oldResource = new AutowireServiceResource(
|
||||
$this->class,
|
||||
$this->file,
|
||||
array('will_be_different')
|
||||
);
|
||||
|
||||
// test with a stale file *and* a resource that *will* be different than the actual
|
||||
$this->assertFalse($oldResource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the constructor arguments have changed');
|
||||
}
|
||||
|
||||
public function testIsFreshSameConstructorArgs()
|
||||
{
|
||||
$oldResource = AutowirePass::createResourceForClass(
|
||||
new \ReflectionClass(__NAMESPACE__.'\Foo')
|
||||
);
|
||||
|
||||
// test with a stale file *but* the resource will not be changed
|
||||
$this->assertTrue($oldResource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the constructor arguments have changed');
|
||||
}
|
||||
|
||||
public function testNotFreshIfClassNotFound()
|
||||
{
|
||||
$resource = new AutowireServiceResource(
|
||||
'Some\Non\Existent\Class',
|
||||
$this->file,
|
||||
array()
|
||||
);
|
||||
|
||||
$this->assertFalse($resource->isFresh($this->getStaleFileTime()), '->isFresh() returns false if the class no longer exists');
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unlink($this->file);
|
||||
}
|
||||
|
||||
private function getStaleFileTime()
|
||||
{
|
||||
return $this->time - 10;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
public function __construct($foo)
|
||||
{
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user