mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT Allow ClassManifest to handle classes with namespaces, or that extend classes in namspaces or that implement interfaces in namespaces.
This commit is contained in:
parent
bad1b88942
commit
3e6a91a07f
@ -40,19 +40,59 @@ class SS_ClassManifest {
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_STRING, 'save_to' => 'extends', 'can_jump_to' => 14),
|
||||
6 => array(T_STRING, 'save_to' => 'extends[]', 'can_jump_to' => 14),
|
||||
7 => T_WHITESPACE,
|
||||
8 => T_IMPLEMENTS,
|
||||
9 => T_WHITESPACE,
|
||||
10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'),
|
||||
11 => array(T_WHITESPACE, 'optional' => true),
|
||||
12 => array(',', 'can_jump_to' => 10),
|
||||
12 => array(',', 'can_jump_to' => 10, 'save_to' => 'interfaces[]'),
|
||||
13 => array(T_WHITESPACE, 'can_jump_to' => 10),
|
||||
14 => array(T_WHITESPACE, 'optional' => true),
|
||||
15 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_namespaced_class_parser() {
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_CLASS,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_STRING, 'can_jump_to' => array(8, 16), 'save_to' => 'className'),
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_NS_SEPARATOR, 'save_to' => 'extends[]', 'optional' => true),
|
||||
7 => array(T_STRING, 'save_to' => 'extends[]', 'can_jump_to' => array(6, 16)),
|
||||
8 => T_WHITESPACE,
|
||||
9 => T_IMPLEMENTS,
|
||||
10 => T_WHITESPACE,
|
||||
11 => array(T_NS_SEPARATOR, 'save_to' => 'interfaces[]', 'optional' => true),
|
||||
12 => array(T_STRING, 'can_jump_to' => array(11, 16), 'save_to' => 'interfaces[]'),
|
||||
13 => array(T_WHITESPACE, 'optional' => true),
|
||||
14 => array(',', 'can_jump_to' => 11, 'save_to' => 'interfaces[]'),
|
||||
15 => array(T_WHITESPACE, 'can_jump_to' => 11),
|
||||
16 => array(T_WHITESPACE, 'optional' => true),
|
||||
17 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_namespace_parser() {
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_NAMESPACE,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_NS_SEPARATOR, 'save_to' => 'namespaceName[]', 'optional' => true),
|
||||
3 => array(T_STRING, 'save_to' => 'namespaceName[]', 'can_jump_to' => 2),
|
||||
4 => array(T_WHITESPACE, 'optional' => true),
|
||||
5 => ';',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
@ -267,6 +307,7 @@ class SS_ClassManifest {
|
||||
|
||||
$classes = null;
|
||||
$interfaces = null;
|
||||
$namespace = null;
|
||||
|
||||
// The results of individual file parses are cached, since only a few
|
||||
// files will have changed and TokenisedRegularExpression is quite
|
||||
@ -277,30 +318,48 @@ class SS_ClassManifest {
|
||||
|
||||
if ($data = $this->cache->load($key)) {
|
||||
$valid = (
|
||||
isset($data['classes']) && isset($data['interfaces'])
|
||||
&& is_array($data['classes']) && is_array($data['interfaces'])
|
||||
isset($data['classes']) && isset($data['interfaces']) && isset($data['namespace'])
|
||||
&& is_array($data['classes']) && is_array($data['interfaces']) && is_array($data['namespace'])
|
||||
);
|
||||
|
||||
if ($valid) {
|
||||
$classes = $data['classes'];
|
||||
$interfaces = $data['interfaces'];
|
||||
$namespace = $data['namespace'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$classes) {
|
||||
$tokens = token_get_all($file);
|
||||
if(version_compare(PHP_VERSION, '5.3', '>=')) {
|
||||
$classes = self::get_namespaced_class_parser()->findAll($tokens);
|
||||
$namespace = self::get_namespace_parser()->findAll($tokens);
|
||||
if($namespace) {
|
||||
$namespace = implode('', $namespace[0]['namespaceName']) . '\\';
|
||||
} else {
|
||||
$namespace = '';
|
||||
}
|
||||
} else {
|
||||
$classes = self::get_class_parser()->findAll($tokens);
|
||||
$namespace = '';
|
||||
}
|
||||
$interfaces = self::get_interface_parser()->findAll($tokens);
|
||||
|
||||
$cache = array('classes' => $classes, 'interfaces' => $interfaces);
|
||||
$cache = array('classes' => $classes, 'interfaces' => $interfaces, 'namespace' => $namespace);
|
||||
$this->cache->save($cache, $key, array('fileparse'));
|
||||
}
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$name = $class['className'];
|
||||
$extends = isset($class['extends']) ? $class['extends'] : null;
|
||||
$name = $namespace . $class['className'];
|
||||
$extends = isset($class['extends']) ? implode('', $class['extends']) : null;
|
||||
$implements = isset($class['interfaces']) ? $class['interfaces'] : null;
|
||||
|
||||
if($extends && $extends[0] != '/\\') {
|
||||
$extends = $namespace . $extends;
|
||||
} elseif($extends) {
|
||||
$extends = substr($extends, 1);
|
||||
}
|
||||
|
||||
if (array_key_exists($name, $this->classes)) {
|
||||
throw new Exception(sprintf(
|
||||
'There are two files containing the "%s" class: "%s" and "%s"',
|
||||
@ -322,7 +381,19 @@ class SS_ClassManifest {
|
||||
$this->roots[] = $name;
|
||||
}
|
||||
|
||||
if ($implements) foreach ($implements as $interface) {
|
||||
if ($implements) {
|
||||
$interface = $namespace;
|
||||
for($i = 0; $i < count($implements); ++$i) {
|
||||
if($implements[$i] == ',') {
|
||||
$interface = $namespace;
|
||||
continue;
|
||||
}
|
||||
if($implements[$i] == '\\' && $interface == $namespace) {
|
||||
$interface = '';
|
||||
} else {
|
||||
$interface .= $implements[$i];
|
||||
}
|
||||
if($i == count($implements)-1 || $implements[$i+1] == ',') {
|
||||
$interface = strtolower($interface);
|
||||
|
||||
if (!isset($this->implementors[$interface])) {
|
||||
@ -332,9 +403,11 @@ class SS_ClassManifest {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->interfaces[strtolower($interface['interfaceName'])] = $pathname;
|
||||
$this->interfaces[strtolower($namespace . $interface['interfaceName'])] = $pathname;
|
||||
}
|
||||
}
|
||||
|
||||
|
114
tests/core/manifest/NamespacedClassManifestTest.php
Normal file
114
tests/core/manifest/NamespacedClassManifestTest.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the {@link SS_ClassManifest} class.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class NamespacedClassManifestTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
protected $manifest;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/namespaced_classmanifest';
|
||||
$this->manifest = new SS_ClassManifest($this->base, false, true, false);
|
||||
}
|
||||
|
||||
public function testGetItemPath() {
|
||||
$expect = array(
|
||||
'SAPPHIRE\TEST\CLASSA' => 'module/classes/ClassA.php',
|
||||
'Sapphire\Test\ClassA' => 'module/classes/ClassA.php',
|
||||
'sapphire\test\classa' => 'module/classes/ClassA.php',
|
||||
'SAPPHIRE\TEST\INTERFACEA' => 'module/interfaces/InterfaceA.php',
|
||||
'Sapphire\Test\InterfaceA' => 'module/interfaces/InterfaceA.php',
|
||||
'sapphire\test\interfacea' => 'module/interfaces/InterfaceA.php'
|
||||
);
|
||||
|
||||
foreach ($expect as $name => $path) {
|
||||
$this->assertEquals("{$this->base}/$path", $this->manifest->getItemPath($name));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetClasses() {
|
||||
$expect = array(
|
||||
'sapphire\test\classa' => "{$this->base}/module/classes/ClassA.php",
|
||||
'sapphire\test\classb' => "{$this->base}/module/classes/ClassB.php",
|
||||
'sapphire\test\classc' => "{$this->base}/module/classes/ClassC.php",
|
||||
'sapphire\test\classd' => "{$this->base}/module/classes/ClassD.php",
|
||||
'sapphire\test\classe' => "{$this->base}/module/classes/ClassE.php",
|
||||
'sapphire\test\classf' => "{$this->base}/module/classes/ClassF.php",
|
||||
'sapphire\test\classg' => "{$this->base}/module/classes/ClassG.php"
|
||||
);
|
||||
|
||||
$this->assertEquals($expect, $this->manifest->getClasses());
|
||||
}
|
||||
|
||||
public function testGetClassNames() {
|
||||
$this->assertEquals(
|
||||
array('sapphire\test\classa', 'sapphire\test\classb', 'sapphire\test\classc', 'sapphire\test\classd', 'sapphire\test\classe', 'sapphire\test\classf', 'sapphire\test\classg'),
|
||||
$this->manifest->getClassNames());
|
||||
}
|
||||
|
||||
public function testGetDescendants() {
|
||||
$expect = array(
|
||||
'sapphire\test\classa' => array('sapphire\test\ClassB')
|
||||
);
|
||||
|
||||
$this->assertEquals($expect, $this->manifest->getDescendants());
|
||||
}
|
||||
|
||||
public function testGetDescendantsOf() {
|
||||
$expect = array(
|
||||
'SAPPHIRE\TEST\CLASSA' => array('sapphire\test\ClassB'),
|
||||
'sapphire\test\classa' => array('sapphire\test\ClassB'),
|
||||
);
|
||||
|
||||
foreach ($expect as $class => $desc) {
|
||||
$this->assertEquals($desc, $this->manifest->getDescendantsOf($class));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetInterfaces() {
|
||||
$expect = array(
|
||||
'sapphire\test\interfacea' => "{$this->base}/module/interfaces/InterfaceA.php",
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getInterfaces());
|
||||
}
|
||||
|
||||
public function testGetImplementors() {
|
||||
$expect = array(
|
||||
'sapphire\test\interfacea' => array('sapphire\test\ClassE'),
|
||||
'interfacea' => array('sapphire\test\ClassF'),
|
||||
'sapphire\test\subtest\interfacea' => array('sapphire\test\ClassG')
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getImplementors());
|
||||
}
|
||||
|
||||
public function testGetImplementorsOf() {
|
||||
$expect = array(
|
||||
'SAPPHIRE\TEST\INTERFACEA' => array('sapphire\test\ClassE'),
|
||||
'sapphire\test\interfacea' => array('sapphire\test\ClassE'),
|
||||
'INTERFACEA' => array('sapphire\test\ClassF'),
|
||||
'interfacea' => array('sapphire\test\ClassF'),
|
||||
'SAPPHIRE\TEST\SUBTEST\INTERFACEA' => array('sapphire\test\ClassG'),
|
||||
'sapphire\test\subtest\interfacea' => array('sapphire\test\ClassG'),
|
||||
);
|
||||
|
||||
foreach ($expect as $interface => $impl) {
|
||||
$this->assertEquals($impl, $this->manifest->getImplementorsOf($interface));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetConfigs() {
|
||||
$expect = array("{$this->base}/module/_config.php");
|
||||
$this->assertEquals($expect, $this->manifest->getConfigs());
|
||||
}
|
||||
|
||||
public function testGetModules() {
|
||||
$expect = array("module" => "{$this->base}/module");
|
||||
$this->assertEquals($expect, $this->manifest->getModules());
|
||||
}
|
||||
}
|
@ -47,6 +47,45 @@ interface InterfaceC extends InterfaceA, InterfaceB {
|
||||
}
|
||||
interface InterfaceD extends InterfaceA, InterfaceB, InterfaceC {
|
||||
}
|
||||
?>
|
||||
PHP
|
||||
);
|
||||
}
|
||||
|
||||
function getNamespaceTokens() {
|
||||
return token_get_all(<<<PHP
|
||||
<?php
|
||||
|
||||
namespace sapphire\\test;
|
||||
|
||||
class ClassA {
|
||||
|
||||
}
|
||||
|
||||
class ClassB extends ParentClassB {
|
||||
|
||||
}
|
||||
|
||||
class ClassC extends \\ParentClassC {
|
||||
|
||||
}
|
||||
|
||||
class ClassD extends subtest\\ParentClassD {
|
||||
|
||||
}
|
||||
|
||||
class ClassE implements InterfaceE {
|
||||
|
||||
}
|
||||
|
||||
class ClassF implements \\InterfaceF {
|
||||
|
||||
}
|
||||
|
||||
class ClassG implements subtest\\InterfaceG {
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
PHP
|
||||
);
|
||||
@ -65,23 +104,53 @@ PHP
|
||||
$this->assertArrayHasKey('ClassB', $classes);
|
||||
|
||||
$this->assertArrayHasKey('ClassC', $classes);
|
||||
$this->assertEquals('ParentClassC', $classes['ClassC']['extends']);
|
||||
$this->assertEquals(array('ParentClassC'), $classes['ClassC']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassD', $classes);
|
||||
$this->assertEquals('ParentClassD', $classes['ClassD']['extends']);
|
||||
$this->assertEquals(array('ParentClassD'), $classes['ClassD']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassD']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassE', $classes);
|
||||
$this->assertEquals('ParentClassE', $classes['ClassE']['extends']);
|
||||
$this->assertEquals(array('ParentClassE'), $classes['ClassE']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassE']['interfaces']);
|
||||
$this->assertContains('InterfaceB', $classes['ClassE']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassF', $classes);
|
||||
$this->assertEquals('ParentClassF', $classes['ClassF']['extends']);
|
||||
$this->assertEquals(array('ParentClassF'), $classes['ClassF']['extends']);
|
||||
$this->assertContains('InterfaceA', $classes['ClassF']['interfaces']);
|
||||
$this->assertContains('InterfaceB', $classes['ClassF']['interfaces']);
|
||||
}
|
||||
|
||||
function testNamesapcedClassDefParser() {
|
||||
if(version_compare(PHP_VERSION, '5.3', '<')) {
|
||||
return;
|
||||
}
|
||||
$parser = SS_ClassManifest::get_namespaced_class_parser();
|
||||
|
||||
$tokens = $this->getNamespaceTokens();
|
||||
|
||||
$matches = $parser->findAll($tokens);
|
||||
|
||||
$classes = array();
|
||||
if($matches) foreach($matches as $match) $classes[$match['className']] = $match;
|
||||
|
||||
$this->assertArrayHasKey('ClassA', $classes);
|
||||
$this->assertArrayHasKey('ClassB', $classes);
|
||||
$this->assertEquals(array('ParentClassB'), $classes['ClassB']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassC', $classes);
|
||||
$this->assertEquals(array('\\', 'ParentClassC'), $classes['ClassC']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassD', $classes);
|
||||
$this->assertEquals(array('subtest', '\\', 'ParentClassD'), $classes['ClassD']['extends']);
|
||||
|
||||
$this->assertArrayHasKey('ClassE', $classes);
|
||||
$this->assertContains('InterfaceE', $classes['ClassE']['interfaces']);
|
||||
|
||||
$this->assertArrayHasKey('ClassF', $classes);
|
||||
$this->assertEquals(array('\\', 'InterfaceF'), $classes['ClassF']['interfaces']);
|
||||
}
|
||||
|
||||
function testInterfaceDefParser() {
|
||||
$parser = SS_ClassManifest::get_interface_parser();
|
||||
|
||||
@ -96,4 +165,20 @@ PHP
|
||||
$this->assertArrayHasKey('InterfaceC', $interfaces);
|
||||
$this->assertArrayHasKey('InterfaceD', $interfaces);
|
||||
}
|
||||
|
||||
function testNamespaceDefParser() {
|
||||
if(version_compare(PHP_VERSION, '5.3', '<')) {
|
||||
return;
|
||||
}
|
||||
$parser = SS_ClassManifest::get_namespace_parser();
|
||||
|
||||
$namespacedTokens = $this->getNamespaceTokens();
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
$namespacedMatches = $parser->findAll($namespacedTokens);
|
||||
$matches = $parser->findAll($tokens);
|
||||
|
||||
$this->assertEquals(array(), $matches);
|
||||
$this->assertEquals(array('sapphire', '\\', 'test'), $namespacedMatches[0]['namespaceName']);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassB extends ClassA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassC extends \ClassA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassD extends subtest\ClassC { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassE implements InterfaceA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassF implements \InterfaceA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
class ClassG implements subtest\InterfaceA { }
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
namespace sapphire\test;
|
||||
|
||||
interface InterfaceA { }
|
Loading…
Reference in New Issue
Block a user