[ClassLoader] ordered ClassCollectionLoader writing to avoid redeclaration at runtime
This commit is contained in:
parent
a1b73887f7
commit
26a1e0be5e
@ -19,6 +19,7 @@ namespace Symfony\Component\ClassLoader;
|
||||
class ClassCollectionLoader
|
||||
{
|
||||
static private $loaded;
|
||||
static private $baseClassesCountMap;
|
||||
|
||||
/**
|
||||
* Loads a list of classes and caches them in one big file.
|
||||
@ -61,6 +62,9 @@ class ClassCollectionLoader
|
||||
$time = filemtime($cache);
|
||||
$meta = unserialize(file_get_contents($metadata));
|
||||
|
||||
sort($meta[1]);
|
||||
sort($classes);
|
||||
|
||||
if ($meta[1] != $classes) {
|
||||
$reload = true;
|
||||
} else {
|
||||
@ -81,6 +85,9 @@ class ClassCollectionLoader
|
||||
return;
|
||||
}
|
||||
|
||||
// order classes to avoid redeclaration at runtime (class declared before its parent)
|
||||
self::orderClasses($classes);
|
||||
|
||||
$files = array();
|
||||
$content = '';
|
||||
foreach ($classes as $class) {
|
||||
@ -220,4 +227,49 @@ class ClassCollectionLoader
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders a set of classes according to their number of parents.
|
||||
*
|
||||
* @param array $classes
|
||||
*
|
||||
* @throws \InvalidArgumentException When a class can't be loaded
|
||||
*/
|
||||
static private function orderClasses(array &$classes)
|
||||
{
|
||||
foreach ($classes as $class) {
|
||||
if (isset(self::$baseClassesCountMap[$class])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
|
||||
}
|
||||
|
||||
// The counter is cached to avoid reflection if the same class is asked again later
|
||||
self::$baseClassesCountMap[$class] = self::countParentClasses($reflectionClass) + count($reflectionClass->getInterfaces());
|
||||
}
|
||||
|
||||
asort(self::$baseClassesCountMap);
|
||||
|
||||
$classes = array_intersect(array_keys(self::$baseClassesCountMap), $classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of parent classes in userland.
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @param integer $count If exists, the current counter
|
||||
* @return integer
|
||||
*/
|
||||
static private function countParentClasses(\ReflectionClass $class, $count = 0)
|
||||
{
|
||||
if (($parent = $class->getParentClass()) && $parent->isUserDefined()) {
|
||||
$count = self::countParentClasses($parent, ++$count);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,74 @@
|
||||
namespace Symfony\Component\ClassLoader\Tests;
|
||||
|
||||
use Symfony\Component\ClassLoader\ClassCollectionLoader;
|
||||
use Symfony\Component\ClassLoader\UniversalClassLoader;
|
||||
|
||||
class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getDifferentOrders
|
||||
*/
|
||||
public function testClassReordering(array $classes)
|
||||
{
|
||||
$loader = new UniversalClassLoader();
|
||||
$loader->registerNamespace('ClassesWithParents', __DIR__.'/Fixtures');
|
||||
$loader->register();
|
||||
|
||||
$expected = <<<EOF
|
||||
<?php
|
||||
|
||||
namespace ClassesWithParents
|
||||
{
|
||||
|
||||
interface CInterface {}
|
||||
}
|
||||
|
||||
|
||||
namespace ClassesWithParents
|
||||
{
|
||||
|
||||
class B implements CInterface {}
|
||||
}
|
||||
|
||||
|
||||
namespace ClassesWithParents
|
||||
{
|
||||
|
||||
class A extends B {}
|
||||
}
|
||||
|
||||
EOF;
|
||||
|
||||
$dir = sys_get_temp_dir();
|
||||
$fileName = uniqid('symfony_');
|
||||
|
||||
ClassCollectionLoader::load($classes, $dir, $fileName, true);
|
||||
$cachedContent = @file_get_contents($dir.'/'.$fileName.'.php');
|
||||
|
||||
$this->assertEquals($expected, $cachedContent);
|
||||
}
|
||||
|
||||
public function getDifferentOrders()
|
||||
{
|
||||
return array(
|
||||
array(array(
|
||||
'ClassesWithParents\\A',
|
||||
'ClassesWithParents\\CInterface',
|
||||
'ClassesWithParents\\B',
|
||||
)),
|
||||
array(array(
|
||||
'ClassesWithParents\\B',
|
||||
'ClassesWithParents\\A',
|
||||
'ClassesWithParents\\CInterface',
|
||||
)),
|
||||
array(array(
|
||||
'ClassesWithParents\\CInterface',
|
||||
'ClassesWithParents\\B',
|
||||
'ClassesWithParents\\A',
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
public function testFixNamespaceDeclarations()
|
||||
{
|
||||
$source = <<<EOF
|
||||
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace ClassesWithParents;
|
||||
|
||||
class A extends B {}
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace ClassesWithParents;
|
||||
|
||||
class B implements CInterface {}
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace ClassesWithParents;
|
||||
|
||||
interface CInterface {}
|
Reference in New Issue
Block a user