.gitignore000064400000000027144760111300006530 0ustar00/vendor/ composer.lock .scrutinizer.yml000064400000001517144760111300007727 0ustar00before_commands: - "composer install --no-dev --prefer-source" tools: external_code_coverage: timeout: 1200 php_code_coverage: enabled: true php_code_sniffer: enabled: true config: standard: PSR2 filter: paths: ["src/*"] php_cpd: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_cs_fixer: enabled: true config: level: all filter: paths: ["src/*"] php_loc: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_pdepend: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_analyzer: enabled: true filter: paths: ["src/*"] sensiolabs_security_checker: true .travis.coverage.sh000075500000000260144760111300010256 0ustar00set -x if [ "$TRAVIS_PHP_VERSION" = '7.0' ] ; then wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover ./clover.xml fi .travis.yml000064400000000417144760111300006654 0ustar00language: php php: - 5.5 - 5.6 - 7.0 - hhvm before_script: - composer install script: - vendor/bin/phpunit --verbose --coverage-text --coverage-clover=clover.xml --colors after_script: - sh .travis.coverage.sh matrix: allow_failures: - php: hhvm README.md000064400000006140144760111300006021 0ustar00Parser Reflection API Library ----------------- Parser Reflection API library provides a set of classes that extend original internal Reflection classes, but powered by [PHP-Parser](https://github.com/nikic/PHP-Parser) library thus allowing to create a reflection instance without loading classes into the memory. This library can be used for analysing the source code, automatic proxy creation and much more. [![Build Status](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/build.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/build-status/master) [![Code Coverage](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master) [![SensioLabs Insight](https://img.shields.io/sensiolabs/i/1fdfee9c-839a-4209-a2f2-42dadc859621.svg)](https://insight.sensiolabs.com/projects/1fdfee9c-839a-4209-a2f2-42dadc859621)[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%205.5-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/packagist/l/goaop/parser-reflection.svg)](https://packagist.org/packages/goaop/parser-reflection) Installation ------------ Library can be installed with composer. Installation is quite easy: ```bash $ composer require goaop/parser-reflection ``` Composer will install the library to your project's `vendor/goaop/parser-reflection` directory. Usage ------------ Just use `Go\ParserReflection` package reflection classes like traditional ones: ```php $parsedClass = new \Go\ParserReflection\ReflectionClass(SomeClass::class); var_dump($parsedClass->getMethods()); ``` How it works? ------------ To understand how library works let's look at what happens during the call to the `new \Go\ParserReflection\ReflectionClass(SomeClass::class)` * `\Go\ParserReflection\ReflectionClass` asks reflection engine to give an AST node for the given class name * Reflection engine asks a locator to locate a filename for the given class * `ComposerLocator` instance asks the Composer to find a filename for the given class and returns this result back to the reflection engine * Reflection engine loads the content of file and passes it to the [PHP-Parser](https://github.com/nikic/PHP-Parser) for tokenization and processing * PHP-Parser returns an AST (Abstract Syntax Tree) * Reflection engine then analyse this AST to extract specific nodes an wrap them into corresponding reflection classes. Compatibility ------------ All parser reflection classes extend PHP internal reflection classes, this means that you can use `\Go\ParserReflection\ReflectionClass` instance in any place that asks for `\ReflectionClass` instance. All reflection methods should be compatible with original ones, providing an except methods that requires object manipulation, such as `invoke()`, `invokeArgs()`, `setAccessible()`, etc. These methods will trigger the autoloading of class and switching to the internal reflection. composer.json000064400000001215144760111300007262 0ustar00{ "name": "goaop/parser-reflection", "description": "Provides reflection information, based on raw source", "license": "MIT", "authors": [ { "name": "Alexander Lisachenko", "email": "lisachenko.it@gmail.com" } ], "minimum-stability": "stable", "autoload": { "psr-4": { "Go\\ParserReflection\\": "src" }, "files": ["src/bootstrap.php"], "exclude-from-classmap": ["/tests/"] }, "autoload-dev": { "psr-4": { "Go\\ParserReflection\\": "tests" } }, "require": { "php": ">=5.5.0", "nikic/php-parser": "^1.2|^2.0" }, "require-dev": { "phpunit/phpunit": "~4.0" } } phpunit.xml.dist000064400000001725144760111300007721 0ustar00 ./tests/ ./src/ performance src/Locator/ComposerLocator.php000064400000002672144760111300012566 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Locator; use Composer\Autoload\ClassLoader; use Go\ParserReflection\LocatorInterface; use Go\ParserReflection\ReflectionException; /** * Locator, that can find a file for the given class name by asking composer */ class ComposerLocator implements LocatorInterface { /** * @var ClassLoader */ private $loader; public function __construct(ClassLoader $loader = null) { if (!$loader) { $loaders = spl_autoload_functions(); foreach ($loaders as $loader) { if (is_array($loader) && $loader[0] instanceof ClassLoader) { $loader = $loader[0]; break; } } if (!$loader) { throw new ReflectionException("Can not found a correct composer loader"); } } $this->loader = $loader; } /** * Returns a path to the file for given class name * * @param string $className Name of the class * * @return string|false Path to the file with given class or false if not found */ public function locateClass($className) { return $this->loader->findFile($className); } } src/LocatorInterface.php000064400000001207144760111300011265 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; /** * Locator is responsible to return a file name for given item, typically class */ interface LocatorInterface { /** * Returns a path to the file for given class name * * @param string $className Name of the class * * @return string|false Path to the file with given class or false if not found */ public function locateClass($className); } src/NodeVisitor/GeneratorDetector.php000064400000002114144760111300013724 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\NodeVisitor; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; /** * Visitor to check if the method body */ class GeneratorDetector extends NodeVisitorAbstract { private $isGenerator = false; /** * {@inheritDoc} */ public function enterNode(Node $node) { // There may be internal generators in closures, we do not need to look at them if ($node instanceof Node\Expr\Closure) { return NodeTraverser::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Expr\Yield_ || $node instanceof Node\Expr\YieldFrom) { $this->isGenerator = true; } return null; } /** * Returns the * * @return bool */ public function isGenerator() { return $this->isGenerator; } } src/NodeVisitor/StaticVariablesCollector.php000064400000004067144760111300015244 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\NodeVisitor; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; /** * Visitor to collect static variables in the method/function body and resove them */ class StaticVariablesCollector extends NodeVisitorAbstract { /** * Reflection context, eg. ReflectionClass, ReflectionMethod, etc * * @var mixed */ private $context; private $staticVariables = []; /** * Default constructor * * @param mixed $context Reflection context, eg. ReflectionClass, ReflectionMethod, etc */ public function __construct($context) { $this->context = $context; } /** * {@inheritDoc} */ public function enterNode(Node $node) { // There may be internal closures, we do not need to look at them if ($node instanceof Node\Expr\Closure) { return NodeTraverser::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Stmt\Static_) { $expressionSolver = new NodeExpressionResolver($this->context); $staticVariables = $node->vars; foreach ($staticVariables as $staticVariable) { $expr = $staticVariable->default; if ($expr) { $expressionSolver->process($expr); $value = $expressionSolver->getValue(); } else { $value = null; } $this->staticVariables[$staticVariable->name] = $value; } } return null; } /** * Returns an associative map of static variables in the method/function body * * @return array */ public function getStaticVariables() { return $this->staticVariables; } } src/ReflectionClass.php000064400000010372144760111300011124 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\Traits\ReflectionClassLikeTrait; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\TraitUse; use ReflectionClass as InternalReflectionClass; /** * AST-based reflection class */ class ReflectionClass extends InternalReflectionClass { use ReflectionClassLikeTrait, InternalPropertiesEmulationTrait; /** * Initializes reflection instance * * @param string|object $argument Class name or instance of object * @param ClassLike $classLikeNode AST node for class */ public function __construct($argument, ClassLike $classLikeNode = null) { $fullClassName = is_object($argument) ? get_class($argument) : $argument; $namespaceParts = explode('\\', $fullClassName); $this->className = array_pop($namespaceParts); // Let's unset original read-only property to have a control over it via __get unset($this->name); $this->namespaceName = join('\\', $namespaceParts); $this->classLikeNode = $classLikeNode ?: ReflectionEngine::parseClass($fullClassName); } /** * Parses interfaces from the concrete class node * * @param ClassLike $classLikeNode Class-like node * * @return array|\ReflectionClass[] List of reflections of interfaces */ public static function collectInterfacesFromClassNode(ClassLike $classLikeNode) { $interfaces = []; $isInterface = $classLikeNode instanceof Interface_; $interfaceField = $isInterface ? 'extends' : 'implements'; $hasInterfaces = in_array($interfaceField, $classLikeNode->getSubNodeNames()); $implementsList = $hasInterfaces ? $classLikeNode->$interfaceField : array(); if ($implementsList) { foreach ($implementsList as $implementNode) { if ($implementNode instanceof FullyQualified) { $implementName = $implementNode->toString(); $interface = interface_exists($implementName, false) ? new parent($implementName) : new static($implementName); $interfaces[$implementName] = $interface; } } } return $interfaces; } /** * Parses traits from the concrete class node * * @param ClassLike $classLikeNode Class-like node * @param array $traitAdaptations List of method adaptations * * @return array|\ReflectionClass[] List of reflections of traits */ public static function collectTraitsFromClassNode(ClassLike $classLikeNode, array &$traitAdaptations) { $traits = []; if (!empty($classLikeNode->stmts)) { foreach ($classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof TraitUse) { foreach ($classLevelNode->traits as $classTraitName) { if ($classTraitName instanceof FullyQualified) { $traitName = $classTraitName->toString(); $trait = trait_exists($traitName, false) ? new parent($traitName) : new static($traitName); $traits[$traitName] = $trait; } } $traitAdaptations = $classLevelNode->adaptations; } } } return $traits; } /** * Emulating original behaviour of reflection */ public function __debugInfo() { return array( 'name' => $this->getName() ); } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize() { parent::__construct($this->getName()); } } src/ReflectionEngine.php000064400000014637144760111300011274 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use PhpParser\Lexer; use PhpParser\Node; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Property; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser; use PhpParser\ParserFactory; /** * AST-based reflection engine, powered by PHP-Parser */ class ReflectionEngine { /** * @var null|LocatorInterface */ protected static $locator = null; /** * @var array|Node[] */ protected static $parsedFiles = array(); /** * @var null|Parser */ protected static $parser = null; /** * @var null|NodeTraverser */ protected static $traverser = null; private function __construct() {} public static function init(LocatorInterface $locator) { $refParser = new \ReflectionClass(Parser::class); $isNewParser = $refParser->isInterface(); if (!$isNewParser) { self::$parser = new Parser(new Lexer(['usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos' ]])); } else { self::$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); } self::$traverser = $traverser = new NodeTraverser(); $traverser->addVisitor(new NameResolver()); self::$locator = $locator; } /** * Locates a file name for class * * @param string $fullClassName Full name of the class * * @return string */ public static function locateClassFile($fullClassName) { if (class_exists($fullClassName, false) || interface_exists($fullClassName, false) || trait_exists($fullClassName, false) ) { $refClass = new \ReflectionClass($fullClassName); $classFileName = $refClass->getFileName(); } else { $classFileName = self::$locator->locateClass($fullClassName); } if (!$classFileName) { throw new \InvalidArgumentException("Class $fullClassName was not found by locator"); } return $classFileName; } /** * Tries to parse a class by name using LocatorInterface * * @param string $fullClassName Class name to load * * @return ClassLike */ public static function parseClass($fullClassName) { $classFileName = self::locateClassFile($fullClassName); $namespaceParts = explode('\\', $fullClassName); $className = array_pop($namespaceParts); $namespaceName = join('\\', $namespaceParts); if ($namespaceName) { // we have a namespace nodes somewhere $namespace = self::parseFileNamespace($classFileName, $namespaceName); $namespaceNodes = $namespace->stmts; } else { // global namespace $namespaceNodes = self::parseFile($classFileName); } foreach ($namespaceNodes as $namespaceLevelNode) { if ($namespaceLevelNode instanceof ClassLike && $namespaceLevelNode->name == $className) { return $namespaceLevelNode; } } throw new \InvalidArgumentException("Class $fullClassName was not found in the $classFileName"); } /** * Parses class method * * @param string $fullClassName Name of the class * @param string $methodName Name of the method * * @return ClassMethod */ public static function parseClassMethod($fullClassName, $methodName) { $class = self::parseClass($fullClassName); $classNodes = $class->stmts; foreach ($classNodes as $classLevelNode) { if ($classLevelNode instanceof ClassMethod && $classLevelNode->name == $methodName) { return $classLevelNode; } } throw new \InvalidArgumentException("Method $methodName was not found in the $fullClassName"); } /** * Parses class property * * @param string $fullClassName Name of the class * @param string $propertyName Name of the property * * @return array Pair of [Property and PropertyProperty] nodes */ public static function parseClassProperty($fullClassName, $propertyName) { $class = self::parseClass($fullClassName); $classNodes = $class->stmts; foreach ($classNodes as $classLevelNode) { if ($classLevelNode instanceof Property) { foreach ($classLevelNode->props as $classProperty) { if ($classProperty->name == $propertyName) { return [$classLevelNode, $classProperty]; } } } } throw new \InvalidArgumentException("Property $propertyName was not found in the $fullClassName"); } /** * Parses a file and returns an AST for it * * @param string $fileName Name of the file * * @return Node[] */ public static function parseFile($fileName) { if (isset(self::$parsedFiles[$fileName])) { return self::$parsedFiles[$fileName]; } $fileContent = file_get_contents($fileName); $treeNode = self::$parser->parse($fileContent); $treeNode = self::$traverser->traverse($treeNode); self::$parsedFiles[$fileName] = $treeNode; return $treeNode; } /** * Parses a file namespace and returns an AST for it * * @param string $fileName Name of the file * @param string $namespaceName Namespace name * * @return Namespace_ */ public static function parseFileNamespace($fileName, $namespaceName) { $topLevelNodes = self::parseFile($fileName); // namespaces can be only top-level nodes, so we can scan them directly foreach ($topLevelNodes as $topLevelNode) { if ($topLevelNode instanceof Namespace_ && ($topLevelNode->name->toString() == $namespaceName)) { return $topLevelNode; } } throw new ReflectionException("Namespace $namespaceName was not found in the file $fileName"); } } src/ReflectionException.php000064400000000633144760111300012014 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use ReflectionException as BaseException; /** * General parser-level reflection exception */ class ReflectionException extends BaseException { } src/ReflectionFile.php000064400000006671144760111300010745 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use PhpParser\Node; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Namespace_; /** * AST-based reflector for the source file */ class ReflectionFile { /** * Name of the file for reflectino * * @var string */ protected $fileName; /** * List of namespaces in the file * * @var ReflectionFileNamespace[]|array */ protected $fileNamespaces; /** * Top-level nodes for the file * * @var Node[] */ private $topLevelNodes; /** * ReflectionFile constructor. * * @param string $fileName Name of the file to reflect * @param null|array|Node[] $topLevelNodes Optional corresponding list of AST nodes for that file */ public function __construct($fileName, $topLevelNodes = null) { $this->fileName = $fileName; $this->topLevelNodes = $topLevelNodes ?: ReflectionEngine::parseFile($fileName); } /** * Returns a namespace from the file or false if no such a namespace * * @param string $namespaceName * * @return bool|ReflectionFileNamespace */ public function getFileNamespace($namespaceName) { if ($this->hasFileNamespace($namespaceName)) { return $this->fileNamespaces[$namespaceName]; } return false; } /** * Gets the list of namespaces in the file * * @return array|ReflectionFileNamespace[] */ public function getFileNamespaces() { if (!isset($this->fileNamespaces)) { $this->fileNamespaces = $this->findFileNamespaces(); } return $this->fileNamespaces; } /** * Returns the name of current reflected file * * @return string */ public function getName() { return $this->fileName; } /** * Returns the presence of namespace in the file * * @param string $namespaceName Namespace to check * * @return bool */ public function hasFileNamespace($namespaceName) { $namespaces = $this->getFileNamespaces(); return isset($namespaces[$namespaceName]); } /** * Searches for file namespaces in the given AST * * @return array|ReflectionFileNamespace[] */ private function findFileNamespaces() { $namespaces = array(); // namespaces can be only top-level nodes, so we can scan them directly foreach ($this->topLevelNodes as $topLevelNode) { if ($topLevelNode instanceof Namespace_) { $namespaceName = $topLevelNode->name ? $topLevelNode->name->toString() : '\\'; $namespaces[$namespaceName] = new ReflectionFileNamespace( $this->fileName, $namespaceName, $topLevelNode ); } } if (!$namespaces) { // if we don't have a namespaces at all, this is global namespace $globalNamespaceNode = new Namespace_(new FullyQualified(''), $this->topLevelNodes); $namespaces['\\'] = new ReflectionFileNamespace($this->fileName, '\\', $globalNamespaceNode); } return $namespaces; } } src/ReflectionFileNamespace.php000064400000022656144760111300012563 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Const_; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; /** * AST-based reflection for the concrete namespace in the file */ class ReflectionFileNamespace { /** * List of classes in the namespace * * @var array|ReflectionClass[] */ protected $fileClasses; /** * List of functions in the namespace * * @var array|ReflectionFunction[] */ protected $fileFunctions; /** * List of constants in the namespace * * @var array */ protected $fileConstants; /** * List of imported namespaces (aliases) * * @var array */ protected $fileNamespaceAliases; /** * Namespace node * * @var Namespace_ */ private $namespaceNode; /** * Name of the file * * @var string */ private $fileName; /** * File namespace constructor * * @param string $fileName Name of the file * @param string $namespaceName Name of the namespace * @param Namespace_|null $namespaceNode Optional AST-node for this namespace block */ public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null) { if (!$namespaceNode) { $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName); } $this->namespaceNode = $namespaceNode; $this->fileName = $fileName; } /** * Returns the concrete class from the file namespace or false if there is no class * * @param string $className * * @return bool|ReflectionClass */ public function getClass($className) { if ($this->hasClass($className)) { return $this->fileClasses[$className]; } return false; } /** * Gets list of classes in the namespace * * @return ReflectionClass[]|array */ public function getClasses() { if (!isset($this->fileClasses)) { $this->fileClasses = $this->findClasses(); } return $this->fileClasses; } /** * Returns a value for the constant * * @param string $constantName name of the constant to fetch * * @return bool|mixed */ public function getConstant($constantName) { if ($this->hasConstant($constantName)) { return $this->fileConstants[$constantName]; } return false; } /** * Returns a list of defined constants in the namespace * * @return array */ public function getConstants() { if (!isset($this->fileConstants)) { $this->fileConstants = $this->findConstants(); } return $this->fileConstants; } /** * Gets doc comments from a class. * * @return string|false The doc comment if it exists, otherwise "false" */ public function getDocComment() { $docComment = false; $comments = $this->namespaceNode->getAttribute('comments'); if ($comments) { $docComment = (string) $comments[0]; } return $docComment; } /** * Gets starting line number * * @return integer */ public function getEndLine() { return $this->namespaceNode->getAttribute('endLine'); } /** * Returns the name of file * * @return string */ public function getFileName() { return $this->fileName; } /** * Returns the concrete function from the file namespace or false if there is no function * * @param string $functionName * * @return bool|ReflectionFunction */ public function getFunction($functionName) { if ($this->hasFunction($functionName)) { return $this->fileFunctions[$functionName]; } return false; } /** * Gets list of functions in the namespace * * @return ReflectionFunction[]|array */ public function getFunctions() { if (!isset($this->fileFunctions)) { $this->fileFunctions = $this->findFunctions(); } return $this->fileFunctions; } /** * Gets namespace name * * @return string */ public function getName() { $nameNode = $this->namespaceNode->name; return $nameNode ? $nameNode->toString() : ''; } /** * Returns a list of namespace aliases * * @return array */ public function getNamespaceAliases() { if (!isset($this->fileNamespaceAliases)) { $this->fileNamespaceAliases = $this->findNamespaceAliases(); } return $this->fileNamespaceAliases; } /** * Gets starting line number * * @return integer */ public function getStartLine() { return $this->namespaceNode->getAttribute('startLine'); } /** * Checks if the given class is present in this filenamespace * * @param string $className * * @return bool */ public function hasClass($className) { $classes = $this->getClasses(); return isset($classes[$className]); } /** * Checks if the given constant is present in this filenamespace * * @param string $constantName * * @return bool */ public function hasConstant($constantName) { $constants = $this->getConstants(); return isset($constants[$constantName]); } /** * Checks if the given function is present in this filenamespace * * @param string $functionName * * @return bool */ public function hasFunction($functionName) { $functions = $this->getFunctions(); return isset($functions[$functionName]); } /** * Searches for classes in the given AST * * @return array|ReflectionClass[] */ private function findClasses() { $classes = array(); $namespaceName = $this->getName(); // classes can be only top-level nodes in the namespace, so we can scan them directly foreach ($this->namespaceNode->stmts as $namespaceLevelNode) { if ($namespaceLevelNode instanceof ClassLike) { $classShortName = $namespaceLevelNode->name; $className = $namespaceName ? $namespaceName .'\\' . $classShortName : $classShortName; $namespaceLevelNode->setAttribute('fileName', $this->fileName); $classes[$className] = new ReflectionClass($className, $namespaceLevelNode); } } return $classes; } /** * Searches for functions in the given AST * * @return array */ private function findFunctions() { $functions = array(); $namespaceName = $this->getName(); // functions can be only top-level nodes in the namespace, so we can scan them directly foreach ($this->namespaceNode->stmts as $namespaceLevelNode) { if ($namespaceLevelNode instanceof Function_) { $funcShortName = $namespaceLevelNode->name; $functionName = $namespaceName ? $namespaceName .'\\' . $funcShortName : $funcShortName; $namespaceLevelNode->setAttribute('fileName', $this->fileName); $functions[$funcShortName] = new ReflectionFunction($functionName, $namespaceLevelNode); } } return $functions; } /** * Searches for constants in the given AST * * @return array */ private function findConstants() { $constants = array(); $expressionSolver = new NodeExpressionResolver($this); // constants can be only top-level nodes in the namespace, so we can scan them directly foreach ($this->namespaceNode->stmts as $namespaceLevelNode) { if ($namespaceLevelNode instanceof Const_) { $nodeConstants = $namespaceLevelNode->consts; if (!empty($nodeConstants)) { foreach ($nodeConstants as $nodeConstant) { $expressionSolver->process($nodeConstant->value); $constants[$nodeConstant->name] = $expressionSolver->getValue(); } } } } return $constants; } /** * Searchse for namespace aliases for the current block * * @return array */ private function findNamespaceAliases() { $namespaceAliases = []; // aliases can be only top-level nodes in the namespace, so we can scan them directly foreach ($this->namespaceNode->stmts as $namespaceLevelNode) { if ($namespaceLevelNode instanceof Use_) { $useAliases = $namespaceLevelNode->uses; if (!empty($useAliases)) { foreach ($useAliases as $useNode) { $namespaceAliases[$useNode->name->toString()] = $useNode->alias; } } } } return $namespaceAliases; } } src/ReflectionFunction.php000064400000006532144760111300011647 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait; use PhpParser\Node\Stmt\Function_; use ReflectionFunction as BaseReflectionFunction; /** * AST-based reflection for function */ class ReflectionFunction extends BaseReflectionFunction { use ReflectionFunctionLikeTrait, InternalPropertiesEmulationTrait; /** * Initializes reflection instance for given AST-node * * @param string|\Closure $functionName The name of the function to reflect or a closure. * @param Function_|null $functionNode Function node AST */ public function __construct($functionName, Function_ $functionNode) { $namespaceParts = explode('\\', $functionName); // Remove the last one part with function name array_pop($namespaceParts); $this->namespaceName = join('\\', $namespaceParts); $this->functionLikeNode = $functionNode; unset($this->name); } /** * Emulating original behaviour of reflection */ public function __debugInfo() { $nodeName = 'unknown'; if ($this->functionLikeNode instanceof Function_) { $nodeName = $this->functionLikeNode->name; } return ['name' => $nodeName]; } /** * {@inheritDoc} */ public function getClosure() { $this->initializeInternalReflection(); return parent::getClosure(); } /** * {@inheritDoc} */ public function invoke($args = null) { $this->initializeInternalReflection(); return call_user_func_array('parent::invoke', func_get_args()); } /** * {@inheritDoc} */ public function invokeArgs(array $args) { $this->initializeInternalReflection(); return parent::invokeArgs($args); } /** * Checks if function is disabled * * Only internal functions can be disabled using disable_functions directive. * User-defined functions are unaffected. */ public function isDisabled() { return false; } /** * Returns textual representation of function * * @return string */ public function __toString() { $paramFormat = ($this->getNumberOfParameters() > 0) ? "\n\n - Parameters [%d] {%s\n }" : ''; $reflectionFormat = "%sFunction [ function %s ] {\n @@ %s %d - %d{$paramFormat}\n}\n"; return sprintf( $reflectionFormat, $this->getDocComment() ? $this->getDocComment() . "\n" : '', $this->getName(), $this->getFileName(), $this->getStartLine(), $this->getEndLine(), count($this->getParameters()), array_reduce($this->getParameters(), function ($str, ReflectionParameter $param) { return $str . "\n " . $param; }, '') ); } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize() { parent::__construct($this->getName()); } } src/ReflectionMethod.php000064400000017413144760111300011302 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use ReflectionMethod as BaseReflectionMethod; /** * AST-based reflection for the method in a class */ class ReflectionMethod extends BaseReflectionMethod { use ReflectionFunctionLikeTrait, InternalPropertiesEmulationTrait; /** * Name of the class * * @var string */ private $className; /** * Initializes reflection instance for the method node * * @param string $className Name of the class * @param string $methodName Name of the method * @param ClassMethod $classMethodNode AST-node for method */ public function __construct($className, $methodName, ClassMethod $classMethodNode = null) { //for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too $this->className = $className; $this->functionLikeNode = $classMethodNode ?: ReflectionEngine::parseClassMethod($className, $methodName); // Let's unset original read-only properties to have a control over them via __get unset($this->name, $this->class); } /** * Emulating original behaviour of reflection */ public function __debugInfo() { return [ 'name' => $this->getClassMethodNode()->name, 'class' => $this->className ]; } /** * Returns the string representation of the Reflection method object. * * @link http://php.net/manual/en/reflectionmethod.tostring.php * * @return string */ public function __toString() { $paramFormat = ($this->getNumberOfParameters() > 0) ? "\n\n - Parameters [%d] {%s\n }" : ''; $methodParameters = $this->getParameters(); try { $prototype = $this->getPrototype(); } catch (\ReflectionException $e) { $prototype = null; } $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : ''; $paramString = ''; $identation = str_repeat(' ', 4); foreach ($methodParameters as $methodParameter) { $paramString .= "\n{$identation}" . $methodParameter; } return sprintf( "%sMethod [ %s%s%s %s method %s ] {\n @@ %s %d - %d{$paramFormat}\n}\n", $this->getDocComment() ? $this->getDocComment() . "\n" : '', $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '', $this->isConstructor() ? ', ctor' : '', $this->isDestructor() ? ', dtor' : '', $this->isFinal() ? ' final' : '', $this->isStatic() ? ' static' : '', $this->isAbstract() ? ' abstract' : '', join(' ', \Reflection::getModifierNames($this->getModifiers() & 1792)), $this->getName(), $this->getFileName(), $this->getStartLine(), $this->getEndLine(), count($methodParameters), $paramString ); } /** * {@inheritDoc} */ public function getClosure($object) { $this->initializeInternalReflection(); return parent::getClosure($object); } /** * {@inheritDoc} */ public function getDeclaringClass() { return new ReflectionClass($this->className); } /** * {@inheritDoc} */ public function getModifiers() { $modifiers = 0; if ($this->isPublic()) { $modifiers += self::IS_PUBLIC; } if ($this->isProtected()) { $modifiers += self::IS_PROTECTED; } if ($this->isPrivate()) { $modifiers += self::IS_PRIVATE; } if ($this->isAbstract()) { $modifiers += self::IS_ABSTRACT; } if ($this->isFinal()) { $modifiers += self::IS_FINAL; } if ($this->isStatic()) { $modifiers += self::IS_STATIC; } return $modifiers; } /** * {@inheritDoc} */ public function getPrototype() { $parent = $this->getDeclaringClass()->getParentClass(); if (!$parent) { throw new ReflectionException("No prototype"); } $prototypeMethod = $parent->getMethod($this->getName()); if (!$prototypeMethod) { throw new ReflectionException("No prototype"); } return $prototypeMethod; } /** * {@inheritDoc} */ public function invoke($object, $args = null) { $this->initializeInternalReflection(); return call_user_func_array('parent::invoke', func_get_args()); } /** * {@inheritDoc} */ public function invokeArgs($object, array $args) { $this->initializeInternalReflection(); return parent::invokeArgs($object, $args); } /** * {@inheritDoc} */ public function isAbstract() { return $this->getClassMethodNode()->isAbstract(); } /** * {@inheritDoc} */ public function isConstructor() { return $this->getClassMethodNode()->name == '__construct'; } /** * {@inheritDoc} */ public function isDestructor() { return $this->getClassMethodNode()->name == '__destruct'; } /** * {@inheritDoc} */ public function isFinal() { return $this->getClassMethodNode()->isFinal(); } /** * {@inheritDoc} */ public function isPrivate() { return $this->getClassMethodNode()->isPrivate(); } /** * {@inheritDoc} */ public function isProtected() { return $this->getClassMethodNode()->isProtected(); } /** * {@inheritDoc} */ public function isPublic() { return $this->getClassMethodNode()->isPublic(); } /** * {@inheritDoc} */ public function isStatic() { return $this->getClassMethodNode()->isStatic(); } /** * {@inheritDoc} */ public function setAccessible($accessible) { $this->initializeInternalReflection(); parent::setAccessible($accessible); } /** * Parses methods from the concrete class node * * @param ClassLike $classLikeNode Class-like node * @param string $fullClassName FQN of the class * * @return array|ReflectionMethod[] */ public static function collectFromClassNode(ClassLike $classLikeNode, $fullClassName) { $methods = []; foreach ($classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof ClassMethod) { $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName')); $methodName = $classLevelNode->name; $methods[$methodName] = new ReflectionMethod( $fullClassName, $methodName, $classLevelNode ); } } return $methods; } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize() { parent::__construct($this->className, $this->getName()); } /** * Returns ClassMethod node to prevent all possible type checks with instanceof * * @return ClassMethod */ private function getClassMethodNode() { return $this->functionLikeNode; } } src/ReflectionParameter.php000064400000021724144760111300012002 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Name; use PhpParser\Node\Param; use ReflectionParameter as BaseReflectionParameter; /** * AST-based reflection for method/function parameter */ class ReflectionParameter extends BaseReflectionParameter { use InternalPropertiesEmulationTrait; /** * Reflection function or method * * @var \ReflectionFunctionAbstract */ private $declaringFunction; /** * Stores the default value for node (if present) * * @var mixed */ private $defaultValue = null; /** * Whether or not default value is constant * * @var bool */ private $isDefaultValueConstant = false; /** * Name of the constant of default value * * @var string */ private $defaultValueConstantName; /** * Index of parameter in the list * * @var int */ private $parameterIndex = 0; /** * Concrete parameter node * * @var Param */ private $parameterNode; /** * Initializes a reflection for the property * * @param string|array $unusedFunctionName Name of the function/method * @param string $parameterName Name of the parameter to reflect * @param Param $parameterNode Parameter definition node * @param int $parameterIndex Index of parameter * @param \ReflectionFunctionAbstract $declaringFunction */ public function __construct( $unusedFunctionName, $parameterName, Param $parameterNode = null, $parameterIndex = 0, \ReflectionFunctionAbstract $declaringFunction = null ) { // Let's unset original read-only property to have a control over it via __get unset($this->name); $this->parameterNode = $parameterNode; $this->parameterIndex = $parameterIndex; $this->declaringFunction = $declaringFunction; if ($this->isDefaultValueAvailable()) { if ($declaringFunction instanceof \ReflectionMethod) { $context = $declaringFunction->getDeclaringClass(); } else { $context = $declaringFunction; }; $expressionSolver = new NodeExpressionResolver($context); $expressionSolver->process($this->parameterNode->default); $this->defaultValue = $expressionSolver->getValue(); $this->isDefaultValueConstant = $expressionSolver->isConstant(); $this->defaultValueConstantName = $expressionSolver->getConstantName(); } } /** * Emulating original behaviour of reflection */ public function __debugInfo() { return array( 'name' => $this->parameterNode->name, ); } /** * Returns string representation of this parameter. * * @return string */ public function __toString() { $parameterType = $this->parameterNode->type; if (is_object($parameterType)) { $parameterType = $parameterType->toString(); } $isNullableParam = !empty($parameterType) && $this->allowsNull(); $isOptional = $this->isOptional(); $defaultValue = ''; if ($isOptional) { $defaultValue = $this->getDefaultValue(); if (is_string($defaultValue) && strlen($defaultValue) > 15) { $defaultValue = substr($defaultValue, 0, 15) . '...'; } $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true)); } return sprintf( 'Parameter #%d [ %s %s%s%s%s$%s%s ]', $this->parameterIndex, ($this->isVariadic() || $isOptional) ? '' : '', $parameterType ? ltrim($parameterType, '\\') . ' ' : '', $isNullableParam ? 'or NULL ' : '', $this->isVariadic() ? '...' : '', $this->isPassedByReference() ? '&' : '', $this->getName(), $isOptional ? (' = ' . $defaultValue) : '' ); } /** * {@inheritDoc} */ public function allowsNull() { $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null; if ($hasDefaultNull) { return true; } return !isset($this->parameterNode->type); } /** * {@inheritDoc} */ public function canBePassedByValue() { return !$this->isPassedByReference(); } /** * @inheritDoc */ public function getClass() { $parameterType = $this->parameterNode->type; if ($parameterType instanceof Name) { if (!$parameterType instanceof Name\FullyQualified) { throw new ReflectionException("Can not resolve a class name for parameter"); } $className = $parameterType->toString(); $classExists = class_exists($className, false); return $classExists ? new \ReflectionClass($className) : new ReflectionClass($className); } return null; } /** * {@inheritDoc} */ public function getDeclaringClass() { if ($this->declaringFunction instanceof \ReflectionMethod) { return $this->declaringFunction->getDeclaringClass(); }; return null; } /** * {@inheritDoc} */ public function getDeclaringFunction() { return $this->declaringFunction; } /** * {@inheritDoc} */ public function getDefaultValue() { if (!$this->isDefaultValueAvailable()) { throw new ReflectionException('Internal error: Failed to retrieve the default value'); } return $this->defaultValue; } /** * {@inheritDoc} */ public function getDefaultValueConstantName() { if (!$this->isDefaultValueAvailable()) { throw new ReflectionException('Internal error: Failed to retrieve the default value'); } return $this->defaultValueConstantName; } /** * @inheritDoc */ public function getName() { return $this->parameterNode->name; } /** * {@inheritDoc} */ public function getPosition() { return $this->parameterIndex; } /** * @inheritDoc */ public function getType() { $isBuiltin = false; $allowsNull = $this->allowsNull(); $parameterType = $this->parameterNode->type; if (is_object($parameterType)) { $parameterType = $parameterType->toString(); } elseif (is_string($parameterType)) { $isBuiltin = true; } return new ReflectionType($parameterType, $allowsNull, $isBuiltin); } /** * @inheritDoc */ public function hasType() { $hasType = isset($this->parameterNode->type); return $hasType; } /** * @inheritDoc */ public function isArray() { return 'array' === $this->parameterNode->type; } /** * @inheritDoc */ public function isCallable() { return 'callable' === $this->parameterNode->type; } /** * @inheritDoc */ public function isDefaultValueAvailable() { return isset($this->parameterNode->default); } /** * {@inheritDoc} */ public function isDefaultValueConstant() { return $this->isDefaultValueConstant; } /** * {@inheritDoc} */ public function isOptional() { return $this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues(); } /** * @inheritDoc */ public function isPassedByReference() { return (bool) $this->parameterNode->byRef; } /** * @inheritDoc */ public function isVariadic() { return (bool) $this->parameterNode->variadic; } /** * Returns if all following parameters have a default value definition. * * @return bool * @throws ReflectionException If could not fetch declaring function reflection */ protected function haveSiblingsDefalutValues() { $function = $this->getDeclaringFunction(); if (null === $function) { throw new ReflectionException('Could not get the declaring function reflection.'); } /** @var \ReflectionParameter[] $remainingParameters */ $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1); foreach ($remainingParameters as $reflectionParameter) { if (!$reflectionParameter->isDefaultValueAvailable()) { return false; } } return true; } } src/ReflectionProperty.php000064400000014447144760111300011712 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\InitializationTrait; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\PropertyProperty; use ReflectionProperty as BaseReflectionProperty; /** * AST-based reflection for class property */ class ReflectionProperty extends BaseReflectionProperty { use InitializationTrait, InternalPropertiesEmulationTrait; /** * Type of property node * * @var Property */ private $propertyTypeNode; /** * Concrete property node * * @var PropertyProperty */ private $propertyNode; /** * Name of the class * * @var string */ private $className; /** * Initializes a reflection for the property * * @param string $className Name of the class with properties * @param string $propertyName Name of the property to reflect * @param Property $propertyType Property type definition node * @param PropertyProperty $propertyNode Concrete property definition (value, name) */ public function __construct( $className, $propertyName, Property $propertyType = null, PropertyProperty $propertyNode = null ) { $this->className = $className; if (!$propertyType || !$propertyNode) { list ($propertyType, $propertyNode) = ReflectionEngine::parseClassProperty($className, $propertyName); } $this->propertyTypeNode = $propertyType; $this->propertyNode = $propertyNode; // Let's unset original read-only properties to have a control over them via __get unset($this->name, $this->class); } /** * Emulating original behaviour of reflection */ public function __debugInfo() { return array( 'name' => isset($this->propertyNode) ? $this->propertyNode->name : 'unknown', 'class' => $this->className ); } /** * Return string representation of this little old property. * * @return string */ public function __toString() { return sprintf( "Property [%s %s $%s ]\n", $this->isStatic() ? '' : ($this->isDefault() ? ' ' : ' '), join(' ', \Reflection::getModifierNames($this->getModifiers())), $this->getName() ); } /** * {@inheritDoc} */ public function getDeclaringClass() { return new ReflectionClass($this->className); } /** * @inheritDoc */ public function getDocComment() { $docBlock = $this->propertyTypeNode->getDocComment(); return $docBlock ? $docBlock->getText() : false; } /** * {@inheritDoc} */ public function getModifiers() { $modifiers = 0; if ($this->isPublic()) { $modifiers += self::IS_PUBLIC; } if ($this->isProtected()) { $modifiers += self::IS_PROTECTED; } if ($this->isPrivate()) { $modifiers += self::IS_PRIVATE; } if ($this->isStatic()) { $modifiers += self::IS_STATIC; } return $modifiers; } /** * @inheritDoc */ public function getName() { return $this->propertyNode->name; } /** * @inheritDoc */ public function getValue($object = null) { if (!isset($object)) { $solver = new NodeExpressionResolver($this->getDeclaringClass()); if (!isset($this->propertyNode->default)) { return null; } $solver->process($this->propertyNode->default); return $solver->getValue(); } $this->initializeInternalReflection(); return parent::getValue($object); } /** * @inheritDoc */ public function isDefault() { return isset($this->propertyNode->default); } /** * {@inheritDoc} */ public function isPrivate() { return $this->propertyTypeNode->isPrivate(); } /** * {@inheritDoc} */ public function isProtected() { return $this->propertyTypeNode->isProtected(); } /** * {@inheritDoc} */ public function isPublic() { return $this->propertyTypeNode->isPublic(); } /** * {@inheritDoc} */ public function isStatic() { return $this->propertyTypeNode->isStatic(); } /** * {@inheritDoc} */ public function setAccessible($accessible) { $this->initializeInternalReflection(); parent::setAccessible($accessible); } /** * @inheritDoc */ public function setValue($object, $value = null) { $this->initializeInternalReflection(); parent::setValue($object, $value); } /** * Parses properties from the concrete class node * * @param ClassLike $classLikeNode Class-like node * @param string $fullClassName FQN of the class * * @return array|ReflectionProperty[] */ public static function collectFromClassNode(ClassLike $classLikeNode, $fullClassName) { $properties = []; foreach ($classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof Property) { foreach ($classLevelNode->props as $classPropertyNode) { $propertyName = $classPropertyNode->name; $properties[$propertyName] = new static( $fullClassName, $propertyName, $classLevelNode, $classPropertyNode ); } } } return $properties; } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize() { parent::__construct($this->className, $this->getName()); } } src/ReflectionType.php000064400000002326144760111300011000 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use ReflectionType as BaseReflectionType; /** * ReflectionType implementation */ class ReflectionType extends BaseReflectionType { /** * If type allows null or not * * @var boolean */ private $allowsNull; /** * Is type built-in or not * * @var */ private $isBuiltin; /** * @var string Type name */ private $type; /** * Initializes reflection data */ public function __construct($type, $allowsNull, $isBuiltin) { $this->type = $type; $this->allowsNull = $allowsNull; $this->isBuiltin = $isBuiltin; } /** * @inheritDoc */ public function allowsNull() { return $this->allowsNull; } /** * @inheritDoc */ public function isBuiltin() { return $this->isBuiltin; } /** * @inheritDoc */ public function __toString() { return $this->type; } } src/Traits/InitializationTrait.php000064400000002043144760111300013301 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; trait InitializationTrait { /** * Is internal reflection is initialized or not * * @var boolean */ private $isInitialized = false; /** * Initializes internal reflection for calling misc runtime methods */ public function initializeInternalReflection() { if (!$this->isInitialized) { $this->__initialize(); $this->isInitialized = true; } } /** * Returns the status of initialization status for internal object * * @return bool */ public function isInitialized() { return $this->isInitialized; } /** * Implementation of internal reflection initialization * * @return void */ abstract protected function __initialize(); } src/Traits/InternalPropertiesEmulationTrait.php000064400000002060144760111300016020 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; /** * Class for emulating internal properties behaviour */ trait InternalPropertiesEmulationTrait { /** * Magic method that should be defined to provide an info about internal properties * * @return array */ abstract public function __debugInfo(); /** * Magic implementation of properties * * @param string $propertyName Name of the property to return * * @return null|mixed */ public function __get($propertyName) { $internalInfo = $this->__debugInfo(); if (!isset($internalInfo[$propertyName])) { $className = get_class($this); trigger_error("Undefined property {$className}::\${$propertyName}"); return null; } return $internalInfo[$propertyName]; } } src/Traits/ReflectionClassLikeTrait.php000064400000063430144760111300014206 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; use Go\ParserReflection\ReflectionClass; use Go\ParserReflection\ReflectionException; use Go\ParserReflection\ReflectionMethod; use Go\ParserReflection\ReflectionProperty; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; use PhpParser\Node\Stmt\TraitUseAdaptation; /** * General class-like reflection */ trait ReflectionClassLikeTrait { use InitializationTrait; /** * @var ClassLike */ protected $classLikeNode; /** * Short name of the class, without namespace * * @var string */ protected $className; /** * List of all constants from the class * * @var array */ protected $constants; /** * Interfaces, empty array or null if not initialized yet * * @var \ReflectionClass[]|array|null */ protected $interfaceClasses; /** * List of traits, empty array or null if not initialized yet * * @var \ReflectionClass[]|array|null */ protected $traits; /** * Additional list of trait adaptations * * @var TraitUseAdaptation[]|array */ protected $traitAdaptations; /** * @var array|ReflectionMethod[] */ protected $methods; /** * Namespace name * * @var string */ protected $namespaceName = ''; /** * Parent class, or false if not present, null if uninitialized yet * * @var \ReflectionClass|false|null */ protected $parentClass; /** * @var array|ReflectionProperty[] */ protected $properties; /** * Returns the string representation of the ReflectionClass object. * * @return string */ public function __toString() { $isObject = $this instanceof \ReflectionObject; $staticProperties = $staticMethods = $defaultProperties = $dynamicProperties = $methods = []; $format = "%s [ %sclass %s%s%s ] {\n"; $format .= " @@ %s %d-%d\n\n"; $format .= " - Constants [%d] {%s\n }\n\n"; $format .= " - Static properties [%d] {%s\n }\n\n"; $format .= " - Static methods [%d] {%s\n }\n\n"; $format .= " - Properties [%d] {%s\n }\n\n"; $format .= ($isObject ? " - Dynamic properties [%d] {%s\n }\n\n" : '%s%s'); $format .= " - Methods [%d] {%s\n }\n"; $format .= "}\n"; foreach ($this->getProperties() as $property) { if ($property->isStatic()) { $staticProperties[] = $property; } elseif ($property->isDefault()) { $defaultProperties[] = $property; } else { $dynamicProperties[] = $property; } } foreach ($this->getMethods() as $method) { if ($method->isStatic()) { $staticMethods[] = $method; } else { $methods[] = $method; } } $buildString = function (array $items, $indentLevel = 4) { if (!count($items)) { return ''; } $indent = "\n" . str_repeat(' ', $indentLevel); return $indent . implode($indent, explode("\n", implode("\n", $items))); }; $buildConstants = function (array $items, $indentLevel = 4) { $str = ''; foreach ($items as $name => $value) { $str .= "\n" . str_repeat(' ', $indentLevel); $str .= sprintf( 'Constant [ %s %s ] { %s }', gettype($value), $name, $value ); } return $str; }; $interfaceNames = $this->getInterfaceNames(); $parentClass = $this->getParentClass(); $modifiers = ''; if ($this->isAbstract()) { $modifiers = 'abstract '; } elseif ($this->isFinal()) { $modifiers = 'final '; }; $string = sprintf( $format, ($isObject ? 'Object of class' : 'Class'), $modifiers, $this->getName(), false !== $parentClass ? (' extends ' . $parentClass->getName()) : '', $interfaceNames ? (' implements ' . implode(', ', $interfaceNames)) : '', $this->getFileName(), $this->getStartLine(), $this->getEndLine(), count($this->getConstants()), $buildConstants($this->getConstants()), count($staticProperties), $buildString($staticProperties), count($staticMethods), $buildString($staticMethods), count($defaultProperties), $buildString($defaultProperties), $isObject ? count($dynamicProperties) : '', $isObject ? $buildString($dynamicProperties) : '', count($methods), $buildString($methods) ); return $string; } /** * {@inheritDoc} */ public function getConstant($name) { if ($this->hasConstant($name)) { return $this->constants[$name]; } return false; } /** * {@inheritDoc} */ public function getConstants() { if (!isset($this->constants)) { $directConstants = $this->findConstants(); $parentConstants = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance) { $result += $instance->getConstants(); }); $constants = $directConstants + $parentConstants; $this->constants = $constants; } return $this->constants; } /** * {@inheritDoc} */ public function getConstructor() { $constructor = $this->getMethod('__construct'); if (!$constructor) { return null; } return $constructor; } /** * Gets default properties from a class (including inherited properties). * * @link http://php.net/manual/en/reflectionclass.getdefaultproperties.php * * @return array An array of default properties, with the key being the name of the property and the value being * the default value of the property or NULL if the property doesn't have a default value */ public function getDefaultProperties() { $defaultValues = []; $properties = $this->getProperties(); $staticOrder = [true, false]; foreach ($staticOrder as $shouldBeStatic) { foreach ($properties as $property) { $isStaticProperty = $property->isStatic(); if ($shouldBeStatic !== $isStaticProperty) { continue; } $propertyName = $property->getName(); $isInternalReflection = get_class($property) == \ReflectionProperty::class; if (!$isInternalReflection || $isStaticProperty) { $defaultValues[$propertyName] = $property->getValue(); } elseif (!$isStaticProperty) { // Internal reflection and dynamic property $classProperties = $property->getDeclaringClass()->getDefaultProperties(); $defaultValues[$propertyName] = $classProperties[$propertyName]; } } } return $defaultValues; } /** * {@inheritDoc} */ public function getDocComment() { $docComment = $this->classLikeNode->getDocComment(); return $docComment ? $docComment->getText() : false; } public function getEndLine() { return $this->classLikeNode->getAttribute('endLine'); } public function getExtension() { return null; } public function getExtensionName() { return false; } public function getFileName() { return $this->classLikeNode->getAttribute('fileName'); } /** * {@inheritDoc} */ public function getInterfaceNames() { return array_keys($this->getInterfaces()); } /** * {@inheritDoc} */ public function getInterfaces() { if (!isset($this->interfaceClasses)) { $this->interfaceClasses = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance) { if ($instance->isInterface()) { $result[$instance->name] = $instance; } $result += $instance->getInterfaces(); }); } return $this->interfaceClasses; } /** * {@inheritdoc} */ public function getMethod($name) { $methods = $this->getMethods(); foreach ($methods as $method) { if ($method->getName() == $name) { return $method; } } return false; } /** * Returns list of reflection methods * * @param null|integer $filter Optional filter * * @return array|\ReflectionMethod[] */ public function getMethods($filter = null) { if (!isset($this->methods)) { $directMethods = ReflectionMethod::collectFromClassNode($this->classLikeNode, $this->getName()); $parentMethods = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) { $reflectionMethods = []; foreach ($instance->getMethods() as $reflectionMethod) { if (!$isParent || !$reflectionMethod->isPrivate()) { $reflectionMethods[$reflectionMethod->name] = $reflectionMethod; } } $result += $reflectionMethods; }); $methods = $directMethods + $parentMethods; $this->methods = $methods; } if (!isset($filter)) { return array_values($this->methods); } $methods = []; foreach ($this->methods as $method) { if (!($filter & $method->getModifiers())) { continue; } $methods[] = $method; } return $methods; } /** * Returns a bitfield of the access modifiers for this class. * * @link http://php.net/manual/en/reflectionclass.getmodifiers.php * * NB: this method is not fully compatible with original value because of hidden internal constants * * @return int */ public function getModifiers() { $modifiers = 0; if ($this->isFinal()) { $modifiers += \ReflectionClass::IS_FINAL; } if (PHP_VERSION_ID < 70000 && $this->isTrait()) { $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT; } if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) { $modifiers += \ReflectionClass::IS_EXPLICIT_ABSTRACT; } if ($this->isInterface()) { $abstractMethods = $this->getMethods(); } else { $abstractMethods = $this->getMethods(\ReflectionMethod::IS_ABSTRACT); } if (!empty($abstractMethods)) { $modifiers += \ReflectionClass::IS_IMPLICIT_ABSTRACT; } return $modifiers; } /** * {@inheritDoc} */ public function getName() { $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : ''; return $namespaceName . $this->getShortName(); } /** * {@inheritDoc} */ public function getNamespaceName() { return $this->namespaceName; } /** * {@inheritDoc} */ public function getParentClass() { if (!isset($this->parentClass)) { static $extendsField = 'extends'; $parentClass = false; $hasExtends = in_array($extendsField, $this->classLikeNode->getSubNodeNames()); $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null; if ($extendsNode instanceof FullyQualified) { $extendsName = $extendsNode->toString(); $parentClass = class_exists($extendsName, false) ? new parent($extendsName) : new static($extendsName); } $this->parentClass = $parentClass; } return $this->parentClass; } /** * Retrieves reflected properties. * * @param int $filter The optional filter, for filtering desired property types. * It's configured using the ReflectionProperty constants, and defaults to all property types. * * @return array|\Go\ParserReflection\ReflectionProperty[] */ public function getProperties($filter = null) { if (!isset($this->properties)) { $directProperties = ReflectionProperty::collectFromClassNode($this->classLikeNode, $this->getName()); $parentProperties = $this->recursiveCollect(function (array &$result, \ReflectionClass $instance, $isParent) { $reflectionProperties = []; foreach ($instance->getProperties() as $reflectionProperty) { if (!$isParent || !$reflectionProperty->isPrivate()) { $reflectionProperties[$reflectionProperty->name] = $reflectionProperty; } } $result += $reflectionProperties; }); $properties = $directProperties + $parentProperties; $this->properties = $properties; } // Without filter we can just return the full list if (!isset($filter)) { return array_values($this->properties); } $properties = []; foreach ($this->properties as $property) { if (!($filter & $property->getModifiers())) { continue; } $properties[] = $property; } return $properties; } /** * {@inheritdoc} */ public function getProperty($name) { $properties = $this->getProperties(); foreach ($properties as $property) { if ($property->getName() == $name) { return $property; } } return false; } /** * {@inheritDoc} */ public function getShortName() { return $this->className; } public function getStartLine() { return $this->classLikeNode->getAttribute('startLine'); } /** * Returns an array of trait aliases * * @link http://php.net/manual/en/reflectionclass.gettraitaliases.php * * @return array|null an array with new method names in keys and original names (in the format "TraitName::original") in * values. */ public function getTraitAliases() { $aliases = []; $traits = $this->getTraits(); foreach ($this->traitAdaptations as $adaptation) { if ($adaptation instanceof TraitUseAdaptation\Alias) { $methodName = $adaptation->method; $traitName = null; foreach ($traits as $trait) { if ($trait->hasMethod($methodName)) { $traitName = $trait->getName(); break; } } $aliases[$adaptation->newName] = $traitName . '::'. $methodName; } } return $aliases; } /** * Returns an array of names of traits used by this class * * @link http://php.net/manual/en/reflectionclass.gettraitnames.php * * @return array */ public function getTraitNames() { return array_keys($this->getTraits()); } /** * Returns an array of traits used by this class * * @link http://php.net/manual/en/reflectionclass.gettraits.php * * @return array|\ReflectionClass[] */ public function getTraits() { if (!isset($this->traits)) { $traitAdaptations = []; $this->traits = ReflectionClass::collectTraitsFromClassNode($this->classLikeNode, $traitAdaptations); $this->traitAdaptations = $traitAdaptations; } return $this->traits; } /** * {@inheritDoc} */ public function hasConstant($name) { $constants = $this->getConstants(); $hasConstant = isset($constants[$name]) || array_key_exists($constants, $name); return $hasConstant; } /** * {@inheritdoc} */ public function hasMethod($name) { $methods = $this->getMethods(); foreach ($methods as $method) { if ($method->getName() == $name) { return true; } } return false; } /** * {@inheritdoc} */ public function hasProperty($name) { $properties = $this->getProperties(); foreach ($properties as $property) { if ($property->getName() == $name) { return true; } } return false; } /** * {@inheritDoc} */ public function implementsInterface($interfaceName) { $allInterfaces = $this->getInterfaces(); return isset($allInterfaces[$interfaceName]); } /** * {@inheritDoc} */ public function inNamespace() { return !empty($this->namespaceName); } /** * {@inheritDoc} */ public function isAbstract() { if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) { return true; } elseif ($this->isInterface() && !empty($this->getMethods())) { return true; } elseif ($this->isTrait()) { return PHP_VERSION_ID < 70000 ? true : false; } return false; } /** * Currently, anonymous classes aren't supported for parsed reflection */ public function isAnonymous() { return false; } /** * {@inheritDoc} */ public function isCloneable() { if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) { return false; } if ($this->hasMethod('__clone')) { return $this->getMethod('__clone')->isPublic(); } return true; } /** * {@inheritDoc} */ public function isFinal() { $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal(); return $isFinal; } /** * {@inheritDoc} */ public function isInstance($object) { if (!is_object($object)) { throw new \RuntimeException(sprintf('Parameter must be an object, "%s" provided.', gettype($object))); } $className = $this->getName(); return $className === get_class($object) || is_subclass_of($object, $className); } /** * {@inheritDoc} */ public function isInstantiable() { if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) { return false; } if (null === ($constructor = $this->getConstructor())) { return true; } return $constructor->isPublic(); } /** * {@inheritDoc} */ public function isInterface() { return ($this->classLikeNode instanceof Interface_); } /** * {@inheritDoc} */ public function isInternal() { // never can be an internal method return false; } /** * {@inheritDoc} */ public function isIterateable() { return $this->implementsInterface('Traversable'); } /** * {@inheritDoc} */ public function isSubclassOf($class) { if (is_object($class)) { if ($class instanceof ReflectionClass) { $class = $class->name; } else { $class = get_class($class); } } if (!$this->classLikeNode instanceof Class_) { return false; } else { $extends = $this->classLikeNode->extends; if ($extends && $extends->toString() == $class) { return true; } } $parent = $this->getParentClass(); return false === $parent ? false : $parent->isSubclassOf($class); } /** * {@inheritDoc} */ public function isTrait() { return ($this->classLikeNode instanceof Trait_); } /** * {@inheritDoc} */ public function isUserDefined() { // always defined by user, because we parse the source code return true; } /** * Gets static properties * * @link http://php.net/manual/en/reflectionclass.getstaticproperties.php * * @return array */ public function getStaticProperties() { // In runtime static properties can be changed in any time if ($this->isInitialized()) { return forward_static_call('parent::getStaticProperties'); } $properties = []; $reflectionProperties = $this->getProperties(ReflectionProperty::IS_STATIC); foreach ($reflectionProperties as $reflectionProperty) { if (!$reflectionProperty instanceof ReflectionProperty) { if (!$reflectionProperty->isPublic()) { $reflectionProperty->setAccessible(true); } } $properties[$reflectionProperty->getName()] = $reflectionProperty->getValue(); } return $properties; } /** * Gets static property value * * @param string $name The name of the static property for which to return a value. * @param mixed $default A default value to return in case the class does not declare * a static property with the given name * * @return mixed * @throws ReflectionException If there is no such property and no default value was given */ public function getStaticPropertyValue($name, $default = null) { $properties = $this->getStaticProperties(); $propertyExists = array_key_exists($name, $properties); if (!$propertyExists && func_num_args() === 1) { throw new ReflectionException("Static property does not exist and no default value is given"); } return $propertyExists ? $properties[$name] : $default; } /** * Creates a new class instance from given arguments. * * @link http://php.net/manual/en/reflectionclass.newinstance.php * @param mixed $args Accepts a variable number of arguments which are passed to the class constructor * * @return object */ public function newInstance($args = null) { $this->initializeInternalReflection(); return call_user_func_array('parent::newInstance', func_get_args()); } /** * Creates a new class instance from given arguments. * * @link http://php.net/manual/en/reflectionclass.newinstanceargs.php * * @param array $args The parameters to be passed to the class constructor as an array. * * @return object */ public function newInstanceArgs(array $args = []) { $function = __FUNCTION__; $this->initializeInternalReflection(); return parent::$function($args); } /** * Creates a new class instance without invoking the constructor. * * @link http://php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php * * @return object */ public function newInstanceWithoutConstructor($args = null) { $function = __FUNCTION__; $this->initializeInternalReflection(); return parent::$function($args); } /** * Sets static property value * * @link http://php.net/manual/en/reflectionclass.setstaticpropertyvalue.php * * @param string $name Property name * @param mixed $value New property value */ public function setStaticPropertyValue($name, $value) { $this->initializeInternalReflection(); forward_static_call('parent::setStaticPropertyValue', $name, $value); } private function recursiveCollect(\Closure $collector) { $result = []; $isParent = true; $traits = $this->getTraits(); foreach ($traits as $trait) { $collector($result, $trait, !$isParent); } $parentClass = $this->getParentClass(); if ($parentClass) { $collector($result, $parentClass, $isParent); } $interfaces = ReflectionClass::collectInterfacesFromClassNode($this->classLikeNode); foreach ($interfaces as $interface) { $collector($result, $interface, $isParent); } return $result; } /** * Returns list of constants from the class * * @return array */ private function findConstants() { $constants = array(); $expressionSolver = new NodeExpressionResolver($this); // constants can be only top-level nodes in the class, so we can scan them directly foreach ($this->classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof ClassConst) { $nodeConstants = $classLevelNode->consts; if (!empty($nodeConstants)) { foreach ($nodeConstants as $nodeConstant) { $expressionSolver->process($nodeConstant->value); $constants[$nodeConstant->name] = $expressionSolver->getValue(); } } } } return $constants; } } src/Traits/ReflectionFunctionLikeTrait.php000064400000016456144760111300014734 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; use Go\ParserReflection\NodeVisitor\GeneratorDetector; use Go\ParserReflection\NodeVisitor\StaticVariablesCollector; use Go\ParserReflection\ReflectionFileNamespace; use Go\ParserReflection\ReflectionParameter; use Go\ParserReflection\ReflectionType; use PhpParser\Node\Expr\Closure; use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\NodeTraverser; /** * General trait for all function-like reflections */ trait ReflectionFunctionLikeTrait { use InitializationTrait; /** * @var FunctionLike */ protected $functionLikeNode; /** * Namespace name * * @var string */ protected $namespaceName = ''; /** * @var array|ReflectionParameter[] */ protected $parameters; /** * {@inheritDoc} */ public function getClosureScopeClass() { $this->initializeInternalReflection(); return forward_static_call('parent::getClosureScopeClass'); } /** * {@inheritDoc} */ public function getClosureThis() { $this->initializeInternalReflection(); return forward_static_call('parent::getClosureThis'); } public function getDocComment() { $docComment = $this->functionLikeNode->getDocComment(); return $docComment ? $docComment->getText() : false; } public function getEndLine() { return $this->functionLikeNode->getAttribute('endLine'); } public function getExtension() { return null; } public function getExtensionName() { return false; } public function getFileName() { return $this->functionLikeNode->getAttribute('fileName'); } /** * {@inheritDoc} */ public function getName() { if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { $functionName = $this->functionLikeNode->name; return $this->namespaceName ? $this->namespaceName . '\\' . $functionName : $functionName; } return false; } /** * {@inheritDoc} */ public function getNamespaceName() { return $this->namespaceName; } /** * Get the number of parameters that a function defines, both optional and required. * * @link http://php.net/manual/en/reflectionfunctionabstract.getnumberofparameters.php * * @return int */ public function getNumberOfParameters() { return count($this->functionLikeNode->getParams()); } /** * Get the number of required parameters that a function defines. * * @link http://php.net/manual/en/reflectionfunctionabstract.getnumberofrequiredparameters.php * * @return int */ public function getNumberOfRequiredParameters() { $requiredParameters = 0; foreach ($this->getParameters() as $parameter) { if (!$parameter->isOptional()) { $requiredParameters++; } } return $requiredParameters; } /** * {@inheritDoc} */ public function getParameters() { if (!isset($this->parameters)) { $parameters = []; foreach ($this->functionLikeNode->getParams() as $parameterIndex => $parameterNode) { $reflectionParameter = new ReflectionParameter( $this->getName(), $parameterNode->name, $parameterNode, $parameterIndex, $this ); $parameters[] = $reflectionParameter; } $this->parameters = $parameters; } return $this->parameters; } /** * Gets the specified return type of a function * * @return \ReflectionType * * @link http://php.net/manual/en/reflectionfunctionabstract.getreturntype.php */ public function getReturnType() { $isBuiltin = false; $returnType = $this->functionLikeNode->getReturnType(); if (is_object($returnType)) { $returnType = $returnType->toString(); } elseif (is_string($returnType)) { $isBuiltin = true; } return new ReflectionType($returnType, false, $isBuiltin); } /** * {@inheritDoc} */ public function getShortName() { if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { return $this->functionLikeNode->name; } return false; } public function getStartLine() { return $this->functionLikeNode->getAttribute('startLine'); } /** * {@inheritDoc} */ public function getStaticVariables() { $nodeTraverser = new NodeTraverser(false); $variablesCollector = new StaticVariablesCollector($this); $nodeTraverser->addVisitor($variablesCollector); /* @see https://github.com/nikic/PHP-Parser/issues/235 */ $nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: array()); return $variablesCollector->getStaticVariables(); } /** * Checks if the function has a specified return type * * @return bool * * @link http://php.net/manual/en/reflectionfunctionabstract.hasreturntype.php */ public function hasReturnType() { $returnType = $this->functionLikeNode->getReturnType(); return isset($returnType); } /** * {@inheritDoc} */ public function inNamespace() { return !empty($this->namespaceName); } /** * {@inheritDoc} */ public function isClosure() { return $this->functionLikeNode instanceof Closure; } /** * {@inheritDoc} */ public function isDeprecated() { // userland method/function/closure can not be deprecated return false; } /** * {@inheritDoc} */ public function isGenerator() { $nodeTraverser = new NodeTraverser(false); $nodeDetector = new GeneratorDetector(); $nodeTraverser->addVisitor($nodeDetector); /* @see https://github.com/nikic/PHP-Parser/issues/235 */ $nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: array()); return $nodeDetector->isGenerator(); } /** * {@inheritDoc} */ public function isInternal() { // never can be an internal method return false; } /** * {@inheritDoc} */ public function isUserDefined() { // always defined by user, because we parse the source code return true; } /** * {@inheritDoc} */ public function isVariadic() { foreach ($this->getParameters() as $parameter) { if ($parameter->isVariadic()) { return true; } } return false; } /** * {@inheritDoc} */ public function returnsReference() { return $this->functionLikeNode->returnsByRef(); } } src/ValueResolver/NodeExpressionResolver.php000064400000021134144760111300015327 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\ValueResolver; use Go\ParserReflection\ReflectionClass; use Go\ParserReflection\ReflectionException; use Go\ParserReflection\ReflectionFileNamespace; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Scalar; use PhpParser\Node\Scalar\MagicConst; /** * Tries to resolve expression into value */ class NodeExpressionResolver { /** * List of exception for constant fetch * * @var array */ private static $notConstants = [ 'true' => true, 'false' => true, 'null' => true, ]; /** * Name of the constant (if present) * * @var null|string */ private $constantName = null; /** * Current reflection context for parsing * * @var mixed */ private $context; /** * Flag if expression is constant * * @var bool */ private $isConstant = false; /** * Node resolving level, 1 = top-level * * @var int */ private $nodeLevel = 0; /** * @var mixed Value of expression/constant */ private $value; public function __construct($context) { $this->context = $context; } public function getConstantName() { return $this->constantName; } public function getValue() { return $this->value; } public function isConstant() { return $this->isConstant; } /** * {@inheritDoc} */ public function process(Node $node) { $this->nodeLevel = 0; $this->isConstant = false; $this->constantName = null; $this->value = $this->resolve($node); } /** * Resolves node into valid value * * @param Node $node * * @return mixed */ protected function resolve(Node $node) { $value = null; try { ++$this->nodeLevel; $nodeType = $node->getType(); $methodName = 'resolve' . str_replace('_', '', $nodeType); if (method_exists($this, $methodName)) { $value = $this->$methodName($node); } } finally { --$this->nodeLevel; } return $value; } protected function resolveScalarDNumber(Scalar\DNumber $node) { return $node->value; } protected function resolveScalarLNumber(Scalar\LNumber $node) { return $node->value; } protected function resolveScalarString(Scalar\String_ $node) { return $node->value; } protected function resolveScalarMagicConstMethod() { if ($this->context instanceof \ReflectionMethod) { $fullName = $this->context->getDeclaringClass()->getName() . '::' . $this->context->getShortName(); return $fullName; } return ''; } protected function resolveScalarMagicConstFunction() { if ($this->context instanceof \ReflectionFunctionAbstract) { return $this->context->getName(); } return ''; } protected function resolveScalarMagicConstNamespace() { if (method_exists($this->context, 'getNamespaceName')) { return $this->context->getNamespaceName(); } if ($this->context instanceof ReflectionFileNamespace) { return $this->context->getName(); } return ''; } protected function resolveScalarMagicConstClass() { if ($this->context instanceof \ReflectionClass) { return $this->context->getName(); } if (method_exists($this->context, 'getDeclaringClass')) { $declaringClass = $this->context->getDeclaringClass(); if ($declaringClass instanceof \ReflectionClass) { return $declaringClass->getName(); } } return ''; } protected function resolveScalarMagicConstDir() { if (method_exists($this->context, 'getFileName')) { return dirname($this->context->getFileName()); } return ''; } protected function resolveScalarMagicConstFile() { if (method_exists($this->context, 'getFileName')) { return $this->context->getFileName(); } return ''; } protected function resolveScalarMagicConstLine(MagicConst\Line $node) { return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0; } protected function resolveScalarMagicConstTrait() { if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) { return $this->context->getName(); } return ''; } protected function resolveExprConstFetch(Expr\ConstFetch $node) { $constantValue = null; $isResolved = false; /** @var ReflectionFileNamespace|null $fileNamespace */ $fileNamespace = null; $isFQNConstant = $node->name instanceof Node\Name\FullyQualified; $constantName = $node->name->toString(); if (!$isFQNConstant) { if (method_exists($this->context, 'getFileName')) { $fileName = $this->context->getFileName(); $namespaceName = $this->context->getNamespaceName(); $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); if ($fileNamespace->hasConstant($constantName)) { $constantValue = $fileNamespace->getConstant($constantName); $constantName = $fileNamespace->getName() . '\\' . $constantName; $isResolved = true; } } } if (!$isResolved && defined($constantName)) { $constantValue = constant($constantName); } if ($this->nodeLevel === 1 && !isset(self::$notConstants[$constantName])) { $this->isConstant = true; $this->constantName = $constantName; } return $constantValue; } protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node) { $refClass = $this->fetchReflectionClass($node->class); $constantName = $node->name; // special handling of ::class constants if ('class' === $constantName) { return $refClass->getName(); } return $refClass->getConstant($constantName); } protected function resolveExprArray(Expr\Array_ $node) { $result = []; foreach ($node->items as $itemIndex => $arrayItem) { $itemValue = $this->resolve($arrayItem->value); $itemKey = isset($arrayItem->key) ? $this->resolve($arrayItem->key) : $itemIndex; $result[$itemKey] = $itemValue; } return $result; } /** * Utility method to fetch reflection class instance by name * * Supports: * 'self' keyword * 'parent' keyword * not-FQN class names * * @param Node\Name $node Class name node * * @return bool|\ReflectionClass * * @throws ReflectionException */ private function fetchReflectionClass(Node\Name $node) { $className = $node->toString(); $isFQNClass = $node instanceof Node\Name\FullyQualified; if ($isFQNClass) { return new ReflectionClass($className); } if ('self' === $className) { if ($this->context instanceof \ReflectionClass) { return $this->context; } elseif (method_exists($this->context, 'getDeclaringClass')) { return $this->context->getDeclaringClass(); } } if ('parent' === $className) { if ($this->context instanceof \ReflectionClass) { return $this->context->getParentClass(); } elseif (method_exists($this->context, 'getDeclaringClass')) { return $this->context->getDeclaringClass()->getParentClass(); } } if (method_exists($this->context, 'getFileName')) { /** @var ReflectionFileNamespace|null $fileNamespace */ $fileName = $this->context->getFileName(); $namespaceName = $this->context->getNamespaceName(); $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); return $fileNamespace->getClass($className); } throw new ReflectionException("Can not resolve class $className"); } } src/bootstrap.php000064400000000731144760111300010057 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ use Go\ParserReflection\Locator\ComposerLocator; use Go\ParserReflection\ReflectionEngine; /** * This file is used for automatic configuration of Go\ParserReflection\ReflectionEngine class */ ReflectionEngine::init(new ComposerLocator()); tests/ReflectionClassTest.php000064400000030711144760111300012336 0ustar00getFileNamespace('Go\ParserReflection\Stub'); $this->parsedRefFileNamespace = $parsedFileNamespace; include_once $fileName; } /** * Tests that names are correct for reflection data * * @dataProvider listOfClasses55 * * @param string $className Class name to test */ public function testGeneralInfoGetters($className) { $parsedRefClass = $this->parsedRefFileNamespace->getClass($className); $this->performGeneralMethodComparison($parsedRefClass); } /** * Tests getModifier() method * NB: value is masked because there are many internal constants that aren't exported in the userland * * @dataProvider listOfClasses55 * * @param string $className Class name to test */ public function testGetModifiers($className) { $mask = \ReflectionClass::IS_EXPLICIT_ABSTRACT + \ReflectionClass::IS_FINAL + \ReflectionClass::IS_IMPLICIT_ABSTRACT; $parsedRefClass = $this->parsedRefFileNamespace->getClass($className); $originalRefClass = new \ReflectionClass($className); $parsedModifiers = $parsedRefClass->getModifiers() & $mask; $originalModifiers = $originalRefClass->getModifiers() & $mask; $this->assertEquals($originalModifiers, $parsedModifiers); } /** * Tests getMethods() returns correct number of methods for the class * * @dataProvider listOfClasses55 * * @param string $className Class name to test */ public function testGetMethods($className) { $parsedRefClass = $this->parsedRefFileNamespace->getClass($className); $originalRefClass = new \ReflectionClass($className); $parsedMethods = $parsedRefClass->getMethods(); $originalMethods = $originalRefClass->getMethods(); if ($parsedRefClass->getTraitAliases()) { $this->markTestIncomplete("Adoptation methods for traits are not supported yet"); } $this->assertCount(count($originalMethods), $parsedMethods); } /** * Tests getProperties() returns correct number of properties for the class * * @dataProvider listOfClasses55 * * @param string $className Class name to test */ public function testGetProperties($className) { $parsedRefClass = $this->parsedRefFileNamespace->getClass($className); $originalRefClass = new \ReflectionClass($className); $parsedProperties = $parsedRefClass->getProperties(); $originalProperties = $originalRefClass->getProperties(); $this->assertCount(count($originalProperties), $parsedProperties); } /** * Data provider with list of all class names to test for PHP5.5 and upper * * @return array */ public function listOfClasses55() { $classNames = [ ExplicitAbstractClass::class => [ExplicitAbstractClass::class], ImplicitAbstractClass::class => [ImplicitAbstractClass::class], FinalClass::class => [FinalClass::class], ClassWithMethodsAndProperties::class => [ClassWithMethodsAndProperties::class], SimpleInterface::class => [SimpleInterface::class], InterfaceWithMethod::class => [InterfaceWithMethod::class], SimpleTrait::class => [SimpleTrait::class], SimpleInheritance::class => [SimpleInheritance::class], SimpleAbstractInheritance::class => [SimpleAbstractInheritance::class], ClassWithInterface::class => [ClassWithInterface::class], ClassWithTrait::class => [ClassWithTrait::class], ClassWithTraitAndInterface::class => [ClassWithTraitAndInterface::class], NoCloneable::class => [NoCloneable::class], NoInstantiable::class => [NoInstantiable::class], AbstractInterface::class => [AbstractInterface::class], ClassWithScalarConstants::class => [ClassWithScalarConstants::class], ClassWithMagicConstants::class => [ClassWithMagicConstants::class], ClassWithConstantsAndInheritance::class => [ClassWithConstantsAndInheritance::class], TraitWithProperties::class => [TraitWithProperties::class], ClassWithTraitAndAdaptation::class => [ClassWithTraitAndAdaptation::class], ClassWithTraitAndConflict::class => [ClassWithTraitAndConflict::class], ]; return $classNames; } /** * Tests specific features of PHP5.6 and newer, for example, array constants, etc */ public function testGettersPHP56() { if (PHP_VERSION_ID < 50600) { $this->markTestSkipped("Can not test new features on old version of PHP"); } $fileName = stream_resolve_include_path(__DIR__ . self::STUB_FILE56); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); include_once $fileName; $parsedFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub'); foreach ($parsedFileNamespace->getClasses() as $parsedRefClass) { $this->performGeneralMethodComparison($parsedRefClass); } } public function testNewInstanceMethod() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(FinalClass::class); $instance = $parsedRefClass->newInstance(); $this->assertInstanceOf(FinalClass::class, $instance); $this->assertSame([], $instance->args); } public function testNewInstanceArgsMethod() { $someValueByRef = 5; $arguments = [1, &$someValueByRef]; $parsedRefClass = $this->parsedRefFileNamespace->getClass(FinalClass::class); $instance = $parsedRefClass->newInstanceArgs($arguments); $this->assertInstanceOf(FinalClass::class, $instance); $this->assertSame($arguments, $instance->args); } public function testNewInstanceWithoutConstructorMethod() { $arguments = [1, 2]; $parsedRefClass = $this->parsedRefFileNamespace->getClass(FinalClass::class); $instance = $parsedRefClass->newInstanceWithoutConstructor($arguments); $this->assertInstanceOf(FinalClass::class, $instance); $this->assertSame([], $instance->args); } public function testSetStaticPropertyValueMethod() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ClassWithConstantsAndInheritance::class); $originalRefClass = new \ReflectionClass(ClassWithConstantsAndInheritance::class); $parsedRefClass->setStaticPropertyValue('h', 'test'); $this->assertSame($parsedRefClass->getStaticPropertyValue('h'), $originalRefClass->getStaticPropertyValue('h')); } public function testGetMethodsFiltering() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ClassWithMethodsAndProperties::class); $originalRefClass = new \ReflectionClass(ClassWithMethodsAndProperties::class); $parsedMethods = $parsedRefClass->getMethods(\ReflectionMethod::IS_PUBLIC); $originalMethods = $originalRefClass->getMethods(\ReflectionMethod::IS_PUBLIC); $this->assertCount(count($originalMethods), $parsedMethods); $parsedMethods = $parsedRefClass->getMethods(\ReflectionMethod::IS_PRIVATE | \ReflectionMethod::IS_STATIC); $originalMethods = $originalRefClass->getMethods(\ReflectionMethod::IS_PRIVATE | \ReflectionMethod::IS_STATIC); $this->assertCount(count($originalMethods), $parsedMethods); } public function testDirectMethods() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ImplicitAbstractClass::class); $originalRefClass = new \ReflectionClass(ImplicitAbstractClass::class); $this->assertEquals($originalRefClass->hasMethod('test'), $parsedRefClass->hasMethod('test')); $this->assertCount(count($originalRefClass->getMethods()), $parsedRefClass->getMethods()); $originalMethodName = $originalRefClass->getMethod('test')->getName(); $parsedMethodName = $parsedRefClass->getMethod('test')->getName(); $this->assertSame($originalMethodName, $parsedMethodName); } public function testInheritedMethods() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(SimpleAbstractInheritance::class); $originalRefClass = new \ReflectionClass(SimpleAbstractInheritance::class); $this->assertEquals($originalRefClass->hasMethod('test'), $parsedRefClass->hasMethod('test')); } public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionClass::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionClass::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); } } /** * Performs list of common checks on parsed and runtime refelection * * @param ReflectionCLass $parsedRefClass * @param array $allNameGetters Optional list of getters to check */ protected function performGeneralMethodComparison(ReflectionCLass $parsedRefClass, array $allNameGetters = []) { $allNameGetters = $allNameGetters ?: [ 'getStartLine', 'getEndLine', 'getDocComment', 'getExtension', 'getExtensionName', 'getName', 'getNamespaceName', 'getShortName', 'inNamespace', 'isAbstract', 'isCloneable', 'isFinal', 'isInstantiable', 'isInterface', 'isInternal', 'isIterateable', 'isTrait', 'isUserDefined', 'getConstants', 'getTraitNames', 'getInterfaceNames', 'getStaticProperties', 'getDefaultProperties', 'getTraitAliases' ]; $className = $parsedRefClass->getName(); $originalRefClass = new \ReflectionClass($className); foreach ($allNameGetters as $getterName) { $expectedValue = $originalRefClass->$getterName(); $actualValue = $parsedRefClass->$getterName(); $this->assertSame( $expectedValue, $actualValue, "$getterName() for class $className should be equal" ); } } } tests/ReflectionFileNamespaceTest.php000064400000010651144760111300013766 0ustar00getFileNamespace('Go\ParserReflection\Stub'); $this->parsedRefFileNamespace = $parsedFileNamespace; include_once $fileName; } public function testGetClass() { $refClass = $this->parsedRefFileNamespace->getClass('Unknown'); $this->assertFalse($refClass); $refClass = $this->parsedRefFileNamespace->getClass(TestNamespaceClassFoo::class); $this->assertInstanceOf(\ReflectionClass::class, $refClass); $this->assertEquals(TestNamespaceClassFoo::class, $refClass->name); } public function testGetClasses() { $refClasses = $this->parsedRefFileNamespace->getClasses(); $this->assertCount(2, $refClasses); } public function testGetConstant() { $constValue = $this->parsedRefFileNamespace->getConstant('Unknown'); $this->assertFalse($constValue); $constValue = $this->parsedRefFileNamespace->getConstant('NAMESPACE_NAME'); $this->assertNotFalse($constValue); $this->assertEquals(\Go\ParserReflection\Stub\NAMESPACE_NAME, $constValue); } public function testGetDocComment() { $docComment = $this->parsedRefFileNamespace->getDocComment(); $this->assertNotEmpty($docComment); } public function testGetEndLine() { $endLine = $this->parsedRefFileNamespace->getEndLine(); $this->assertEquals(\Go\ParserReflection\Stub\END_MARKER + 1, $endLine); } public function testGetFileName() { $fileName = $this->parsedRefFileNamespace->getFileName(); $this->assertEquals(\Go\ParserReflection\Stub\FILE_NAME, $fileName); } public function testGetFunction() { $refFunction = $this->parsedRefFileNamespace->getFunction('Unknown'); $this->assertFalse($refFunction); $refFunction = $this->parsedRefFileNamespace->getFunction('testFunctionBar'); $this->assertInstanceOf(\ReflectionFunction::class, $refFunction); $this->assertEquals('testFunctionBar', $refFunction->name); } public function testGetFunctions() { $refFunctions = $this->parsedRefFileNamespace->getFunctions(); $this->assertCount(1, $refFunctions); } public function testGetName() { $namespaceName = $this->parsedRefFileNamespace->getName(); $this->assertEquals(\Go\ParserReflection\Stub\NAMESPACE_NAME, $namespaceName); } public function testGetNamespaceAliases() { $expectedAliases = [ 'ReflectionClass' => 'UnusedReflectionClass', 'PhpParser\Node' => 'UnusedNode', 'PhpParser\Node\Expr' => 'UnusedNodeExpr' ]; $realAliases = $this->parsedRefFileNamespace->getNamespaceAliases(); $this->assertEquals($expectedAliases, $realAliases); } public function testGetStartLine() { $startLine = $this->parsedRefFileNamespace->getStartLine(); $this->assertEquals(\Go\ParserReflection\Stub\START_MARKER - 2, $startLine); } public function testHasClass() { $hasClass = $this->parsedRefFileNamespace->hasClass('Unknown'); $this->assertFalse($hasClass); $hasClass = $this->parsedRefFileNamespace->hasClass(TestNamespaceClassFoo::class); $this->assertTrue($hasClass); } public function testHasConstant() { $hasConstant = $this->parsedRefFileNamespace->hasConstant('Unknown'); $this->assertFalse($hasConstant); $hasConstant = $this->parsedRefFileNamespace->hasConstant('NAMESPACE_NAME'); $this->assertTrue($hasConstant); } public function testHasFunction() { $hasFunction = $this->parsedRefFileNamespace->hasFunction('Unknown'); $this->assertFalse($hasFunction); $hasFunction = $this->parsedRefFileNamespace->hasFunction('testFunctionBar'); $this->assertTrue($hasFunction); } } tests/ReflectionFileTest.php000064400000004015144760111300012146 0ustar00parsedRefFile = $reflectionFile; } public function testGetName() { $fileName = $this->parsedRefFile->getName(); $expectedName = stream_resolve_include_path(__DIR__ . self::STUB_FILE); $this->assertEquals($expectedName, $fileName); } public function testGetFileNamespaces() { $reflectionFileNamespaces = $this->parsedRefFile->getFileNamespaces(); $this->assertCount(3, $reflectionFileNamespaces); } public function testGetFileNamespace() { $reflectionFileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $this->assertInstanceOf(ReflectionFileNamespace::class, $reflectionFileNamespace); $reflectionFileNamespace = $this->parsedRefFile->getFileNamespace('Unknown'); $this->assertFalse($reflectionFileNamespace); } public function testHasFileNamespace() { $hasFileNamespace = $this->parsedRefFile->hasFileNamespace('Go\ParserReflection\Stub'); $this->assertTrue($hasFileNamespace); $hasFileNamespace = $this->parsedRefFile->hasFileNamespace('Unknown'); $this->assertFalse($hasFileNamespace); } public function testGetGlobalFileNamespace() { $fileName = stream_resolve_include_path(__DIR__ . self::STUB_GLOBAL_FILE); $reflectionFile = new ReflectionFile($fileName); $reflectionFileNamespace = $reflectionFile->getFileNamespace('\\'); $this->assertInstanceOf(ReflectionFileNamespace::class, $reflectionFileNamespace); } } tests/ReflectionFunctionTest.php000064400000012177144760111300013064 0ustar00parsedRefFile = $reflectionFile; include_once $fileName; } public function testGeneralInfoGetters() { $allNameGetters = [ 'getStartLine', 'getEndLine', 'getDocComment', 'getExtension', 'getExtensionName', 'getName', 'getNamespaceName', 'getShortName', 'inNamespace', 'getStaticVariables', 'getNumberOfParameters', 'getNumberOfRequiredParameters', '__toString', 'isDisabled', 'returnsReference', 'getClosureScopeClass', 'getClosureThis' ]; if (PHP_VERSION_ID >= 70000) { $allNameGetters[] = 'hasReturnType'; } foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { $functionName = $refFunction->getName(); $originalRefFunction = new \ReflectionFunction($functionName); foreach ($allNameGetters as $getterName) { $expectedValue = $originalRefFunction->$getterName(); $actualValue = $refFunction->$getterName(); $this->assertSame( $expectedValue, $actualValue, "{$getterName}() for function {$functionName} should be equal" ); } } } } public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionFunction::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionFunction::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); } } public function testGetClosureMethod() { $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $refFunc = $fileNamespace->getFunction('noGeneratorFunc'); $closure = $refFunc->getClosure(); $this->assertInstanceOf(\Closure::class, $closure); $retValue = $closure(); $this->assertEquals(100, $retValue); } public function testInvokeMethod() { $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $refFunc = $fileNamespace->getFunction('funcWithReturnArgs'); $retValue = $refFunc->invoke(1, 2, 3); $this->assertEquals([1, 2, 3], $retValue); } public function testInvokeArgsMethod() { $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $refFunc = $fileNamespace->getFunction('funcWithReturnArgs'); $retValue = $refFunc->invokeArgs([1, 2, 3]); $this->assertEquals([1, 2, 3], $retValue); } public function testGetReturnTypeMethod() { if (PHP_VERSION_ID < 70000) { $this->markTestSkipped('Test available only for PHP7.0 and newer'); } $fileName = stream_resolve_include_path(__DIR__ . self::STUB_FILE70); $reflectionFile = new ReflectionFile($fileName); include $fileName; foreach ($reflectionFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { $functionName = $refFunction->getName(); $originalRefFunction = new \ReflectionFunction($functionName); $hasReturnType = $refFunction->hasReturnType(); $this->assertSame( $originalRefFunction->hasReturnType(), $hasReturnType, "Presence of return type for function {$functionName} should be equal" ); if ($hasReturnType) { $parsedReturnType = $refFunction->getReturnType(); $originalReturnType = $originalRefFunction->getReturnType(); $this->assertSame($originalReturnType->allowsNull(), $parsedReturnType->allowsNull()); $this->assertSame($originalReturnType->isBuiltin(), $parsedReturnType->isBuiltin()); $this->assertSame($originalReturnType->__toString(), $parsedReturnType->__toString()); } } } } } tests/ReflectionMethodTest.php000064400000011670144760111300012514 0ustar00originalRefClass = $refClass = new \ReflectionClass(self::STUB_CLASS); $fileName = $refClass->getFileName(); $reflectionFile = new ReflectionFile($fileName); $parsedClass = $reflectionFile->getFileNamespace($refClass->getNamespaceName())->getClass($refClass->getName()); $this->parsedRefClass = $parsedClass; } public function testGeneralInfoGetters() { $allNameGetters = [ 'getStartLine', 'getEndLine', 'getDocComment', 'getExtension', 'getExtensionName', 'getName', 'getNamespaceName', 'getShortName', 'inNamespace', 'getStaticVariables', 'isClosure', 'isDeprecated', 'isInternal', 'isUserDefined', 'isAbstract', 'isConstructor', 'isDestructor', 'isFinal', 'isPrivate', 'isProtected', 'isPublic', 'isStatic', '__toString', 'getNumberOfParameters', 'getNumberOfRequiredParameters', 'returnsReference', 'getClosureScopeClass', 'getClosureThis' ]; if (PHP_VERSION_ID >= 50600) { $allNameGetters[] = 'isVariadic'; $allNameGetters[] = 'isGenerator'; } if (PHP_VERSION_ID >= 70000) { $allNameGetters[] = 'hasReturnType'; } $allMethods = $this->originalRefClass->getMethods(); foreach ($allMethods as $refMethod) { $methodName = $refMethod->getName(); $parsedMethod = $this->parsedRefClass->getMethod($methodName); foreach ($allNameGetters as $getterName) { $expectedValue = $refMethod->$getterName(); $actualValue = $parsedMethod->$getterName(); $this->assertSame( $expectedValue, $actualValue, "$getterName() for method $methodName should be equal" ); } } } public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionMethod::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionMethod::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); } } public function testGetClosureMethod() { $refMethod = $this->parsedRefClass->getMethod('funcWithDocAndBody'); $closure = $refMethod->getClosure(null); $this->assertInstanceOf(\Closure::class, $closure); $retValue = $closure(); $this->assertEquals('hello', $retValue); } public function testInvokeMethod() { $refMethod = $this->parsedRefClass->getMethod('funcWithReturnArgs'); $retValue = $refMethod->invoke(null, 1, 2, 3); $this->assertEquals([1, 2, 3], $retValue); } public function testInvokeArgsMethod() { $refMethod = $this->parsedRefClass->getMethod('funcWithReturnArgs'); $retValue = $refMethod->invokeArgs(null, [1, 2, 3]); $this->assertEquals([1, 2, 3], $retValue); } public function testDebugInfoMethod() { $parsedRefMethod = new ReflectionMethod(self::STUB_CLASS, 'funcWithDocAndBody'); $originalRefMethod = new \ReflectionMethod(self::STUB_CLASS, 'funcWithDocAndBody'); $expectedValue = (array) $originalRefMethod; $this->assertSame($expectedValue, $parsedRefMethod->__debugInfo()); } public function testSetAccessibleMethod() { $refMethod = $this->parsedRefClass->getMethod('protectedStaticFunc'); $refMethod->setAccessible(true); $retValue = $refMethod->invokeArgs(null, []); $this->assertEquals(null, $retValue); } public function testGetPrototypeMethod() { $refMethod = $this->parsedRefClass->getMethod('prototypeMethod'); $retValue = $refMethod->invokeArgs(null, []); $this->assertEquals(self::STUB_CLASS, $retValue); $prototype = $refMethod->getPrototype(); $this->assertInstanceOf(\ReflectionMethod::class, $prototype); $prototype->setAccessible(true); $retValue = $prototype->invokeArgs(null, []); $this->assertNotEquals(self::STUB_CLASS, $retValue); } } tests/ReflectionParameterTest.php000064400000021161144760111300013210 0ustar00parsedRefFile = $reflectionFile; include_once $fileName; } public function testGeneralInfoGetters() { $allNameGetters = [ 'isArray', 'isCallable', 'isOptional', 'isPassedByReference', 'isDefaultValueAvailable', 'getPosition', 'canBePassedByValue', 'allowsNull', 'getDefaultValue', 'getDefaultValueConstantName', 'isDefaultValueConstant', '__toString' ]; $onlyWithDefaultValues = array_flip([ 'getDefaultValue', 'getDefaultValueConstantName', 'isDefaultValueConstant' ]); if (PHP_VERSION_ID >= 50600) { $allNameGetters[] = 'isVariadic'; } if (PHP_VERSION_ID >= 70000) { $allNameGetters[] = 'hasType'; } foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { $functionName = $refFunction->getName(); foreach ($refFunction->getParameters() as $refParameter) { $parameterName = $refParameter->getName(); $originalRefParameter = new \ReflectionParameter($functionName, $parameterName); foreach ($allNameGetters as $getterName) { // skip some methods if there is no default value $isDefaultValueAvailable = $originalRefParameter->isDefaultValueAvailable(); if (isset($onlyWithDefaultValues[$getterName]) && !$isDefaultValueAvailable) { continue; } $expectedValue = $originalRefParameter->$getterName(); $actualValue = $refParameter->$getterName(); $this->assertSame( $expectedValue, $actualValue, "{$getterName}() for parameter {$functionName}:{$parameterName} should be equal" ); } } } } } public function testGetClassMethod() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedFunction = $parsedNamespace->getFunction('miscParameters'); $parameters = $parsedFunction->getParameters(); $this->assertSame(null, $parameters[0 /* array $arrayParam*/]->getClass()); $this->assertSame(null, $parameters[3 /* callable $callableParam */]->getClass()); $objectParam = $parameters[5 /* \stdClass $objectParam */]->getClass(); $this->assertInstanceOf(\ReflectionClass::class, $objectParam); $this->assertSame(\stdClass::class, $objectParam->getName()); $typehintedParamWithNs = $parameters[7 /* ReflectionParameter $typehintedParamWithNs */]->getClass(); $this->assertInstanceOf(\ReflectionClass::class, $typehintedParamWithNs); $this->assertSame(ReflectionParameter::class, $typehintedParamWithNs->getName()); } public function testGetDeclaringClassMethodReturnsObject() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(Foo::class); $parsedFunction = $parsedClass->getMethod('methodParam'); $parameters = $parsedFunction->getParameters(); $this->assertSame($parsedClass->getName(), $parameters[0]->getDeclaringClass()->getName()); } public function testGetDeclaringClassMethodReturnsNull() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedFunction = $parsedNamespace->getFunction('miscParameters'); $parameters = $parsedFunction->getParameters(); $this->assertNull($parameters[0]->getDeclaringClass()); } public function testDebugInfoMethod() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedFunction = $parsedNamespace->getFunction('miscParameters'); $parsedRefParameters = $parsedFunction->getParameters(); $parsedRefParameter = $parsedRefParameters[0]; $originalRefParameter = new \ReflectionParameter('Go\ParserReflection\Stub\miscParameters', 'arrayParam'); $expectedValue = (array) $originalRefParameter; $this->assertSame($expectedValue, $parsedRefParameter->__debugInfo()); } /** * @dataProvider listOfDefaultGetters * * @param string $getterName Name of the getter to call */ public function testGetDefaultValueThrowsAnException($getterName) { $originalException = null; $parsedException = null; try { $originalRefParameter = new \ReflectionParameter('Go\ParserReflection\Stub\miscParameters', 'arrayParam'); $originalRefParameter->$getterName(); } catch (\ReflectionException $e) { $originalException = $e; } try { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedFunction = $parsedNamespace->getFunction('miscParameters'); $parsedRefParameters = $parsedFunction->getParameters(); $parsedRefParameter = $parsedRefParameters[0]; $parsedRefParameter->$getterName(); } catch (\ReflectionException $e) { $parsedException = $e; } $this->assertInstanceOf(\ReflectionException::class, $originalException); $this->assertInstanceOf(\ReflectionException::class, $parsedException); $this->assertSame($originalException->getMessage(), $parsedException->getMessage()); } public function listOfDefaultGetters() { return [ ['getDefaultValue'], ['getDefaultValueConstantName'] ]; } public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionParameter::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionParameter::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); } } public function testGetTypeMethod() { if (PHP_VERSION_ID < 70000) { $this->markTestSkipped('Test available only for PHP7.0 and newer'); } foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getFunctions() as $refFunction) { $functionName = $refFunction->getName(); foreach ($refFunction->getParameters() as $refParameter) { $parameterName = $refParameter->getName(); $originalRefParameter = new \ReflectionParameter($functionName, $parameterName); $hasType = $refParameter->hasType(); $this->assertSame( $originalRefParameter->hasType(), $hasType, "Presence of type for parameter {$functionName}:{$parameterName} should be equal" ); if ($hasType) { $parsedReturnType = $refParameter->getType(); $originalReturnType = $originalRefParameter->getType(); $this->assertSame($originalReturnType->allowsNull(), $parsedReturnType->allowsNull()); $this->assertSame($originalReturnType->isBuiltin(), $parsedReturnType->isBuiltin()); $this->assertSame($originalReturnType->__toString(), $parsedReturnType->__toString()); } } } } } } tests/ReflectionPropertyTest.php000064400000007632144760111300013123 0ustar00originalRefClass = $refClass = new \ReflectionClass(self::STUB_CLASS); $fileName = $refClass->getFileName(); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); $parsedClass = $reflectionFile->getFileNamespace($refClass->getNamespaceName())->getClass($refClass->getName()); $this->parsedRefClass = $parsedClass; } public function testGeneralInfoGetters() { $allNameGetters = [ 'isDefault', 'getName', 'getModifiers', 'getDocComment', 'isPrivate', 'isProtected', 'isPublic', 'isStatic', '__toString' ]; $allProperties = $this->originalRefClass->getProperties(); foreach ($allProperties as $refProperty) { $propertyName = $refProperty->getName(); $parsedProperty = $this->parsedRefClass->getProperty($propertyName); foreach ($allNameGetters as $getterName) { $expectedValue = $refProperty->$getterName(); $actualValue = $parsedProperty->$getterName(); $this->assertSame( $expectedValue, $actualValue, "$getterName() for property $propertyName should be equal" ); } } } public function testSetAccessibleMethod() { $parsedProperty = $this->parsedRefClass->getProperty('protectedStaticProperty'); $parsedProperty->setAccessible(true); $value = $parsedProperty->getValue(); $this->assertSame('foo', $value); } public function testGetSetValueForObjectMethods() { $parsedProperty = $this->parsedRefClass->getProperty('protectedProperty'); $parsedProperty->setAccessible(true); $className = self::STUB_CLASS; $obj = new $className; $value = $parsedProperty->getValue($obj); $this->assertSame('a', $value); $parsedProperty->setValue($obj, 43); $value = $parsedProperty->getValue($obj); $this->assertSame(43, $value); } public function testCompatibilityWithOriginalConstructor() { $parsedRefProperty = new ReflectionProperty(self::STUB_CLASS, 'publicStaticProperty'); $originalValue = $parsedRefProperty->getValue(); $this->assertSame(M_PI, $originalValue); } public function testDebugInfoMethod() { $parsedRefProperty = new ReflectionProperty(self::STUB_CLASS, 'publicStaticProperty'); $originalRefProperty = new \ReflectionProperty(self::STUB_CLASS, 'publicStaticProperty'); $expectedValue = (array) $originalRefProperty; $this->assertSame($expectedValue, $parsedRefProperty->__debugInfo()); } public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionProperty::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionProperty::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join($allMissedMethods, ', ') . ' are not implemented'); } } } tests/Stub/AbstractClassWithMethods.php000064400000002761144760111300014250 0ustar00args = array_slice(array($a, &$b), 0, func_num_args()); } } abstract class ClassWithMethodsAndProperties { public $publicProperty; protected $protectedProperty; private $privateProperty; static public $staticPublicProperty; static protected $staticProtectedProperty; static private $staticPrivateProperty; public function publicMethod() {} protected function protectedMethod() {} private function privateMethod() {} static public function publicStaticMethod() {} static protected function protectedStaticMethod() {} static private function privateStaticMethod() {} abstract public function publicAbstractMethod(); abstract protected function protectedAbstractMethod(); final public function publicFinalMethod() {} final protected function protectedFinalMethod() {} final private function privateFinalMethod() {} } interface SimpleInterface {} interface InterfaceWithMethod { function foo(); } trait SimpleTrait { function foo() { return __CLASS__; } } trait ConflictedSimpleTrait { function foo() { return 'BAZ'; } } class SimpleInheritance extends ExplicitAbstractClass {} abstract class SimpleAbstractInheritance extends ImplicitAbstractClass { public $b = 'bar1'; public $d = 'foobar'; private $e = 'foobaz'; } class ClassWithInterface implements SimpleInterface {} class ClassWithTrait { use SimpleTrait; } class ClassWithTraitAndAdaptation { use SimpleTrait { foo as protected fooBar; foo as private fooBaz; } } class ClassWithTraitAndConflict { use SimpleTrait, ConflictedSimpleTrait { foo as protected fooBar; ConflictedSimpleTrait::foo insteadof SimpleTrait; } } class ClassWithTraitAndInterface implements InterfaceWithMethod { use SimpleTrait; } class NoCloneable { private function __clone() {} } class NoInstantiable { private function __construct() {} } interface AbstractInterface { public function foo(); public function bar(); } class ClassWithScalarConstants { const A = 10, A1 = 11; const B = 42.0; const C = 'foo'; const D = false; } class ClassWithMagicConstants { const A = __DIR__; const B = __FILE__; const C = __NAMESPACE__; const D = __CLASS__; const E = __LINE__; public static $a = self::A; protected static $b = self::B; private static $c = self::C; } const NS_CONST = 'test'; class ClassWithConstantsAndInheritance extends ClassWithMagicConstants { const A = 'overridden'; const H = M_PI; const J = NS_CONST; public static $h = self::H; } trait TraitWithProperties { private $a = 'foo'; protected $b = 'bar'; public $c = 'baz'; private static $as = 1; protected static $bs = __TRAIT__; public static $cs = 'foo'; } tests/Stub/FileWithClasses56.php000064400000000536144760111300012541 0ustar00