.gitignore 0000644 00000000027 14476011467 0006545 0 ustar 00 /vendor/
composer.lock
.scrutinizer.yml 0000644 00000001517 14476011467 0007744 0 ustar 00 before_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.sh 0000755 00000000260 14476011467 0010273 0 ustar 00 set -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.yml 0000644 00000000417 14476011467 0006671 0 ustar 00 language: 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.md 0000644 00000007056 14476011467 0006045 0 ustar 00 Parser 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 for PHP versions 5.5, 5.6, 7.0; for automatic proxy creation and much more.
[](https://scrutinizer-ci.com/g/goaop/parser-reflection/build-status/master)
[](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master)
[](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master)
[](https://insight.sensiolabs.com/projects/1fdfee9c-839a-4209-a2f2-42dadc859621)[](https://php.net/)
[](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());
```
Or you can use an additional classes [`ReflectionFile`][0] and [`ReflectionFileNamespace`][1] to analyse a raw PHP files
```php
$parsedFile = new \Go\ParserReflection\ReflectionFile('SomeClass.php');
$fileNameSpaces = $parsedFile->getFileNamespaces()
var_dump($fileNameSpaces);
var_dump($fileNameSpaces[0]->getClass(SomeClass::class)->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.
[0]: docs/reflection_file.md
[1]: docs/reflection_file_namespace.md
composer.json 0000644 00000001215 14476011467 0007277 0 ustar 00 {
"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"
}
}
docs/README.md 0000644 00000002213 14476011467 0006763 0 ustar 00 ParserReflection API Reference
==============
Introduction
--------
PHP comes with a complete reflection API that adds the ability to reverse-engineer classes, interfaces, functions, methods and extensions. Additionally, the reflection API offers ways to retrieve doc comments for functions, classes and methods.
But this reflection API requires concrete element to be loaded into the memory. Once element is loaded, it can not be changed, modified or updated without special extensions.
`goaop\parser-reflection` packet is a userland implementation of reflection API that is fully compatible with internal one, but doesn't require element (e.g. class or function) to be loaded into the PHP. Only the source code is required for performing the raw analysis.
Reference
---------
- [`ReflectionClass`](reflection_class.md)
- [`ReflectionFile`](reflection_file.md)
- [`ReflectionFileNamespace`](reflection_file_namespace.md)
- [`ReflectionFunction`](reflection_function.md)
- [`ReflectionMethod`](reflection_method.md)
- [`ReflectionParameter`](reflection_parameter.md)
- [`ReflectionProperty`](reflection_property.md)
- [`ReflectionType`](reflection_type.md)
docs/reflection_class.md 0000644 00000001477 14476011467 0011360 0 ustar 00 ReflectionClass
==============
The `ReflectionClass` class reports an information about a class. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionClass`][0]
But be careful, that several methods require the class to be loaded into the memory, otherwise an exception will be thrown.
List of methods, that require class to be loaded
---------
- ReflectionClass::newInstance — Creates a new class instance from given arguments.
- ReflectionClass::newInstanceArgs — Creates a new class instance from given arguments.
- ReflectionClass::newInstanceWithoutConstructor — Creates a new class instance without invoking the constructor.
- ReflectionClass::setStaticPropertyValue — Sets static property value
[0]: http://php.net/manual/en/class.reflectionclass.php
docs/reflection_file.md 0000644 00000002667 14476011467 0011174 0 ustar 00 ReflectionFile
==============
The `ReflectionFile` class reports an information about a file with valid PHP source code. This class is not available in the standard PHP
API
---
```php
class ReflectionFile
{
public function __construct($fileName, $topLevelNodes = null) {}
public function getFileNamespace($namespaceName) {}
public function getFileNamespaces() {}
public function getName() {}
public function hasFileNamespace($namespaceName) {}
}
```
Methods
-------
- `ReflectionFile::__construct($fileName, $topLevelNodes = null)`
Constructs an instance of `ReflectionFile` object for given `fileName`. Can accept custom AST-tree nodes as optional parameter.
- `ReflectionFile::getFileNamespace($namespaceName)`
Returns a [`ReflectionFileNamespace`][0] instance for the specified namespace in a file. If you don't know an exact name of namespace in the file, then use `getFileNamespaces()` method to get a full list of namespaces for inspection.
- `ReflectionFile::getFileNamespaces()`
Returns an array with available namespaces in the file. Each `namespace {}` section will be represented as a single instance of [`ReflectionFileNamespace`][0] in this list.
- `ReflectionFile::getName()`
Returns a string with the name of file
- `ReflectionFile::hasFileNamespace($namespaceName)`
Checks if requested namespace is present in the file or not. Returns `true` if present.
[0]: reflection_file_namespace.md
docs/reflection_file_namespace.md 0000644 00000007122 14476011467 0013177 0 ustar 00 ReflectionFileNamespace
==============
The `ReflectionFileNamespace` class reports an information about a namespace in the single file. This class is not available in the standard PHP
API
---
```php
class ReflectionFileNamespace
{
public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null) {}
public function getClass($className) {}
public function getClasses() {}
public function getConstant($constantName) {}
public function getConstants() {}
public function getDocComment() {}
public function getEndLine() {}
public function getFileName() {}
public function getFunction($functionName) {}
public function getFunctions() {}
public function getName() {}
public function getNamespaceAliases() {}
public function getStartLine() {}
public function hasClass($className) {}
public function hasConstant($constantName) {}
public function hasFunction($functionName) {}
}
```
Methods
-------
- `ReflectionFileNamespace::__construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)`
Constructs an instance of `ReflectionFileNamespace` object for given `fileName` and `namespaceName`. Can accept custom `Namespace_` node as optional parameter.
- `ReflectionFileNamespace::getClass($className)`
Returns the concrete [`ReflectionClass`][0] from the file namespace or `false` if there is no such a class in the current namespace
- `ReflectionFileNamespace::getClasses()`
Returns an array with available classes in the namespace. Each class/trait/interface definition will be represented as a single instance of [`ReflectionClass`][0] in this list.
- `ReflectionFileNamespace::getConstant($constantName)`
Returns a value for the constant with name `$constantName` in the file namespace or `false` if there is no such a constant in the current namespace.
- `ReflectionFileNamespace::getConstants()`
Returns an array with all available constants in the namespace.
- `ReflectionFileNamespace::getDocComment()`
Returns a doc-block section for file namespace if present, otherwise `false`
- `ReflectionFileNamespace::getEndLine()`
Returns a end line for the namespace. Be careful, this value is not correct for simple `namespace Name;` definitions.
- `ReflectionFileNamespace::getFileName()`
Returns a string with the name of file
- `ReflectionFileNamespace::getFunction($functionName)`
Returns the concrete [`ReflectionFunction`][1] from the file namespace or `false` if there is no such a function in the current namespace
- `ReflectionFileNamespace::getFunctions()`
Returns an array with available functions in the namespace. Each function definition will be represented as a single instance of [`ReflectionFunction`][1] in this list.
- `ReflectionFile::getName()`
Returns a string with the name of current file namespace
- `ReflectionFileNamespace::getNamespaceAliases()`
Returns an array with all imported class namespaces and aliases for them in the current namespace.
- `ReflectionFileNamespace::getStartLine()`
Returns a start line for the namespace.
- `ReflectionFileNamespace::hasClass($className)`
Checks if the requested class is present in the file namespace or not. Returns `true` if present.
- `ReflectionFileNamespace::hasConstant($constantName)`
Checks if the requested constant is present in the file namespace or not. Returns `true` if present.
- `ReflectionFileNamespace::hasFunction($functionName)`
Checks if the requested function is present in the file namespace or not. Returns `true` if present.
[0]: reflection_class.md
[1]: reflection_function.md
docs/reflection_function.md 0000644 00000001260 14476011467 0012066 0 ustar 00 ReflectionFunction
==============
The `ReflectionFunction` class reports an information about a function. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionFunction`][0]
But be careful, that several methods require the function to be loaded into the memory, otherwise an exception will be thrown.
List of methods, that require function to be loaded
---------
- ReflectionFunction::getClosure — Returns a dynamically created closure for the function
- ReflectionFunction::invoke — Invokes function
- ReflectionFunction::invokeArgs — Invokes function args
[0]: http://php.net/manual/en/class.reflectionfunction.php
docs/reflection_method.md 0000644 00000001371 14476011467 0011524 0 ustar 00 ReflectionMethod
==============
The `ReflectionMethod` class reports an information about a method. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionMethod`][0]
But be careful, that several methods require the method and class to be loaded into the memory, otherwise an exception will be thrown.
List of methods, that require method and a corresponding class to be loaded
---------
- ReflectionMethod::getClosure — Returns a dynamically created closure for the method
- ReflectionMethod::invoke — Invokes method
- ReflectionMethod::invokeArgs — Invokes method args
- ReflectionMethod::setAccessible — Set method accessibility
[0]: http://php.net/manual/en/class.reflectionmethod.php
docs/reflection_parameter.md 0000644 00000000455 14476011467 0012226 0 ustar 00 ReflectionParameter
==============
The `ReflectionParameter` class reports an information about an parameter. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionParameter`][0]
[0]: http://php.net/manual/en/class.reflectionparameter.php
docs/reflection_property.md 0000644 00000001205 14476011467 0012124 0 ustar 00 ReflectionProperty
==============
The `ReflectionProperty` class reports an information about a property. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionProperty`][0]
But be careful, that several methods require the property and class to be loaded into the memory, otherwise an exception will be thrown.
List of methods, that require property and a corresponding class to be loaded
---------
- ReflectionProperty::setAccessible — Set property accessibility
- ReflectionProperty::setValue — Set property value
[0]: http://php.net/manual/en/class.reflectionproperty.php
docs/reflection_type.md 0000644 00000000423 14476011467 0011222 0 ustar 00 ReflectionType
==============
The `ReflectionType` class reports an information about a type. This class is available in the standard PHP, so for any questions, please look at documentation for [`ReflectionType`][0]
[0]: http://php.net/manual/en/class.reflectiontype.php
phpunit.xml.dist 0000644 00000001725 14476011467 0007736 0 ustar 00
./tests/
./src/
performance
src/Locator/ComposerLocator.php 0000644 00000002672 14476011467 0012603 0 ustar 00
*
* 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.php 0000644 00000001207 14476011467 0011302 0 ustar 00
*
* 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.php 0000644 00000002114 14476011467 0013741 0 ustar 00
*
* 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.php 0000644 00000004067 14476011467 0015261 0 ustar 00
*
* 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.php 0000644 00000010373 14476011467 0011142 0 ustar 00
*
* 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.php 0000644 00000015064 14476011467 0011304 0 ustar 00
*
* 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
* @param string|null $fileContent Optional content of the file
*
* @return \PhpParser\Node[]
*/
public static function parseFile($fileName, $fileContent = null)
{
if (isset(self::$parsedFiles[$fileName])) {
return self::$parsedFiles[$fileName];
}
if (!isset($fileContent)) {
$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.php 0000644 00000000633 14476011467 0012031 0 ustar 00
*
* 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.php 0000644 00000006671 14476011467 0010762 0 ustar 00
*
* 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.php 0000644 00000022656 14476011467 0012600 0 ustar 00
*
* 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.php 0000644 00000006533 14476011467 0011665 0 ustar 00
*
* 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.php 0000644 00000020321 14476011467 0011307 0 ustar 00
*
* 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;
/**
* Optional declaring class reference
*
* @var ReflectionClass
*/
private $declaringClass;
/**
* 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
* @param ReflectionClass $declaringClass Optional declaring class
*/
public function __construct(
$className,
$methodName,
ClassMethod $classMethodNode = null,
ReflectionClass $declaringClass = null
) {
//for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too
$this->className = $className;
$this->declaringClass = $declaringClass;
$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 isset($this->declaringClass) ? $this->declaringClass : 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 ReflectionClass $reflectionClass Reflection of the class
*
* @return array|ReflectionMethod[]
*/
public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass)
{
$methods = [];
foreach ($classLikeNode->stmts as $classLevelNode) {
if ($classLevelNode instanceof ClassMethod) {
$classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
$methodName = $classLevelNode->name;
$methods[$methodName] = new ReflectionMethod(
$reflectionClass->name,
$methodName,
$classLevelNode,
$reflectionClass
);
}
}
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.php 0000644 00000022254 14476011467 0012016 0 ustar 00
*
* 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) . '...';
}
/* @see https://3v4l.org/DJOEb for behaviour changes */
if (is_double($defaultValue) && fmod($defaultValue, 1.0) === 0.0) {
$defaultValue = (int) $defaultValue;
}
$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.php 0000644 00000014450 14476011467 0011721 0 ustar 00
*
* 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.php 0000644 00000002326 14476011467 0011015 0 ustar 00
*
* 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.php 0000644 00000002043 14476011467 0013316 0 ustar 00
*
* 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.php 0000644 00000002062 14476011467 0016037 0 ustar 00
*
* 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.php 0000644 00000063415 14476011467 0014226 0 ustar 00
*
* 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);
$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.php 0000644 00000016456 14476011467 0014751 0 ustar 00
*
* 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.php 0000644 00000021134 14476011467 0015344 0 ustar 00
*
* 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.php 0000644 00000000731 14476011467 0010074 0 ustar 00
*
* 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.php 0000644 00000030711 14476011467 0012353 0 ustar 00 getFileNamespace('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.php 0000644 00000010651 14476011467 0014003 0 ustar 00 getFileNamespace('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.php 0000644 00000004015 14476011467 0012163 0 ustar 00 parsedRefFile = $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.php 0000644 00000012177 14476011467 0013101 0 ustar 00 parsedRefFile = $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.php 0000644 00000011671 14476011467 0012532 0 ustar 00 originalRefClass = $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.php 0000644 00000021162 14476011467 0013226 0 ustar 00 parsedRefFile = $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.php 0000644 00000007633 14476011467 0013141 0 ustar 00 originalRefClass = $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.php 0000644 00000002761 14476011467 0014265 0 ustar 00 args = 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.php 0000644 00000000536 14476011467 0012556 0 ustar 00