diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index b9cac9f71845a..37875b53b570a 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -16,7 +16,6 @@ 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 @@ protected function assertValidMappingConfiguration(array $mappingConfig, $object */ 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; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 88ac0c0655e9d..0203f114bc699 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -23,7 +23,6 @@ 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 @@ private function getValidatorMappingFiles(ContainerBuilder $container, array &$f { 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) { diff --git a/src/Symfony/Component/Config/Resource/ComposerResource.php b/src/Symfony/Component/Config/Resource/ComposerResource.php new file mode 100644 index 0000000000000..56224d16c01d8 --- /dev/null +++ b/src/Symfony/Component/Config/Resource/ComposerResource.php @@ -0,0 +1,94 @@ + + * + * 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 + */ +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'); + } + } + } + } +} diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index 23aef37c0c182..a8e1f9611b99a 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -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 @@ private function loadFiles(\ReflectionClass $class) 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); diff --git a/src/Symfony/Component/Config/Tests/Resource/ComposerResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ComposerResourceTest.php new file mode 100644 index 0000000000000..e617f178023c7 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Resource/ComposerResourceTest.php @@ -0,0 +1,46 @@ + + * + * 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); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php index eaeead071796f..3ba4a8caa02f2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php @@ -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 @@ private function updateDefinition(ContainerBuilder $container, $id, Definition $ 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; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index d68688c6029c1..5aa32b975ee8b 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -26,6 +26,7 @@ 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 @@ public function addObjectResource($object) } $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 @@ public function getReflectionClass($class, $koWithThrowingAutoloader = false) 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 @@ public function fileExists($path, $trackContents = true) { $exists = file_exists($path); - if (!$this->trackResources) { + if (!$this->trackResources || $this->inVendors($path)) { return $exists; } @@ -370,12 +380,10 @@ public function fileExists($path, $trackContents = true) 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 @@ private function getExpressionLanguage() 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; + } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php b/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php index ffb8853011134..3ab4c5dc82e7e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/DirectoryLoader.php @@ -27,7 +27,7 @@ public function load($file, $type = null) { $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]) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php index 224524e50e8b6..170e726396a5e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php @@ -29,7 +29,7 @@ public function load($resource, $type = null) { $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); diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 5275ab5010f94..36ef0d4752bc9 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -34,7 +34,7 @@ public function load($resource, $type = null) $path = $this->locator->locate($resource); $this->setCurrentDir(dirname($path)); - $this->container->addResource(new FileResource($path)); + $this->container->fileExists($path); include $path; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 4e1ee6ac12594..253cddd17f628 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -42,7 +42,7 @@ public function load($resource, $type = null) $xml = $this->parseFileToDOM($path); - $this->container->addResource(new FileResource($path)); + $this->container->fileExists($path); // anonymous services $this->processAnonymousServices($xml, $path); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index ba29ca60b50df..861fba2be519b 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -72,7 +72,7 @@ public function load($resource, $type = null) $content = $this->loadFile($path); - $this->container->addResource(new FileResource($path)); + $this->container->fileExists($path); // empty file if (null === $content) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 41a5702549a35..094d931a88901 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -14,6 +14,7 @@ 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 @@ public function testAddObjectResource() $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 @@ public function testAddClassResource() $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 @@ public function testGetReflectionClass() $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 @@ public function testResources() 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 @@ public function testFileExists() } } - $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()