[Config][DI] Add ComposerResource to track runtime + vendors

This commit is contained in:
Nicolas Grekas 2017-02-02 09:31:13 +01:00
parent a46e691ae1
commit a960c761d1
13 changed files with 219 additions and 47 deletions

View File

@ -16,7 +16,6 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\Resource\FileResource;
/**
* This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
@ -268,30 +267,28 @@ abstract class AbstractDoctrineExtension extends Extension
*/
protected function detectMetadataDriver($dir, ContainerBuilder $container)
{
// add the closest existing directory as a resource
$configPath = $this->getMappingResourceConfigDirectory();
$resource = $dir.'/'.$configPath;
while (!is_dir($resource)) {
$resource = dirname($resource);
}
$container->addResource(new FileResource($resource));
$extension = $this->getMappingResourceExtension();
if (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.xml')) && count($files)) {
return 'xml';
} elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.yml')) && count($files)) {
return 'yml';
} elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.php')) && count($files)) {
return 'php';
}
// add the directory itself as a resource
$container->addResource(new FileResource($dir));
if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml')) {
$driver = 'xml';
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml')) {
$driver = 'yml';
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php')) {
$driver = 'php';
} else {
// add the closest existing directory as a resource
$resource = $dir.'/'.$configPath;
while (!is_dir($resource)) {
$resource = dirname($resource);
}
$container->fileExists($resource, false);
if (is_dir($dir.'/'.$this->getMappingObjectDefaultName())) {
return 'annotation';
return $container->fileExists($dir.'/'.$this->getMappingObjectDefaultName(), false) ? 'annotation' : null;
}
$container->fileExists($dir.'/'.$configPath, false);
return $driver;
}
/**

View File

@ -23,7 +23,6 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
@ -994,8 +993,7 @@ class FrameworkExtension extends Extension
{
if (interface_exists('Symfony\Component\Form\FormInterface')) {
$reflClass = new \ReflectionClass('Symfony\Component\Form\FormInterface');
$files['xml'][] = $file = dirname($reflClass->getFileName()).'/Resources/config/validation.xml';
$container->addResource(new FileResource($file));
$files['xml'][] = dirname($reflClass->getFileName()).'/Resources/config/validation.xml';
}
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {

View File

@ -0,0 +1,94 @@
<?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\Config\Resource;
/**
* ComposerResource tracks the PHP version and Composer dependencies.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ComposerResource implements SelfCheckingResourceInterface, \Serializable
{
private $versions;
private $vendors;
private static $runtimeVersion;
private static $runtimeVendors;
public function __construct()
{
self::refresh();
$this->versions = self::$runtimeVersion;
$this->vendors = self::$runtimeVendors;
}
public function getVendors()
{
return array_keys($this->vendors);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return __CLASS__;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
self::refresh();
if (self::$runtimeVersion !== $this->versions) {
return false;
}
return self::$runtimeVendors === $this->vendors;
}
public function serialize()
{
return serialize(array($this->versions, $this->vendors));
}
public function unserialize($serialized)
{
list($this->versions, $this->vendors) = unserialize($serialized);
}
private static function refresh()
{
if (null !== self::$runtimeVersion) {
return;
}
self::$runtimeVersion = array();
self::$runtimeVendors = array();
foreach (get_loaded_extensions() as $ext) {
self::$runtimeVersion[$ext] = phpversion($ext);
}
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = dirname(dirname($r->getFileName()));
if (file_exists($v.'/composer/installed.json')) {
self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
}
}
}
}
}

View File

@ -19,12 +19,14 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
private $files = array();
private $className;
private $classReflector;
private $excludedVendors = array();
private $hash;
public function __construct(\ReflectionClass $classReflector)
public function __construct(\ReflectionClass $classReflector, $excludedVendors = array())
{
$this->className = $classReflector->name;
$this->classReflector = $classReflector;
$this->excludedVendors = $excludedVendors;
}
public function isFresh($timestamp)
@ -75,7 +77,15 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
do {
$file = $class->getFileName();
if (false !== $file && file_exists($file)) {
$this->files[$file] = null;
foreach ($this->excludedVendors as $vendor) {
if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
$file = false;
break;
}
}
if ($file) {
$this->files[$file] = null;
}
}
foreach ($class->getTraits() as $v) {
$this->loadFiles($v);

View File

@ -0,0 +1,46 @@
<?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\Config\Tests\Resource;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Config\Resource\ComposerResource;
class ComposerResourceTest extends \PHPUnit_Framework_TestCase
{
public function testGetVendor()
{
$res = new ComposerResource();
$r = new \ReflectionClass(ClassLoader::class);
$found = false;
foreach ($res->getVendors() as $vendor) {
if ($vendor && 0 === strpos($r->getFileName(), $vendor)) {
$found = true;
break;
}
}
$this->assertTrue($found);
}
public function testSerializeUnserialize()
{
$res = new ComposerResource();
$ser = unserialize(serialize($res));
$this->assertTrue($res->isFresh(0));
$this->assertTrue($ser->isFresh(0));
$this->assertEquals($res, $ser);
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
@ -67,7 +66,7 @@ class FactoryReturnTypePass implements CompilerPassInterface
try {
$m = new \ReflectionFunction($factory);
if (false !== $m->getFileName() && file_exists($m->getFileName())) {
$container->addResource(new FileResource($m->getFileName()));
$container->fileExists($m->getFileName());
}
} catch (\ReflectionException $e) {
return;

View File

@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Config\Resource\ComposerResource;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Config\Resource\FileResource;
@ -108,6 +109,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private $envCounters = array();
/**
* @var string[] the list of vendor directories
*/
private $vendors;
/**
* Sets the track resources flag.
*
@ -269,13 +275,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
$file = $interface->getFileName();
if (false !== $file && file_exists($file)) {
$this->addResource(new FileResource($file));
$this->fileExists($file);
}
}
do {
$file = $class->getFileName();
if (false !== $file && file_exists($file)) {
$this->addResource(new FileResource($file));
$this->fileExists($file);
}
foreach ($class->getTraitNames() as $name) {
$this->addObjectResource($name);
@ -337,7 +343,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
if (!$classReflector) {
$this->addResource($resource ?: new ClassExistenceResource($class, ClassExistenceResource::EXISTS_KO));
} elseif (!$classReflector->isInternal()) {
$this->addResource(new ReflectionClassResource($classReflector));
$path = $classReflector->getFileName();
if (!$this->inVendors($path)) {
$this->addResource(new ReflectionClassResource($classReflector, $this->vendors));
}
}
$this->classReflectors[$class] = $classReflector;
}
@ -360,7 +370,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
{
$exists = file_exists($path);
if (!$this->trackResources) {
if (!$this->trackResources || $this->inVendors($path)) {
return $exists;
}
@ -370,12 +380,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $exists;
}
if ($trackContents) {
if (is_file($path)) {
$this->addResource(new FileResource($path));
} else {
$this->addResource(new DirectoryResource($path, is_string($trackContents) ? $trackContents : null));
}
if ($trackContents && is_dir($path)) {
$this->addResource(new DirectoryResource($path, is_string($trackContents) ? $trackContents : null));
} elseif ($trackContents || is_dir($path)) {
$this->addResource(new FileResource($path));
}
return $exists;
@ -1488,4 +1496,22 @@ EOF;
return $this->expressionLanguage;
}
private function inVendors($path)
{
if (null === $this->vendors) {
$resource = new ComposerResource();
$this->vendors = $resource->getVendors();
$this->addResource($resource);
}
$path = realpath($path) ?: $path;
foreach ($this->vendors as $vendor) {
if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
return true;
}
}
return false;
}
}

View File

@ -27,7 +27,7 @@ class DirectoryLoader extends FileLoader
{
$file = rtrim($file, '/');
$path = $this->locator->locate($file);
$this->container->addResource(new DirectoryResource($path));
$this->container->fileExists($path, false);
foreach (scandir($path) as $dir) {
if ('.' !== $dir[0]) {

View File

@ -29,7 +29,7 @@ class IniFileLoader extends FileLoader
{
$path = $this->locator->locate($resource);
$this->container->addResource(new FileResource($path));
$this->container->fileExists($path);
// first pass to catch parsing errors
$result = parse_ini_file($path, true);

View File

@ -34,7 +34,7 @@ class PhpFileLoader extends FileLoader
$path = $this->locator->locate($resource);
$this->setCurrentDir(dirname($path));
$this->container->addResource(new FileResource($path));
$this->container->fileExists($path);
include $path;
}

View File

@ -42,7 +42,7 @@ class XmlFileLoader extends FileLoader
$xml = $this->parseFileToDOM($path);
$this->container->addResource(new FileResource($path));
$this->container->fileExists($path);
// anonymous services
$this->processAnonymousServices($xml, $path);

View File

@ -72,7 +72,7 @@ class YamlFileLoader extends FileLoader
$content = $this->loadFile($path);
$this->container->addResource(new FileResource($path));
$this->container->fileExists($path);
// empty file
if (null === $content) {

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests;
require_once __DIR__.'/Fixtures/includes/classes.php';
require_once __DIR__.'/Fixtures/includes/ProjectExtension.php';
use Symfony\Component\Config\Resource\ComposerResource;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\DependencyInjection\Alias;
@ -614,7 +615,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$resources = $container->getResources();
$this->assertCount(1, $resources, '1 resource was registered');
$this->assertCount(2, $resources, '2 resources were registered');
/* @var $resource \Symfony\Component\Config\Resource\FileResource */
$resource = end($resources);
@ -640,7 +641,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$resources = $container->getResources();
$this->assertCount(1, $resources, '1 resource was registered');
$this->assertCount(2, $resources, '2 resources were registered');
/* @var $resource \Symfony\Component\Config\Resource\FileResource */
$resource = end($resources);
@ -669,9 +670,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$resources = $container->getResources();
$this->assertCount(2, $resources, '2 resources were registered');
$this->assertCount(3, $resources, '3 resources were registered');
$this->assertSame('reflection.BarClass', (string) $resources[0]);
$this->assertSame('reflection.BarClass', (string) $resources[1]);
$this->assertSame('BarMissingClass', (string) end($resources));
}
@ -715,6 +716,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
public function testFileExists()
{
$container = new ContainerBuilder();
$A = new ComposerResource();
$a = new FileResource(__DIR__.'/Fixtures/xml/services1.xml');
$b = new FileResource(__DIR__.'/Fixtures/xml/services2.xml');
$c = new DirectoryResource($dir = dirname($b));
@ -728,7 +730,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
}
}
$this->assertEquals(array($a, $b, $c), $resources, '->getResources() returns an array of resources read for the current configuration');
$this->assertEquals(array($A, $a, $b, $c), $resources, '->getResources() returns an array of resources read for the current configuration');
}
public function testExtension()