Permalink
Browse files

ENHANCEMENT Allow ClassManifest to handle classes with namespaces, or…

… that extend classes in namspaces or that implement interfaces in namespaces.
  • Loading branch information...
1 parent bad1b88 commit 3e6a91a07fd272523043e79580696f54f0009c8f @simonwelsh simonwelsh committed Dec 17, 2011
@@ -40,19 +40,59 @@ public static function get_class_parser() {
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
*/
@@ -63,7 +103,7 @@ public static function get_interface_parser() {
2 => array(T_STRING, 'save_to' => 'interfaceName')
));
}
-
+
/**
* Constructs and initialises a new class manifest, either loading the data
* from the cache or re-scanning for classes.
@@ -228,7 +268,7 @@ public function regenerate($cache = true) {
'classes', 'roots', 'children', 'descendants', 'interfaces',
'implementors', 'configs'
);
-
+
// Reset the manifest so stale info doesn't cause errors.
foreach ($reset as $reset) {
$this->$reset = array();
@@ -267,6 +307,7 @@ public function handleFile($basename, $pathname, $depth) {
$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,29 +318,47 @@ public function handleFile($basename, $pathname, $depth) {
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);
- $classes = self::get_class_parser()->findAll($tokens);
+ 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(
@@ -321,20 +380,34 @@ public function handleFile($basename, $pathname, $depth) {
} else {
$this->roots[] = $name;
}
-
- if ($implements) foreach ($implements as $interface) {
- $interface = strtolower($interface);
-
- if (!isset($this->implementors[$interface])) {
- $this->implementors[$interface] = array($name);
- } else {
- $this->implementors[$interface][] = $name;
+
+ 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])) {
+ $this->implementors[$interface] = array($name);
+ } else {
+ $this->implementors[$interface][] = $name;
+ }
+ }
}
}
}
-
+
foreach ($interfaces as $interface) {
- $this->interfaces[strtolower($interface['interfaceName'])] = $pathname;
+ $this->interfaces[strtolower($namespace . $interface['interfaceName'])] = $pathname;
}
}
@@ -348,7 +421,7 @@ public function handleFile($basename, $pathname, $depth) {
protected function coalesceDescendants($class) {
$result = array();
$lClass = strtolower($class);
-
+
if (array_key_exists($lClass, $this->children)) {
$this->descendants[$lClass] = array();
@@ -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());
+ }
+}
Oops, something went wrong.

0 comments on commit 3e6a91a

Please sign in to comment.