diff --git a/composer.json b/composer.json index afa689464..8c4a924c6 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "symfony/config": "^2.8", "symfony/translation": "^2.8", "vlucas/phpdotenv": "^2.4", + "nikic/php-parser": "^2.1", "silverstripe/config": "^1@dev" }, "require-dev": { diff --git a/src/Core/Manifest/ClassManifest.php b/src/Core/Manifest/ClassManifest.php index 0ba60b92f..6369412fe 100644 --- a/src/Core/Manifest/ClassManifest.php +++ b/src/Core/Manifest/ClassManifest.php @@ -3,6 +3,9 @@ namespace SilverStripe\Core\Manifest; use Exception; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use PhpParser\ParserFactory; use SilverStripe\Control\Director; /** @@ -163,6 +166,10 @@ class ClassManifest )); } + protected $parser; + protected $traverser; + protected $visitor; + /** * Constructs and initialises a new class manifest, either loading the data * from the cache or re-scanning for classes. @@ -182,6 +189,11 @@ class ClassManifest $this->cache = new $cacheClass('classmanifest'.($includeTests ? '_tests' : '')); $this->cacheKey = 'manifest'; + $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP5, new PhpParser\Lexer); + $this->traverser = new NodeTraverser; + $this->traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + $this->traverser->addVisitor($this->visitor = new SilverStripeNodeVisitor); + if (!$forceRegen && $data = $this->cache->load($this->cacheKey)) { $this->classes = $data['classes']; $this->descendants = $data['descendants']; @@ -593,22 +605,17 @@ class ClassManifest } if (!$valid) { - $tokens = token_get_all(file_get_contents($pathname)); + $this->visitor->reset(); + $stmts = $this->parser->parse(file_get_contents($pathname)); + $this->traverser->traverse($stmts); - $classes = self::get_namespaced_class_parser()->findAll($tokens); - $traits = self::get_trait_parser()->findAll($tokens); + $classes = $this->visitor->getClasses(); + $traits = $this->visitor->getTraits(); + $namespace = $this->visitor->getNamespace(); - $namespace = self::get_namespace_parser()->findAll($tokens); + $imports = [];//$this->getImportsFromTokens($tokens); - if ($namespace) { - $namespace = implode('', $namespace[0]['namespaceName']); - } else { - $namespace = ''; - } - - $imports = $this->getImportsFromTokens($tokens); - - $interfaces = self::get_interface_parser()->findAll($tokens); + $interfaces = $this->visitor->getInterfaces(); $cache = array( 'classes' => $classes, @@ -722,3 +729,78 @@ class ClassManifest } } } + +class SilverStripeNodeVisitor extends NodeVisitorAbstract +{ + + private $classes = []; + + private $traits = []; + + private $namespace = ''; + + private $interfaces = []; + + public function reset() + { + $this->classes = []; + $this->traits = []; + $this->namespace = ''; + $this->interfaces = []; + } + + public function enterNode(PhpParser\Node $node) + { + if ($node instanceof PhpParser\Node\Stmt\Class_) { + $extends = []; + $implements = []; + + if ($node->extends) { + $extends[] = (string)$node->extends; + } + + if ($node->implements) { + foreach ($node->implements as $implement) { + $implements[] = (string)$implement; + } + } + + $this->classes[] = [ + 'className' => $node->name, + 'extends' => $extends, + 'implements' => $implements + ]; + } else if ($node instanceof PhpParser\Node\Stmt\Trait_) { + $this->traits[] = ['traitName' => (string)$node->name]; + } else if ($node instanceof PhpParser\Node\Stmt\Namespace_) { + $this->namespace = (string)$node->name; + } else if ($node instanceof PhpParser\Node\Stmt\Interface_) { + $this->interfaces[] = ['interfaceName' => (string)$node->name]; + } + + if (!$node instanceof PhpParser\Node\Stmt\Namespace_) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + } + + public function getClasses() + { + return $this->classes; + } + + public function getTraits() + { + return $this->traits; + } + + public function getNamespace() + { + return $this->namespace; + } + + public function getInterfaces() + { + return $this->interfaces; + } + +}