merged branch vicb/classcollectionloader (PR #6258)
This PR was submitted for the master branch but it was merged into the 2.2 branch instead (closes #6258). Commits ------- d4acd6d Classcollectionloader: fix traits + enhancements Discussion ---------- Classcollectionloader: fix traits + enhancements Commits: - some tweaks - generates smaller cache files (20% decrease for the SE) - fix traits dependency - thanks php for consistency https://bugs.php.net/bug.php?id=61554 --------------------------------------------------------------------------- by vicb at 2012-12-11T07:56:07Z @stloyd @fabpot thanks for the useful reviews, everything should be fixed now. --------------------------------------------------------------------------- by vicb at 2012-12-11T10:03:48Z Should be ready. --------------------------------------------------------------------------- by vicb at 2013-01-15T21:05:42Z @fabpot I initially marked this PR for 2.3 but it contains a fix for traits, should you merge it ? --------------------------------------------------------------------------- by vicb at 2013-01-23T20:09:32Z @fabpot could this be in 2.2 ? --------------------------------------------------------------------------- by vicb at 2013-02-01T14:07:08Z @fabpot fixed
This commit is contained in:
commit
c85ec8b14a
@ -144,45 +144,78 @@ class ClassCollectionLoader
|
||||
return $source;
|
||||
}
|
||||
|
||||
$rawChunk = '';
|
||||
$output = '';
|
||||
$inNamespace = false;
|
||||
$tokens = token_get_all($source);
|
||||
|
||||
for ($i = 0, $max = count($tokens); $i < $max; $i++) {
|
||||
$token = $tokens[$i];
|
||||
for (reset($tokens); false !== $token = current($tokens); next($tokens)) {
|
||||
if (is_string($token)) {
|
||||
$output .= $token;
|
||||
$rawChunk .= $token;
|
||||
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
|
||||
// strip comments
|
||||
continue;
|
||||
} elseif (T_NAMESPACE === $token[0]) {
|
||||
if ($inNamespace) {
|
||||
$output .= "}\n";
|
||||
$rawChunk .= "}\n";
|
||||
}
|
||||
$output .= $token[1];
|
||||
$rawChunk .= $token[1];
|
||||
|
||||
// namespace name and whitespaces
|
||||
while (($t = $tokens[++$i]) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) {
|
||||
$output .= $t[1];
|
||||
while (($t = next($tokens)) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) {
|
||||
$rawChunk .= $t[1];
|
||||
}
|
||||
if (is_string($t) && '{' === $t) {
|
||||
if ('{' === $t) {
|
||||
$inNamespace = false;
|
||||
--$i;
|
||||
prev($tokens);
|
||||
} else {
|
||||
$output = rtrim($output);
|
||||
$output .= "\n{";
|
||||
$rawChunk = rtrim($rawChunk) . "\n{";
|
||||
$inNamespace = true;
|
||||
}
|
||||
} elseif (T_START_HEREDOC === $token[0]) {
|
||||
$output .= self::compressCode($rawChunk) . $token[1];
|
||||
do {
|
||||
$token = next($tokens);
|
||||
$output .= $token[1];
|
||||
} while ($token[0] !== T_END_HEREDOC);
|
||||
$rawChunk = '';
|
||||
} elseif (T_CONSTANT_ENCAPSED_STRING === $token[0]) {
|
||||
$output .= self::compressCode($rawChunk) . $token[1];
|
||||
$rawChunk = '';
|
||||
} else {
|
||||
$output .= $token[1];
|
||||
$rawChunk .= $token[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ($inNamespace) {
|
||||
$output .= "}\n";
|
||||
$rawChunk .= "}\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
return $output . self::compressCode($rawChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only useful for testing.
|
||||
*/
|
||||
public static function enableTokenizer($bool)
|
||||
{
|
||||
self::$useTokenizer = (Boolean) $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips leading & trailing ws, multiple EOL, multiple ws.
|
||||
*
|
||||
* @param string $code Original PHP code
|
||||
*
|
||||
* @return string compressed code
|
||||
*/
|
||||
private static function compressCode($code)
|
||||
{
|
||||
return preg_replace(
|
||||
array('/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'),
|
||||
array('', '', "\n", ' '),
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,17 +280,19 @@ class ClassCollectionLoader
|
||||
array_unshift($classes, $parent);
|
||||
}
|
||||
|
||||
$traits = array();
|
||||
|
||||
if (function_exists('get_declared_traits')) {
|
||||
foreach ($classes as $c) {
|
||||
foreach (self::getTraits($c) as $trait) {
|
||||
self::$seen[$trait->getName()] = true;
|
||||
|
||||
array_unshift($classes, $trait);
|
||||
foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) {
|
||||
if ($trait !== $c) {
|
||||
$traits[] = $trait;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge(self::getInterfaces($class), $classes);
|
||||
return array_merge(self::getInterfaces($class), $traits, $classes);
|
||||
}
|
||||
|
||||
private static function getInterfaces(\ReflectionClass $class)
|
||||
@ -277,26 +312,54 @@ class ClassCollectionLoader
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private static function getTraits(\ReflectionClass $class)
|
||||
private static function computeTraitDeps(\ReflectionClass $class)
|
||||
{
|
||||
$traits = $class->getTraits();
|
||||
$classes = array();
|
||||
$deps = array($class->getName() => $traits);
|
||||
while ($trait = array_pop($traits)) {
|
||||
if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) {
|
||||
$classes[] = $trait;
|
||||
|
||||
$traits = array_merge($traits, $trait->getTraits());
|
||||
self::$seen[$trait->getName()] = true;
|
||||
$traitDeps = $trait->getTraits();
|
||||
$deps[$trait->getName()] = $traitDeps;
|
||||
$traits = array_merge($traits, $traitDeps);
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
return $deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only useful for testing.
|
||||
* Dependencies resolution.
|
||||
*
|
||||
* This function does not check for circular dependencies as it should never
|
||||
* occur with PHP traits.
|
||||
*
|
||||
* @param array $tree The dependency tree
|
||||
* @param \ReflectionClass $node The node
|
||||
* @param \ArrayObject $resolved An array of already resolved dependencies
|
||||
* @param \ArrayObject $unresolved An array of dependencies to be resolved
|
||||
*
|
||||
* @return \ArrayObject The dependencies for the given node
|
||||
*
|
||||
* @throws \RuntimeException if a circular dependency is detected
|
||||
*/
|
||||
public static function enableTokenizer($bool)
|
||||
private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null)
|
||||
{
|
||||
self::$useTokenizer = (Boolean) $bool;
|
||||
if (null === $resolved) {
|
||||
$resolved = new \ArrayObject();
|
||||
}
|
||||
if (null === $unresolved) {
|
||||
$unresolved = new \ArrayObject();
|
||||
}
|
||||
$nodeName = $node->getName();
|
||||
$unresolved[$nodeName] = $node;
|
||||
foreach ($tree[$nodeName] as $dependency) {
|
||||
if (!$resolved->offsetExists($dependency->getName())) {
|
||||
self::resolveDependencies($tree, $dependency, $resolved, $unresolved);
|
||||
}
|
||||
}
|
||||
$resolved[$nodeName] = $node;
|
||||
unset($unresolved[$nodeName]);
|
||||
return $resolved;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,35 @@ require_once __DIR__.'/Fixtures/ClassesWithParents/A.php';
|
||||
|
||||
class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testTraitDependencies()
|
||||
{
|
||||
if (version_compare(phpversion(), '5.4', '<')) {
|
||||
$this->markTestSkipped('Requires PHP > 5.4');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
require_once __DIR__.'/Fixtures/deps/traits.php';
|
||||
|
||||
$r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
|
||||
$m = $r->getMethod('getOrderedClasses');
|
||||
$m->setAccessible(true);
|
||||
|
||||
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTFoo'));
|
||||
|
||||
$this->assertEquals(
|
||||
array('TD', 'TC', 'TB', 'TA', 'TZ', 'CTFoo'),
|
||||
array_map(function ($class) { return $class->getName(); }, $ordered)
|
||||
);
|
||||
|
||||
$ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTBar'));
|
||||
|
||||
$this->assertEquals(
|
||||
array('TD', 'TZ', 'TC', 'TB', 'TA', 'CTBar'),
|
||||
array_map(function ($class) { return $class->getName(); }, $ordered)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDifferentOrders
|
||||
*/
|
||||
@ -71,8 +100,8 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testClassWithTraitsReordering(array $classes)
|
||||
{
|
||||
if (version_compare(phpversion(), '5.4.0', '<')) {
|
||||
$this->markTestSkipped('Requires PHP > 5.4.0.');
|
||||
if (version_compare(phpversion(), '5.4', '<')) {
|
||||
$this->markTestSkipped('Requires PHP > 5.4');
|
||||
|
||||
return;
|
||||
}
|
||||
@ -86,9 +115,9 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$expected = array(
|
||||
'ClassesWithParents\\GInterface',
|
||||
'ClassesWithParents\\CInterface',
|
||||
'ClassesWithParents\\CTrait',
|
||||
'ClassesWithParents\\ATrait',
|
||||
'ClassesWithParents\\BTrait',
|
||||
'ClassesWithParents\\CTrait',
|
||||
'ClassesWithParents\\B',
|
||||
'ClassesWithParents\\A',
|
||||
'ClassesWithParents\\D',
|
||||
@ -125,8 +154,20 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('<?php '.$expected, ClassCollectionLoader::fixNamespaceDeclarations('<?php '.$source));
|
||||
}
|
||||
|
||||
public function getFixNamespaceDeclarationsData()
|
||||
{
|
||||
return array(
|
||||
array("namespace;\nclass Foo {}\n", "namespace\n{\nclass Foo {}\n}"),
|
||||
array("namespace Foo;\nclass Foo {}\n", "namespace Foo\n{\nclass Foo {}\n}"),
|
||||
array("namespace Bar ;\nclass Foo {}\n", "namespace Bar\n{\nclass Foo {}\n}"),
|
||||
array("namespace Foo\Bar;\nclass Foo {}\n", "namespace Foo\Bar\n{\nclass Foo {}\n}"),
|
||||
array("namespace Foo\Bar\Bar\n{\nclass Foo {}\n}\n", "namespace Foo\Bar\Bar\n{\nclass Foo {}\n}"),
|
||||
array("namespace\n{\nclass Foo {}\n}\n", "namespace\n{\nclass Foo {}\n}"),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getFixNamespaceDeclarationsData
|
||||
* @dataProvider getFixNamespaceDeclarationsDataWithoutTokenizer
|
||||
*/
|
||||
public function testFixNamespaceDeclarationsWithoutTokenizer($source, $expected)
|
||||
{
|
||||
@ -135,7 +176,7 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
ClassCollectionLoader::enableTokenizer(true);
|
||||
}
|
||||
|
||||
public function getFixNamespaceDeclarationsData()
|
||||
public function getFixNamespaceDeclarationsDataWithoutTokenizer()
|
||||
{
|
||||
return array(
|
||||
array("namespace;\nclass Foo {}\n", "namespace\n{\nclass Foo {}\n}\n"),
|
||||
@ -168,41 +209,47 @@ class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
require_once __DIR__.'/Fixtures/'.str_replace(array('\\', '_'), '/', $class).'.php';
|
||||
});
|
||||
|
||||
ClassCollectionLoader::load(array('Namespaced\\WithComments', 'Pearlike_WithComments'), sys_get_temp_dir(), 'bar', false);
|
||||
ClassCollectionLoader::load(
|
||||
array('Namespaced\\WithComments', 'Pearlike_WithComments'),
|
||||
sys_get_temp_dir(),
|
||||
'bar',
|
||||
false
|
||||
);
|
||||
|
||||
spl_autoload_unregister($r);
|
||||
|
||||
$this->assertEquals(<<<EOF
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
namespace Namespaced
|
||||
{
|
||||
|
||||
class WithComments
|
||||
{
|
||||
|
||||
public static \$loaded = true;
|
||||
public static \$loaded = true;
|
||||
}
|
||||
\$string ='string shoult not be modified';
|
||||
\$heredoc =<<<HD
|
||||
|
||||
|
||||
Heredoc should not be modified
|
||||
|
||||
|
||||
HD;
|
||||
\$nowdoc =<<<'ND'
|
||||
|
||||
|
||||
Nowdoc should not be modified
|
||||
|
||||
|
||||
ND;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
class Pearlike_WithComments
|
||||
{
|
||||
|
||||
public static \$loaded = true;
|
||||
public static \$loaded = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EOF
|
||||
, file_get_contents($file));
|
||||
, str_replace("<?php \n", '', file_get_contents($file)));
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
|
@ -16,3 +16,22 @@ class WithComments
|
||||
/** @Boolean */
|
||||
public static $loaded = true;
|
||||
}
|
||||
|
||||
$string = 'string shoult not be modified';
|
||||
|
||||
|
||||
$heredoc = <<<HD
|
||||
|
||||
|
||||
Heredoc should not be modified
|
||||
|
||||
|
||||
HD;
|
||||
|
||||
$nowdoc = <<<'ND'
|
||||
|
||||
|
||||
Nowdoc should not be modified
|
||||
|
||||
|
||||
ND;
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
trait TD
|
||||
{}
|
||||
|
||||
trait TZ
|
||||
{
|
||||
use TD;
|
||||
}
|
||||
|
||||
trait TC
|
||||
{
|
||||
use TD;
|
||||
}
|
||||
|
||||
trait TB
|
||||
{
|
||||
use TC;
|
||||
}
|
||||
|
||||
trait TA
|
||||
{
|
||||
use TB;
|
||||
}
|
||||
|
||||
class CTFoo
|
||||
{
|
||||
use TA;
|
||||
use TZ;
|
||||
}
|
||||
|
||||
class CTBar
|
||||
{
|
||||
use TZ;
|
||||
use TA;
|
||||
}
|
Reference in New Issue
Block a user