Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Classcollectionloader: fix traits + enhancements #6258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 91 additions & 28 deletions src/Symfony/Component/ClassLoader/ClassCollectionLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,45 +144,78 @@ public static function fixNamespaceDeclarations($source)
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
);
}

/**
Expand Down Expand Up @@ -247,17 +280,19 @@ private static function getClassHierarchy(\ReflectionClass $class)
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)
Expand All @@ -277,26 +312,54 @@ private static function getInterfaces(\ReflectionClass $class)
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,35 @@

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
*/
Expand Down Expand Up @@ -71,8 +100,8 @@ public function getDifferentOrders()
*/
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;
}
Expand All @@ -86,9 +115,9 @@ public function testClassWithTraitsReordering(array $classes)
$expected = array(
'ClassesWithParents\\GInterface',
'ClassesWithParents\\CInterface',
'ClassesWithParents\\CTrait',
'ClassesWithParents\\ATrait',
'ClassesWithParents\\BTrait',
'ClassesWithParents\\CTrait',
'ClassesWithParents\\B',
'ClassesWithParents\\A',
'ClassesWithParents\\D',
Expand Down Expand Up @@ -125,8 +154,20 @@ public function testFixNamespaceDeclarations($source, $expected)
$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)
{
Expand All @@ -135,7 +176,7 @@ public function testFixNamespaceDeclarationsWithoutTokenizer($source, $expected)
ClassCollectionLoader::enableTokenizer(true);
}

public function getFixNamespaceDeclarationsData()
public function getFixNamespaceDeclarationsDataWithoutTokenizer()
{
return array(
array("namespace;\nclass Foo {}\n", "namespace\n{\nclass Foo {}\n}\n"),
Expand Down Expand Up @@ -168,41 +209,47 @@ public function testCommentStripping()
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;
}

namespace
{
\$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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
36 changes: 36 additions & 0 deletions src/Symfony/Component/ClassLoader/Tests/Fixtures/deps/traits.php
Original file line number Diff line number Diff line change
@@ -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;
}