.github/dependabot.yml000064400000000224146412133200010726 0ustar00version: 2 updates: - package-ecosystem: composer directory: "/" schedule: interval: daily time: "22:00" open-pull-requests-limit: 10 .github/workflows/phpunit.yml000064400000002775146412133200012362 0ustar00name: "PHPUnit tests" on: pull_request: push: jobs: phpunit: name: "PHPUnit tests" runs-on: ${{ matrix.operating-system }} strategy: matrix: dependencies: - "lowest" - "highest" php-version: - "8.2" operating-system: - "ubuntu-latest" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: coverage: "pcov" php-version: "${{ matrix.php-version }}" ini-values: memory_limit=-1 tools: composer:v2, cs2pr - name: "Cache dependencies" uses: "actions/cache@v2" with: path: | ~/.composer/cache vendor key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" - name: "Install lowest dependencies" if: ${{ matrix.dependencies == 'lowest' }} run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" - name: "Install highest dependencies" if: ${{ matrix.dependencies == 'highest' }} run: "composer update --no-interaction --no-progress --no-suggest" - name: "Install locked dependencies" if: ${{ matrix.dependencies == 'locked' }} run: "composer install --no-interaction --no-progress --no-suggest" - name: "Tests" run: "vendor/bin/phpunit" .gitignore000064400000000055146412133200006530 0ustar00/vendor/ composer.lock /.phpunit.result.cache.scrutinizer.yml000064400000001517146412133200007726 0ustar00before_commands: - "composer install --no-dev --prefer-source" tools: external_code_coverage: timeout: 1200 php_code_coverage: enabled: true php_code_sniffer: enabled: true config: standard: PSR2 filter: paths: ["src/*"] php_cpd: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_cs_fixer: enabled: true config: level: all filter: paths: ["src/*"] php_loc: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_pdepend: enabled: true excluded_dirs: ["docs", "examples", "tests", "vendor"] php_analyzer: enabled: true filter: paths: ["src/*"] sensiolabs_security_checker: true LICENSE000064400000002055146412133200005547 0ustar00Copyright (c) 2015-2020 Alexander Lisachenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000064400000011777146412133200006034 0ustar00Parser Reflection API Library ----------------- This library is **deprecated**. Please use [BetterReflection](https://github.com/Roave/BetterReflection). 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 automatic proxy creation and much more. [![Build Status](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/build.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/build-status/master) [![Code Coverage](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master) [![Total Downloads](https://img.shields.io/packagist/dt/goaop/parser-reflection.svg)](https://packagist.org/packages/goaop/parser-reflection) [![Daily Downloads](https://img.shields.io/packagist/dd/goaop/parser-reflection.svg)](https://packagist.org/packages/goaop/parser-reflection) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/goaop/parser-reflection/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/goaop/parser-reflection/?branch=master) [![PHP Version](https://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/packagist/l/goaop/parser-reflection.svg)](https://packagist.org/packages/goaop/parser-reflection) Installation ------------ Library can be installed with Composer. Installation is quite easy: ```bash $ composer require goaop/parser-reflection ``` Composer will install the library to your project's `vendor/goaop/parser-reflection` directory. Usage ------------ ### Initialization Prior to the first use library can be optionally initialized. If you use Composer for installing packages and loading classes, then you shouldn't worry about initialization, library will be initialized automatically. If project uses a custom autoloader then you should follow the next steps: 1. Create a new class that implements `\Go\ParserReflection\LocatorInterface` 2. Create an instance of that class and pass it to the `ReflectionEngine::init()` method for initial configuration ### Reflecting concrete classes/methods/properties without loading them Just use `Go\ParserReflection` package reflection classes like traditional ones: ```php $parsedClass = new \Go\ParserReflection\ReflectionClass(SomeClass::class); var_dump($parsedClass->getMethods()); $parsedMethod = new \Go\ParserReflection\ReflectionMethod(SomeClass::class, 'someMethod'); echo (string)$parsedMethod; ``` 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(); // We can iterate over namespaces in the file foreach ($fileNameSpaces as $namespace) { $classes = $namespace->getClasses(); // Iterate over the classes in the namespace foreach ($classes as $class) { echo "Found class: ", $class->getName(), PHP_EOL; // Now let's show all methods in the class foreach ($class->getMethods() as $method) { echo "Found class method: ", $class->getName(), '::', $method->getName(), PHP_EOL; } // ...all properties in the class foreach ($class->getProperties() as $property) { echo "Found class property: ", $class->getName(), '->', $property->getName(), PHP_EOL; } } } ``` 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 and 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 process of class loading and switching to the internal reflection. [0]: docs/reflection_file.md [1]: docs/reflection_file_namespace.md composer.json000064400000001361146412133200007263 0ustar00{ "name": "goaop/parser-reflection", "description": "Provides reflection information, based on raw source", "license": "MIT", "authors": [ { "name": "Alexander Lisachenko", "email": "lisachenko.it@gmail.com" } ], "minimum-stability": "stable", "autoload": { "psr-4": { "Go\\ParserReflection\\": "src" }, "files": ["src/bootstrap.php"], "exclude-from-classmap": ["/tests/"] }, "autoload-dev": { "psr-4": { "Go\\ParserReflection\\": "tests" } }, "require": { "php": ">=8.2", "nikic/php-parser": "^4.0" }, "require-dev": { "phpunit/phpunit": "^10.5.8", "tracy/tracy": "^2.10" }, "extra": { "branch-alias": { "dev-master": "4.x-dev" } } } docs/README.md000064400000002222146412133200006745 0ustar00ParserReflection 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. However, this reflection API requires concrete element to be loaded into the memory. Once element is loaded, it cannot be changed, modified or updated without special extensions. `goaop\parser-reflection` packet is a user-land implementation of reflection API that is fully compatible with internal one, but doesn't require an 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.md000064400000001477146412133200011342 0ustar00ReflectionClass ============== 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.md000064400000003132146412133200011142 0ustar00ReflectionFile ============== 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) {} public function isStrictMode() {} } ``` 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. - `ReflectionFile::isStrictMode()` Checks if current file has enabled strict mode via `declare(strict_types=1)` for PHP>=7.0 [0]: reflection_file_namespace.md docs/reflection_file_namespace.md000064400000007122146412133200013161 0ustar00ReflectionFileNamespace ============== 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.md000064400000001260146412133200012050 0ustar00ReflectionFunction ============== 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.md000064400000001371146412133200011506 0ustar00ReflectionMethod ============== 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.md000064400000000455146412133200012210 0ustar00ReflectionParameter ============== 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.md000064400000001205146412133200012106 0ustar00ReflectionProperty ============== 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.md000064400000000423146412133200011204 0ustar00ReflectionType ============== 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 nitpick.json000064400000000117146412133200007073 0ustar00{ "ignore": [ "src/*", "tests/*", "docs/*" ] } phpunit.xml.dist000064400000000717146412133200007720 0ustar00 ./tests/ ./src/ src/Instrument/PathResolver.php000064400000005376146412133200012641 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Instrument; /** * Special class for resolving path for different file systems, wrappers, etc * * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath/4050444 * @see http://bugs.php.net/bug.php?id=52769 * * @link https://github.com/goaop/framework/blob/master/src/Instrument/PathResolver.php */ class PathResolver { /** * Custom replacement for realpath() and stream_resolve_include_path() * * @param string|array $somePath Path without normalization or array of paths * @param bool $shouldCheckExistence Flag for checking existence of resolved filename * * @return array|bool|string */ public static function realpath($somePath, $shouldCheckExistence = false) { // Do not resolve empty string/false/arrays into the current path if (!$somePath) { return $somePath; } if (is_array($somePath)) { return array_map([__CLASS__, __FUNCTION__], $somePath); } // Trick to get scheme name and path in one action. If no scheme, then there will be only one part $components = explode('://', $somePath, 2); [$pathScheme, $path] = isset($components[1]) ? $components : [null, $components[0]]; // Optimization to bypass complex logic for simple paths (eg. not in phar archives) if (!$pathScheme && ($fastPath = stream_resolve_include_path($somePath))) { return $fastPath; } $isRelative = !$pathScheme && ($path[0] !== '/') && ($path[1] !== ':'); if ($isRelative) { $path = getcwd() . DIRECTORY_SEPARATOR . $path; } // resolve path parts (single dot, double dot and double delimiters) $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); if (strpos($path, '.') !== false) { $parts = explode(DIRECTORY_SEPARATOR, $path); $absolutes = []; foreach ($parts as $part) { if ('.' === $part) { continue; } if ('..' === $part) { array_pop($absolutes); } else { $absolutes[] = $part; } } $path = implode(DIRECTORY_SEPARATOR, $absolutes); } if ($pathScheme) { $path = "{$pathScheme}://{$path}"; } if ($shouldCheckExistence && !file_exists($path)) { return false; } return $path; } } src/Locator/CallableLocator.php000064400000001745146412133200012475 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Locator; use Go\ParserReflection\LocatorInterface; /** * Locator, that can find a file for the given class name by asking composer */ class CallableLocator implements LocatorInterface { /** * @var callable */ private $callable; public function __construct(callable $callable) { $this->callable = $callable; } /** * 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(string $className) { return call_user_func($this->callable, ltrim($className, '\\')); } } src/Locator/ComposerLocator.php000064400000003327146412133200012563 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Locator; use Composer\Autoload\ClassLoader; use Go\ParserReflection\Instrument\PathResolver; 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 $composerLoader = null) { if ($composerLoader === null) { $loaders = spl_autoload_functions(); foreach ($loaders as $loader) { if (is_array($loader) && $loader[0] instanceof ClassLoader) { $composerLoader = $loader[0]; break; } } if ($composerLoader === null) { throw new ReflectionException('Can not found a correct composer loader'); } } $this->loader = $composerLoader; } /** * 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(string $className) { $filePath = $this->loader->findFile(ltrim($className, '\\')); if (!empty($filePath)) { $filePath = PathResolver::realpath($filePath); } return $filePath; } } src/LocatorInterface.php000064400000001312146412133200011261 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; /** * Locator is responsible to return a file name for given item, typically class */ interface LocatorInterface { /** * Returns a path to the file for given class name * * @param string $className Name of the class (with or without leading '\' FQCN) * * @return string|false Path to the file with given class or false if not found */ public function locateClass(string $className); } src/NodeVisitor/GeneratorDetector.php000064400000002056146412133200013730 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\NodeVisitor; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; /** * Visitor to check if the method body */ class GeneratorDetector extends NodeVisitorAbstract { private $isGenerator = false; /** * {@inheritDoc} */ public function enterNode(Node $node) { // There may be internal generators in closures, we do not need to look at them if ($node instanceof Node\Expr\Closure) { return NodeTraverser::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Expr\Yield_ || $node instanceof Node\Expr\YieldFrom) { $this->isGenerator = true; } return null; } public function isGenerator(): bool { return $this->isGenerator; } } src/NodeVisitor/RootNamespaceNormalizer.php000064400000003277146412133200015121 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\NodeVisitor; use PhpParser\Node\Stmt\Declare_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeVisitorAbstract; /** * Visitor to normalize the root namespace for the files without the namespace (root namespace) * * File->Namespace->Statements */ class RootNamespaceNormalizer extends NodeVisitorAbstract { /** * {@inheritdoc} */ public function beforeTraverse(array $nodes) { // namespaces can be only top-level nodes, so we can scan them directly foreach ($nodes as $topLevelNode) { if ($topLevelNode instanceof Namespace_) { // file has namespace in it, nothing to change, returning null return null; } } // if we don't have a namespaces at all, this is global namespace, wrap everything in it, except declares $lastDeclareOffset = 0; foreach ($nodes as $lastDeclareOffset => $node) { if (!$node instanceof Declare_) { // $declareOffset now stores the position of first non-declare node statement break; } } // Wrap all statements into the namespace block $globalNamespaceNode = new Namespace_(null, array_slice($nodes, $lastDeclareOffset)); // Replace top-level nodes with namespaced node array_splice($nodes, $lastDeclareOffset, count($nodes), [$globalNamespaceNode]); return $nodes; } } src/NodeVisitor/StaticVariablesCollector.php000064400000004536146412133200015244 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\NodeVisitor; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; /** * Visitor to collect static variables in the method/function body and resolve 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; } if ($staticVariable->var->name instanceof Node\Expr) { $expressionSolver->process($staticVariable->var->name); $name = $expressionSolver->getValue(); } else { $name = $staticVariable->var->name; } $this->staticVariables[$name] = $value; } } return null; } /** * Returns an associative map of static variables in the method/function body */ public function getStaticVariables(): array { return $this->staticVariables; } } src/ReflectionAttribute.php000064400000005614146412133200012024 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use ReflectionAttribute as BaseReflectionAttribute; use PhpParser\Node; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\PropertyProperty; /** * ref original usage https://3v4l.org/duaQI */ class ReflectionAttribute extends BaseReflectionAttribute { public function __construct( private string $attributeName, private ReflectionClass|ReflectionMethod|ReflectionProperty|ReflectionClassConstant|ReflectionFunction|ReflectionParameter $reflector, private array $arguments, private bool $isRepeated, ) { } public function getNode(): Node\Attribute { /** @var Class_|ClassMethod|PropertyProperty|ClassConst|Function_|Param $node */ $node = $this->reflector->getNode(); // attrGroups only exists in Property Stmt if ($node instanceof PropertyProperty) { $node = $this->reflector->getTypeNode(); } $nodeExpressionResolver = new NodeExpressionResolver($this); foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { if ($attr->name->toString() !== $this->attributeName) { continue; } $arguments = []; foreach ($attr->args as $arg) { $nodeExpressionResolver->process($arg->value); $arguments[] = $nodeExpressionResolver->getValue(); } if ($arguments !== $this->arguments) { continue; } return $attr; } } throw new ReflectionException('ReflectionAttribute should be initiated from Go\ParserReflection Reflection classes'); } public function isRepeated(): bool { return $this->isRepeated; } /** * {@inheritDoc} */ public function getArguments(): array { return $this->arguments; } /** * {@inheritDoc} */ public function getName(): string { return $this->attributeName; } /** * {@inheritDoc} */ public function getTarget(): int { throw new \RuntimeException(sprintf('cannot get target from %s', $this::class)); } /** * {@inheritDoc} */ public function newInstance(): object { throw new \RuntimeException(sprintf('cannot create new instance from %s', $this::class)); } } src/ReflectionClass.php000064400000011506146412133200011123 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\AttributeResolverTrait; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\Traits\ReflectionClassLikeTrait; 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 InternalPropertiesEmulationTrait; use ReflectionClassLikeTrait; use AttributeResolverTrait; /** * 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) : ltrim($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 = implode('\\', $namespaceParts); $this->classLikeNode = $classLikeNode ?: ReflectionEngine::parseClass($fullClassName); } /** * Parses interfaces from the concrete class node * * @return InternalReflectionClass[] List of reflections of interfaces */ public static function collectInterfacesFromClassNode(ClassLike $classLikeNode): array { $interfaces = []; $isInterface = $classLikeNode instanceof Interface_; $interfaceField = $isInterface ? 'extends' : 'implements'; $hasInterfaces = in_array($interfaceField, $classLikeNode->getSubNodeNames(), true); $implementsList = $hasInterfaces ? $classLikeNode->$interfaceField : []; 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 array $traitAdaptations List of method adaptations * * @return InternalReflectionClass[] List of reflections of traits */ public static function collectTraitsFromClassNode(ClassLike $classLikeNode, array &$traitAdaptations): array { $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(): array { return [ 'name' => $this->getName() ]; } /** * Returns an AST-node for class */ public function getNode(): ?ClassLike { return $this->classLikeNode; } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize(): void { parent::__construct($this->getName()); } /** * Create a ReflectionClass for a given class name. * * @param string $className * The name of the class to create a reflection for. * * @return InternalReflectionClass The appropriate reflection object. */ protected function createReflectionForClass(string $className) { return class_exists($className, false) ? new parent($className) : new static($className); } } src/ReflectionClassConstant.php000064400000013324146412133200012635 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ declare(strict_types=1); namespace Go\ParserReflection; use Go\ParserReflection\Traits\AttributeResolverTrait; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node; use PhpParser\Node\Const_; use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassLike; use Reflection; use ReflectionClassConstant as BaseReflectionClassConstant; class ReflectionClassConstant extends BaseReflectionClassConstant { use InternalPropertiesEmulationTrait; use AttributeResolverTrait; /** * Concrete class constant node * * @var ClassConst */ private $classConstantNode; /** * @var Const_ */ private $constNode; /** * Name of the class * * @var string */ private $className; /** * Parses class constants from the concrete class node * * @param ClassLike $classLikeNode Class-like node * @param string $reflectionClassName FQN of the class * * @return array|ReflectionClassConstant[] */ public static function collectFromClassNode(ClassLike $classLikeNode, string $reflectionClassName): array { $classConstants = []; foreach ($classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof ClassConst) { foreach ($classLevelNode->consts as $const) { $classConstName = $const->name->toString(); $classConstants[$classConstName] = new ReflectionClassConstant( $reflectionClassName, $classConstName, $classLevelNode, $const ); } } } return $classConstants; } /** * Initializes a reflection for the class constant * * @param string $className Name of the class * @param string $classConstantName Name of the class constant to reflect * @param ?ClassConst $classConstNode ClassConstant definition node * @param Const_|null $constNode Concrete const definition node */ public function __construct( string $className, string $classConstantName, ClassConst $classConstNode = null, Const_ $constNode = null ) { $this->className = ltrim($className, '\\'); if (!$classConstNode || !$constNode) { [$classConstNode, $constNode] = ReflectionEngine::parseClassConstant($className, $classConstantName); } // Let's unset original read-only property to have a control over it via __get unset($this->name, $this->class); $this->classConstantNode = $classConstNode; $this->constNode = $constNode; } /** * Emulating original behaviour of reflection */ public function __debugInfo(): array { return [ 'name' => $this->getName(), 'class' => $this->className ]; } /** * @inheritDoc */ public function getDeclaringClass(): \ReflectionClass { return new ReflectionClass($this->className); } /** * @inheritDoc */ public function getDocComment(): string|false { $docBlock = $this->classConstantNode->getDocComment(); return $docBlock ? $docBlock->getText() : false; } /** * @inheritDoc */ public function getModifiers(): int { $modifiers = 0; if ($this->isPublic()) { $modifiers += ReflectionMethod::IS_PUBLIC; } if ($this->isProtected()) { $modifiers += ReflectionMethod::IS_PROTECTED; } if ($this->isPrivate()) { $modifiers += ReflectionMethod::IS_PRIVATE; } return $modifiers; } /** * @inheritDoc */ public function getName() { return $this->constNode->name->toString(); } /** * @inheritDoc */ public function getValue() { $solver = new NodeExpressionResolver($this->getDeclaringClass()); $solver->process($this->constNode->value); return $solver->getValue(); } /** * @inheritDoc */ public function isPrivate(): bool { return $this->classConstantNode->isPrivate(); } /** * @inheritDoc */ public function isProtected(): bool { return $this->classConstantNode->isProtected(); } /** * @inheritDoc */ public function isPublic(): bool { return $this->classConstantNode->isPublic(); } /** * @inheritDoc */ public function __toString(): string { # Starting from PHP7.3 gettype returns different names, need to remap them static $typeMap = [ 'integer' => 'int', 'boolean' => 'bool', 'double' => 'float', ]; $value = $this->getValue(); $type = gettype($value); if (isset($typeMap[$type])) { $type = $typeMap[$type]; } $valueType = new ReflectionType($type, null, true); return sprintf( "Constant [ %s %s %s ] { %s }\n", implode(' ', Reflection::getModifierNames($this->getModifiers())), strtolower((string) ReflectionType::convertToDisplayType($valueType)), $this->getName(), (string) $value ); } public function getNode(): Node\Stmt\ClassConst { return $this->classConstantNode; } } src/ReflectionEngine.php000064400000022110146412133200011254 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Instrument\PathResolver; use Go\ParserReflection\NodeVisitor\RootNamespaceNormalizer; use InvalidArgumentException; use PhpParser\Lexer; use PhpParser\Node; use PhpParser\Node\Stmt\ClassConst; 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; /** * @var array|Node[] */ protected static $parsedFiles = []; /** * @var null|int */ protected static $maximumCachedFiles; /** * @var null|Parser */ protected static $parser; /** * @var null|NodeTraverser */ protected static $traverser; /** * @var null|Lexer */ protected static $lexer; private function __construct() {} public static function init(LocatorInterface $locator): void { self::$lexer = new Lexer(['usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos' ]]); self::$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, self::$lexer); self::$traverser = $traverser = new NodeTraverser(); $traverser->addVisitor(new NameResolver()); $traverser->addVisitor(new RootNamespaceNormalizer()); self::$locator = $locator; } /** * Limits number of files, that can be cached at any given moment */ public static function setMaximumCachedFiles(int $newLimit): void { self::$maximumCachedFiles = $newLimit; if (count(self::$parsedFiles) > $newLimit) { self::$parsedFiles = array_slice(self::$parsedFiles, 0, $newLimit); } } /** * Locates a file name for class */ public static function locateClassFile(string $fullClassName): string { 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 */ public static function parseClass(string $fullClassName): ClassLike { $classFileName = self::locateClassFile($fullClassName); $namespaceParts = explode('\\', $fullClassName); $className = array_pop($namespaceParts); $namespaceName = implode('\\', $namespaceParts); // we have a namespace node somewhere $namespace = self::parseFileNamespace($classFileName, $namespaceName); $namespaceNodes = $namespace->stmts; $namespaceNode = self::findClassLikeNodeByClassName($namespaceNodes, $className); if ($namespaceNode instanceof ClassLike) { $namespaceNode->setAttribute('fileName', $classFileName); return $namespaceNode; } throw new InvalidArgumentException("Class $fullClassName was not found in the $classFileName"); } /** * Loop through an array and find a ClassLike statement by the given class name. * * If an if statement like `if (false) {` is found, the class will also be search inside that if statement. * This relies on the guide of greg0ire on how to deprecate a type. * * @see https://dev.to/greg0ire/how-to-deprecate-a-type-in-php-48cf */ protected static function findClassLikeNodeByClassName(array $nodes, string $className): ?ClassLike { foreach ($nodes as $node) { if ($node instanceof ClassLike && $node->name->toString() == $className) { return $node; } if ($node instanceof Node\Stmt\If_ && $node->cond instanceof Node\Expr\ConstFetch && isset($node->cond->name->parts[0]) && $node->cond->name->parts[0] === 'false' ) { $result = self::findClassLikeNodeByClassName($node->stmts, $className); if ($result instanceof ClassLike) { return $result; } } } return null; } /** * Parses class method */ public static function parseClassMethod(string $fullClassName, string $methodName): ClassMethod { $class = self::parseClass($fullClassName); $classNodes = $class->stmts; foreach ($classNodes as $classLevelNode) { if ($classLevelNode instanceof ClassMethod && $classLevelNode->name->toString() === $methodName) { return $classLevelNode; } } throw new InvalidArgumentException("Method $methodName was not found in the $fullClassName"); } /** * Parses class property * * @return array Pair of [Property and PropertyProperty] nodes */ public static function parseClassProperty(string $fullClassName, string $propertyName): array { $class = self::parseClass($fullClassName); $classNodes = $class->stmts; foreach ($classNodes as $classLevelNode) { if ($classLevelNode instanceof Property) { foreach ($classLevelNode->props as $classProperty) { if ($classProperty->name->toString() === $propertyName) { return [$classLevelNode, $classProperty]; } } } } throw new InvalidArgumentException("Property $propertyName was not found in the $fullClassName"); } /** * Parses class constants * * @param string $fullClassName * @param string $constantName * @return array Pair of [ClassConst and Const_] nodes */ public static function parseClassConstant(string $fullClassName, string $constantName): array { $class = self::parseClass($fullClassName); $classNodes = $class->stmts; foreach ($classNodes as $classLevelNode) { if ($classLevelNode instanceof ClassConst) { foreach ($classLevelNode->consts as $classConst) { if ($classConst->name->toString() === $constantName) { return [$classLevelNode, $classConst]; } } } } throw new InvalidArgumentException("ClassConstant $constantName was not found in the $fullClassName"); } /** * Parses a file and returns an AST for it * * @param string|null $fileContent Optional content of the file * * @return Node[] */ public static function parseFile(string $fileName, ?string $fileContent = null) { $fileName = PathResolver::realpath($fileName); if (isset(self::$parsedFiles[$fileName]) && !isset($fileContent)) { return self::$parsedFiles[$fileName]; } if (isset(self::$maximumCachedFiles) && (count(self::$parsedFiles) === self::$maximumCachedFiles)) { array_shift(self::$parsedFiles); } 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 * * @throws ReflectionException */ public static function parseFileNamespace(string $fileName, string $namespaceName): Namespace_ { $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_) { continue; } $topLevelNodeName = $topLevelNode->name ? $topLevelNode->name->toString() : ''; if (ltrim($topLevelNodeName, '\\') === trim($namespaceName, '\\')) { return $topLevelNode; } } throw new ReflectionException("Namespace $namespaceName was not found in the file $fileName"); } /** * @return Lexer */ public static function getLexer(): ?Lexer { return self::$lexer; } } src/ReflectionException.php000064400000000664146412133200012017 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use ReflectionException as BaseException; /** * General parser-level reflection exception */ class ReflectionException extends BaseException { } src/ReflectionFile.php000064400000010024146412133200010727 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Instrument\PathResolver; use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; /** * AST-based reflector for the source file */ class ReflectionFile { /** * Name of the file for reflection * * @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(string $fileName, ?array $topLevelNodes = null) { $fileName = PathResolver::realpath($fileName); $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(string $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(): array { if (!isset($this->fileNamespaces)) { $this->fileNamespaces = $this->findFileNamespaces(); } return $this->fileNamespaces; } /** * Returns the name of current reflected file */ public function getName(): string { return $this->fileName; } /** * Returns an AST-nodes for file * * @return Node[] */ public function getNodes(): ?array { return $this->topLevelNodes; } /** * Returns the presence of namespace in the file */ public function hasFileNamespace(string $namespaceName): bool { $namespaces = $this->getFileNamespaces(); return isset($namespaces[$namespaceName]); } /** * Checks if the current file is in strict mode */ public function isStrictMode(): bool { // declare statement for the strict_types can be only top-level node $topLevelNode = reset($this->topLevelNodes); if (!$topLevelNode instanceof Node\Stmt\Declare_) { return false; } $declareStatement = reset($topLevelNode->declares); $isStrictTypeKey = $declareStatement->key->toString() === 'strict_types'; $isScalarValue = $declareStatement->value instanceof Node\Scalar\LNumber; $isStrictMode = $isStrictTypeKey && $isScalarValue && $declareStatement->value->value === 1; return $isStrictMode; } /** * Searches for file namespaces in the given AST * * @return ReflectionFileNamespace[] */ private function findFileNamespaces(): array { $namespaces = []; // 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 ); } } return $namespaces; } } src/ReflectionFileNamespace.php000064400000027336146412133200012562 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Instrument\PathResolver; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Const_; use PhpParser\Node\Stmt\Expression; 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 constants in the namespace including defined via "define(...)" * * @var array */ protected $fileConstantsWithDefined; /** * 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(string $fileName, string $namespaceName, ?Namespace_ $namespaceNode = null) { $fileName = PathResolver::realpath($fileName); 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 * * @return bool|ReflectionClass */ public function getClass(string $className) { if ($this->hasClass($className)) { return $this->fileClasses[$className]; } return false; } /** * Gets list of classes in the namespace * * @return ReflectionClass[] */ public function getClasses(): array { if (!isset($this->fileClasses)) { $this->fileClasses = $this->findClasses(); } return $this->fileClasses; } /** * Returns a value for the constant * * @return bool|mixed */ public function getConstant(string $constantName) { if ($this->hasConstant($constantName)) { return $this->fileConstants[$constantName]; } return false; } /** * Returns a list of defined constants in the namespace * * @param bool $withDefined Include constants defined via "define(...)" in results. * * @return array */ public function getConstants(bool $withDefined = false): array { if ($withDefined) { if (!isset($this->fileConstantsWithDefined)) { $this->fileConstantsWithDefined = $this->findConstants(true); } return $this->fileConstantsWithDefined; } 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(): string|false { $docComment = false; $comments = $this->namespaceNode->getAttribute('comments'); if ($comments) { $docComment = (string)$comments[0]; } return $docComment; } /** * Gets starting line number */ public function getEndLine(): int { return $this->namespaceNode->getAttribute('endLine'); } /** * Returns the name of file */ public function getFileName(): string { return $this->fileName; } /** * Returns the concrete function from the file namespace or false if there is no function * * @return bool|ReflectionFunction */ public function getFunction(string $functionName) { if ($this->hasFunction($functionName)) { return $this->fileFunctions[$functionName]; } return false; } /** * Gets list of functions in the namespace * * @return ReflectionFunction[] */ public function getFunctions(): array { if (!isset($this->fileFunctions)) { $this->fileFunctions = $this->findFunctions(); } return $this->fileFunctions; } /** * Gets namespace name */ public function getName(): string { $nameNode = $this->namespaceNode->name; return $nameNode ? $nameNode->toString() : ''; } /** * Returns a list of namespace aliases */ public function getNamespaceAliases(): array { if (!isset($this->fileNamespaceAliases)) { $this->fileNamespaceAliases = $this->findNamespaceAliases(); } return $this->fileNamespaceAliases; } /** * Returns an AST-node for namespace */ public function getNode(): ?Namespace_ { return $this->namespaceNode; } /** * Helper method to access last token position for namespace * * This method is useful because namespace can be declared with braces or without them */ public function getLastTokenPosition() { $endNamespaceTokenPosition = $this->namespaceNode->getAttribute('endTokenPos'); /** @var Node $lastNamespaceNode */ $lastNamespaceNode = end($this->namespaceNode->stmts); $endStatementTokenPosition = $lastNamespaceNode->getAttribute('endTokenPos'); return max($endNamespaceTokenPosition, $endStatementTokenPosition); } /** * Gets starting line number */ public function getStartLine(): int { return $this->namespaceNode->getAttribute('startLine'); } /** * Checks if the given class is present in this file namespace */ public function hasClass(string $className): bool { $classes = $this->getClasses(); return isset($classes[$className]); } /** * Checks if the given constant is present in this file namespace */ public function hasConstant(string $constantName): bool { $constants = $this->getConstants(); return isset($constants[$constantName]); } /** * Checks if the given function is present in this file namespace */ public function hasFunction(string $functionName): bool { $functions = $this->getFunctions(); return isset($functions[$functionName]); } /** * Searches for classes in the given AST * * @return ReflectionClass[] */ private function findClasses(): array { $classes = []; $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->toString(); $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 ReflectionFunction[] */ private function findFunctions(): array { $functions = []; $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->toString(); $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 * * @param bool $withDefined Include constants defined via "define(...)" in results. * * @return array */ private function findConstants(bool $withDefined = false): array { $constants = []; $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->toString()] = $expressionSolver->getValue(); } } } } if ($withDefined) { foreach ($this->namespaceNode->stmts as $namespaceLevelNode) { if ($namespaceLevelNode instanceof Expression && $namespaceLevelNode->expr instanceof FuncCall && $namespaceLevelNode->expr->name instanceof Name && (string)$namespaceLevelNode->expr->name === 'define' ) { $functionCallNode = $namespaceLevelNode->expr; $expressionSolver->process($functionCallNode->args[0]->value); $constantName = $expressionSolver->getValue(); // Ignore constants, for which name can't be determined. if (!empty($constantName)) { $expressionSolver->process($functionCallNode->args[1]->value); $constantValue = $expressionSolver->getValue(); $constants[$constantName] = $constantValue; } } } } return $constants; } /** * Searches for namespace aliases for the current block */ private function findNamespaceAliases(): array { $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()] = (string) $useNode->getAlias(); } } } } return $namespaceAliases; } } src/ReflectionFunction.php000064400000007124146412133200011644 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Closure; use Go\ParserReflection\Traits\AttributeResolverTrait; 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 InternalPropertiesEmulationTrait; use ReflectionFunctionLikeTrait; use AttributeResolverTrait; /** * Initializes reflection instance for given AST-node * * @param string|Closure $functionName The name of the function to reflect or a closure. * @param Function_ $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 = implode('\\', $namespaceParts); $this->functionLikeNode = $functionNode; unset($this->name); } /** * Emulating original behaviour of reflection */ public function __debugInfo(): array { $nodeName = 'unknown'; if ($this->functionLikeNode instanceof Function_) { $nodeName = $this->functionLikeNode->name->toString(); } return ['name' => $nodeName]; } /** * Returns an AST-node for function */ public function getNode(): Function_ { return $this->functionLikeNode; } /** * {@inheritDoc} */ public function getClosure(): \Closure { $this->initializeInternalReflection(); return parent::getClosure(); } /** * {@inheritDoc} */ public function invoke(mixed ...$args) { $this->initializeInternalReflection(); return parent::invoke(...$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(): bool { return false; } /** * Returns textual representation of function */ public function __toString(): string { $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(), static function ($str, ReflectionParameter $param) { return $str . "\n " . $param; }, '') ); } /** * Implementation of internal reflection initialization */ protected function __initialize(): void { parent::__construct($this->getName()); } } src/ReflectionMethod.php000064400000022256146412133200011302 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\AttributeResolverTrait; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use Reflection; use ReflectionMethod as BaseReflectionMethod; /** * AST-based reflection for the method in a class */ class ReflectionMethod extends BaseReflectionMethod { use InternalPropertiesEmulationTrait; use ReflectionFunctionLikeTrait; use AttributeResolverTrait; /** * 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 = ltrim($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); } /** * Returns an AST-node for method */ public function getNode(): ClassMethod { return $this->functionLikeNode; } /** * Emulating original behaviour of reflection */ public function __debugInfo(): array { return [ 'name' => $this->getClassMethodNode()->name->toString(), 'class' => $this->className ]; } /** * Returns the string representation of the Reflection method object. * * @link http://php.net/manual/en/reflectionmethod.tostring.php */ public function __toString(): string { // Internally $this->getReturnType() !== null is the same as $this->hasReturnType() $returnType = $this->getReturnType(); $hasReturnType = $returnType !== null; $paramsNeeded = $hasReturnType || $this->getNumberOfParameters() > 0; $paramFormat = $paramsNeeded ? "\n\n - Parameters [%d] {%s\n }" : ''; $returnFormat = $hasReturnType ? "\n - Return [ %s ]" : ''; $methodParameters = $this->getParameters(); try { $prototype = $this->getPrototype(); } catch (\ReflectionException $e) { $prototype = null; } $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : ''; $paramString = ''; $indentation = str_repeat(' ', 4); foreach ($methodParameters as $methodParameter) { $paramString .= "\n{$indentation}" . $methodParameter; } return sprintf( "%sMethod [ %s%s%s %s method %s ] {\n @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n", $this->getDocComment() ? $this->getDocComment() . "\n" : '', $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '', $this->isConstructor() ? ', ctor' : '', $this->isFinal() ? ' final' : '', $this->isStatic() ? ' static' : '', $this->isAbstract() ? ' abstract' : '', implode( ' ', Reflection::getModifierNames( $this->getModifiers() & (self::IS_PUBLIC | self::IS_PROTECTED | self::IS_PRIVATE) ) ), $this->getName(), $this->getFileName(), $this->getStartLine(), $this->getEndLine(), count($methodParameters), $paramString, $returnType ? ReflectionType::convertToDisplayType($returnType) : '' ); } /** * {@inheritDoc} */ public function getClosure($object = null): \Closure { $this->initializeInternalReflection(); return parent::getClosure($object); } /** * {@inheritDoc} */ public function getDeclaringClass(): \ReflectionClass { return $this->declaringClass ?? new ReflectionClass($this->className); } /** * {@inheritDoc} */ public function getModifiers(): int { $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(): \ReflectionMethod { $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 $object, mixed ...$args): mixed { $this->initializeInternalReflection(); return parent::invoke($object, ...$args); } /** * {@inheritDoc} */ public function invokeArgs(?object $object, array $args): mixed { $this->initializeInternalReflection(); return parent::invokeArgs($object, $args); } /** * {@inheritDoc} */ public function isAbstract(): bool { return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract(); } /** * {@inheritDoc} */ public function isConstructor(): bool { return $this->getClassMethodNode()->name->toLowerString() === '__construct'; } /** * {@inheritDoc} */ public function isDestructor(): bool { return $this->getClassMethodNode()->name->toLowerString() === '__destruct'; } /** * {@inheritDoc} */ public function isFinal(): bool { return $this->getClassMethodNode()->isFinal(); } /** * {@inheritDoc} */ public function isPrivate(): bool { return $this->getClassMethodNode()->isPrivate(); } /** * {@inheritDoc} */ public function isProtected(): bool { return $this->getClassMethodNode()->isProtected(); } /** * {@inheritDoc} */ public function isPublic(): bool { return $this->getClassMethodNode()->isPublic(); } /** * {@inheritDoc} */ public function isStatic(): bool { return $this->getClassMethodNode()->isStatic(); } /** * {@inheritDoc} */ public function setAccessible($accessible): void { $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 ReflectionMethod[] */ public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass): array { $methods = []; foreach ($classLikeNode->stmts as $classLevelNode) { if ($classLevelNode instanceof ClassMethod) { $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName')); $methodName = $classLevelNode->name->toString(); $methods[$methodName] = new ReflectionMethod( $reflectionClass->name, $methodName, $classLevelNode, $reflectionClass ); } } return $methods; } /** * Implementation of internal reflection initialization */ protected function __initialize(): void { parent::__construct($this->className, $this->getName()); } /** * Returns ClassMethod node to prevent all possible type checks with instanceof */ private function getClassMethodNode(): ClassMethod { return $this->functionLikeNode; } } src/ReflectionNamedType.php000064400000002614146412133200011744 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use ReflectionNamedType as BaseReflectionNamedType; /** * ReflectionNamedType implementation */ class ReflectionNamedType extends BaseReflectionNamedType { /** * If type allows null or not * * @var bool */ 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(): bool { return $this->allowsNull; } /** * @inheritDoc */ public function isBuiltin(): bool { return $this->isBuiltin; } /** * @inheritDoc */ public function __toString(): string { return $this->type; } /** * @inheritDoc */ public function getName(): string { return $this->type; } } src/ReflectionParameter.php000064400000030206146412133200011774 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\AttributeResolverTrait; use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\BinaryOp\Concat; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\Param; use PhpParser\PrettyPrinter\Standard; use ReflectionFunctionAbstract; use ReflectionParameter as BaseReflectionParameter; /** * AST-based reflection for method/function parameter */ class ReflectionParameter extends BaseReflectionParameter { use InternalPropertiesEmulationTrait; use AttributeResolverTrait; /** * Reflection function or method * * @var ReflectionFunctionAbstract */ private $declaringFunction; /** * Stores the default value for node (if present) * * @var mixed */ private $defaultValue; /** * 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; /** * 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, true); $expressionSolver->process($this->parameterNode->default); $this->defaultValue = $expressionSolver->getValue(); $this->isDefaultValueConstant = $expressionSolver->isConstant(); $this->defaultValueConstantName = $expressionSolver->getConstantName(); } } /** * Returns an AST-node for parameter * * @return Param */ public function getNode() { return $this->parameterNode; } /** * Emulating original behaviour of reflection */ public function __debugInfo(): array { return [ 'name' => (string)$this->parameterNode->var->name, ]; } /** * Returns string representation of this parameter. * * @return string */ public function __toString(): string { $parameterType = $this->getType(); $isOptional = $this->isOptional(); $hasDefaultValue = $this->isDefaultValueAvailable(); $defaultValue = ''; if ($hasDefaultValue) { $defaultValue = $this->getDefaultValue(); if ($this->parameterNode->default instanceof Array_) { $this->parameterNode->default->setAttribute('kind', Array_::KIND_SHORT); $printer = new Standard(['shortArraySyntax' => true]); $defaultValue = $printer->prettyPrintExpr($this->parameterNode->default); } elseif (($this->parameterNode->default instanceof Concat || $this->parameterNode->default instanceof ClassConstFetch || is_string($defaultValue) ) && $this->declaringFunction instanceof \ReflectionMethod ) { if ($this->parameterNode->default instanceof ClassConstFetch && is_string($defaultValue) && str_contains($defaultValue, '\\')) { $defaultValue = str_replace('\\', '\\', var_export($defaultValue, true)); } } else { $defaultValue = var_export($defaultValue, true); } } return sprintf( 'Parameter #%d [ %s %s%s%s$%s%s ]', $this->parameterIndex, $isOptional ? '' : '', $parameterType ? ReflectionType::convertToDisplayType($parameterType) . ' ' : '', $this->isVariadic() ? '...' : '', $this->isPassedByReference() ? '&' : '', $this->getName(), ($isOptional && $hasDefaultValue) ? (' = ' . $defaultValue) : '' ); } /** * {@inheritDoc} */ public function allowsNull(): bool { // Enable 7.1 nullable types support if ($this->parameterNode->type instanceof NullableType) { return true; } $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null; if ($hasDefaultNull) { return true; } if (!isset($this->parameterNode->type)) { return true; } return $this->parameterNode->default instanceof ConstFetch && strtolower($this->parameterNode->default->name->toString()) === 'null'; } /** * {@inheritDoc} */ public function canBePassedByValue(): bool { return !$this->isPassedByReference(); } /** * @inheritDoc */ public function getClass(): ?\ReflectionClass { $parameterType = $this->parameterNode->type; if ($parameterType instanceof Name) { if (!$parameterType instanceof Name\FullyQualified) { $parameterTypeName = $parameterType->toString(); if ('self' === $parameterTypeName) { return $this->getDeclaringClass(); } if ('parent' === $parameterTypeName) { return $this->getDeclaringClass()->getParentClass(); } throw new ReflectionException("Can not resolve a class name for parameter"); } $className = $parameterType->toString(); $classOrInterfaceExists = class_exists($className, false) || interface_exists($className, false); return $classOrInterfaceExists ? new \ReflectionClass($className) : new ReflectionClass($className); } return null; } /** * {@inheritDoc} */ public function getDeclaringClass(): ?\ReflectionClass { if ($this->declaringFunction instanceof \ReflectionMethod) { return $this->declaringFunction->getDeclaringClass(); } return null; } /** * {@inheritDoc} */ public function getDeclaringFunction(): ReflectionFunctionAbstract { return $this->declaringFunction; } /** * {@inheritDoc} */ public function getDefaultValue(): mixed { if (!$this->isDefaultValueAvailable()) { throw new ReflectionException('Internal error: Failed to retrieve the default value'); } return $this->defaultValue; } /** * {@inheritDoc} */ public function getDefaultValueConstantName(): null|string { if (!$this->isDefaultValueAvailable()) { throw new ReflectionException('Internal error: Failed to retrieve the default value'); } return $this->defaultValueConstantName; } /** * @inheritDoc */ public function getName(): string { return (string)$this->parameterNode->var->name; } /** * {@inheritDoc} */ public function getPosition(): int { return $this->parameterIndex; } /** * @inheritDoc */ public function getType(): ?\ReflectionType { $isBuiltin = false; $parameterType = $this->parameterNode->type; if ($parameterType instanceof NullableType) { $parameterType = $parameterType->type; } $allowsNull = $this->allowsNull(); if ($parameterType instanceof Identifier) { $isBuiltin = true; $parameterType = $parameterType->toString(); } elseif (is_object($parameterType)) { $parameterType = $parameterType->toString(); } elseif (is_string($parameterType)) { $isBuiltin = true; } else { return null; } if ($parameterType === 'iterable') { $parameterType = 'Traversable|array'; } return new ReflectionNamedType($parameterType, $allowsNull, $isBuiltin); } /** * @inheritDoc */ public function hasType(): bool { $hasType = isset($this->parameterNode->type); return $hasType; } /** * @inheritDoc */ public function isArray(): bool { $type = $this->parameterNode->type; return ($type instanceof Identifier) && 'array' === $type->name; } /** * @inheritDoc */ public function isCallable(): bool { $type = $this->parameterNode->type; return ($type instanceof Identifier) && 'callable' === $type->name; } /** * @inheritDoc */ public function isDefaultValueAvailable(): bool { if (! isset($this->parameterNode->default)) { return false; } // start from PHP 8.1, isDefaultValueAvailable() returns false if next parameter is required // see https://github.com/php/php-src/issues/8090 $parameters = $this->declaringFunction->getNode()->getParams(); for ($key = $this->parameterIndex + 1; $key < count($parameters); ++$key) { if (! $parameters[$key]->default instanceof Expr) { return false; } } return true; } /** * {@inheritDoc} */ public function isDefaultValueConstant(): bool { return $this->isDefaultValueConstant; } /** * {@inheritDoc} */ public function isOptional(): bool { return $this->isVariadic() || ($this->isDefaultValueAvailable() && $this->haveSiblingsDefaultValues()); } /** * @inheritDoc */ public function isPassedByReference(): bool { return (bool) $this->parameterNode->byRef; } /** * @inheritDoc */ public function isVariadic(): bool { return (bool) $this->parameterNode->variadic; } /** * Returns if all following parameters have a default value definition. * * @throws ReflectionException If could not fetch declaring function reflection */ protected function haveSiblingsDefaultValues(): bool { $function = $this->getDeclaringFunction(); if (null === $function) { throw new ReflectionException('Could not get the declaring function reflection.'); } /** @var BaseReflectionParameter[] $remainingParameters */ $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1); foreach ($remainingParameters as $reflectionParameter) { if (!$reflectionParameter->isDefaultValueAvailable()) { return false; } } return true; } } src/ReflectionProperty.php000064400000017045146412133200011706 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use Go\ParserReflection\Traits\AttributeResolverTrait; 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 Reflection; use ReflectionProperty as BaseReflectionProperty; /** * AST-based reflection for class property */ class ReflectionProperty extends BaseReflectionProperty { use InitializationTrait; use InternalPropertiesEmulationTrait; use AttributeResolverTrait; /** * 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 = ltrim($className, '\\'); if (!$propertyType || !$propertyNode) { [$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); } /** * Returns an AST-node for property * * @return PropertyProperty */ public function getNode() { return $this->propertyNode; } /** * Returns an AST-node for property type * * @return Property */ public function getTypeNode() { return $this->propertyTypeNode; } /** * Emulating original behaviour of reflection */ public function __debugInfo(): array { return [ 'name' => isset($this->propertyNode) ? $this->propertyNode->name->toString() : 'unknown', 'class' => $this->className ]; } /** * Return string representation of this little old property. */ public function __toString(): string { $defaultValueDisplay = ''; if ($this->isDefault()) { $this->__initialize(); $defaultValue = $this->getDefaultValue(); $originalDisplay = parent::__toString(); // @see https://3v4l.org/7q1LT $list = explode(' = ', $originalDisplay); array_shift($list); $defaultValueFromDisplay = implode(' = ', $list); $defaultValueDisplay = '= ' . rtrim($defaultValueFromDisplay, ' ]' . PHP_EOL); if (is_array($defaultValue) && str_starts_with($defaultValueDisplay, '= [')) { $defaultValueDisplay .= ']'; } } return sprintf( "Property [ %s $%s %s ]\n", implode(' ', Reflection::getModifierNames($this->getModifiers())), $this->getName(), $defaultValueDisplay ); } /** * {@inheritDoc} */ public function getDeclaringClass(): \ReflectionClass { return new ReflectionClass($this->className); } /** * @inheritDoc */ public function getDocComment(): string|false { $docBlock = $this->propertyTypeNode->getDocComment(); return $docBlock ? $docBlock->getText() : false; } /** * {@inheritDoc} */ public function getModifiers(): int { $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(): string { return $this->propertyNode->name->toString(); } /** * @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(): bool { // TRUE if the property was declared at compile-time return true; } /** * {@inheritDoc} */ public function isPrivate(): bool { return $this->propertyTypeNode->isPrivate(); } /** * {@inheritDoc} */ public function isProtected(): bool { return $this->propertyTypeNode->isProtected(); } /** * {@inheritDoc} */ public function isPublic(): bool { return $this->propertyTypeNode->isPublic(); } /** * {@inheritDoc} */ public function isStatic(): bool { return $this->propertyTypeNode->isStatic(); } /** * {@inheritDoc} */ public function setAccessible($accessible): void { $this->initializeInternalReflection(); parent::setAccessible($accessible); } /** * @inheritDoc */ public function setValue($object, $value = null): void { $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->toString(); $properties[$propertyName] = new static( $fullClassName, $propertyName, $classLevelNode, $classPropertyNode ); } } } return $properties; } /** * Implementation of internal reflection initialization * * @return void */ protected function __initialize(): void { parent::__construct($this->className, $this->getName()); } } src/ReflectionType.php000064400000003766146412133200011010 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use ReflectionNamedType; use ReflectionType as BaseReflectionType; use ReflectionUnionType; /** * ReflectionType implementation */ class ReflectionType extends BaseReflectionType { /** * If type allows null or not * * @var bool */ 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(): bool { return $this->isBuiltin; } /** * @inheritDoc */ public function __toString(): string { return $this->type; } /** * PHP reflection has it's own rules, so 'int' type will be displayed as 'integer', etc... * * @see https://3v4l.org/nZFiT * * @param BaseReflectionType $type Type to display * * @return string */ public static function convertToDisplayType(BaseReflectionType $type) { if ($type instanceof ReflectionNamedType) { $displayType = $type->getName(); } else { $displayType = (string)$type; } $displayType = ltrim($displayType, '\\'); if ($type->allowsNull() && ! $type instanceof ReflectionUnionType) { $displayType = '?' . $displayType; } return $displayType; } } src/Traits/AttributeResolverTrait.php000064400000004234146412133200014002 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; use Go\ParserReflection\ReflectionAttribute; use Go\ParserReflection\ReflectionProperty; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; trait AttributeResolverTrait { /** * @return ReflectionAttribute[] */ public function getAttributes(?string $name = null, int $flags = 0): array { if ($this instanceof ReflectionProperty) { $node = $this->getTypeNode(); } else { $node = $this->getNode(); } $node = $this->getNode(); $attributes = []; $nodeExpressionResolver = new NodeExpressionResolver($this); foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { $arguments = []; foreach ($attr->args as $arg) { $nodeExpressionResolver->process($arg->value); $arguments[] = $nodeExpressionResolver->getValue(); } if ($name === null) { $attributes[] = new ReflectionAttribute($attr->name->toString(), $this, $arguments, $this->isAttributeRepeated($attr->name->toString(), $node->attrGroups)); continue; } if ($name !== $attr->name->toString()) { continue; } $attributes[] = new ReflectionAttribute($name, $this, $arguments, $this->isAttributeRepeated($name, $node->attrGroups)); } } return $attributes; } private function isAttributeRepeated(string $attributeName, array $attrGroups): bool { $count = 0; foreach ($attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { if ($attr->name->toString() === $attributeName) { ++$count; } } } return $count >= 2; } } src/Traits/InitializationTrait.php000064400000002063146412133200013302 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; trait InitializationTrait { /** * Is internal reflection is initialized or not * * @var bool */ private $isInitialized = false; /** * Initializes internal reflection for calling misc runtime methods */ public function initializeInternalReflection(): void { if (!$this->isInitialized) { $this->__initialize(); $this->isInitialized = true; } } /** * Returns the status of initialization status for internal object */ public function __isInitialized(): bool { return $this->isInitialized; } /** * Implementation of internal reflection initialization * * @return void */ abstract protected function __initialize(): void; } src/Traits/InternalPropertiesEmulationTrait.php000064400000002130146412133200016015 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; /** * Class for emulating internal properties behaviour */ trait InternalPropertiesEmulationTrait { /** * Magic method that should be defined to provide an info about internal properties * * @return array */ abstract public function __debugInfo(): array; /** * Magic implementation of properties * * @param string $propertyName Name of the property to return * * @return null|mixed */ public function __get(string $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.php000064400000071375146412133200014214 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; use Closure; use Go\ParserReflection\ReflectionClass; use Go\ParserReflection\ReflectionClassConstant; 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; use ReflectionExtension; use ReflectionObject; use RuntimeException; use function func_num_args; /** * 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; /** * @var array|ReflectionClassConstant[] */ protected $classConstants; /** * Returns the string representation of the ReflectionClass object. */ public function __toString(): string { $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 = static function (array $items, $indentLevel = 4) { if (!count($items)) { return ''; } $indent = "\n" . str_repeat(' ', $indentLevel); return $indent . implode($indent, explode("\n", implode("\n", $items))); }; $buildConstants = static 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): mixed { if ($this->hasConstant($name)) { return $this->constants[$name]; } return false; } /** * {@inheritDoc} */ public function getConstants(?int $filter = null): array { if (!isset($this->constants)) { $this->constants = $this->recursiveCollect( function (array &$result, \ReflectionClass $instance) { $result += $instance->getConstants(); } ); $this->collectSelfConstants(); } return $this->constants; } /** * {@inheritDoc} */ public function getConstructor(): ?ReflectionMethod { $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(): array { $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(): string|false { $docComment = $this->classLikeNode->getDocComment(); return $docComment ? $docComment->getText() : false; } public function getEndLine(): int { return $this->classLikeNode->getAttribute('endLine'); } public function getExtension(): ?ReflectionExtension { return null; } public function getExtensionName(): string|false { return false; } public function getFileName(): string|false { return $this->classLikeNode->getAttribute('fileName'); } /** * {@inheritDoc} */ public function getInterfaceNames(): array { return array_keys($this->getInterfaces()); } /** * {@inheritDoc} */ public function getInterfaces(): array { 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} */ #[\ReturnTypeWillChange] public function getMethod(string $name) { $methods = $this->getMethods(); foreach ($methods as $method) { if ($method->getName() === $name) { return $method; } } return false; } /** * Returns list of reflection methods * * @param null|int $filter Optional filter * * @return \ReflectionMethod[] */ public function getMethods($filter = null): array { 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 */ public function getModifiers(): int { $modifiers = 0; if ($this->isFinal()) { $modifiers += \ReflectionClass::IS_FINAL; } 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(): string { $namespaceName = $this->namespaceName ? $this->namespaceName . '\\' : ''; return $namespaceName . $this->getShortName(); } /** * {@inheritDoc} */ public function getNamespaceName(): string { return $this->namespaceName; } /** * {@inheritDoc} */ public function getParentClass(): \ReflectionClass|false { if (!isset($this->parentClass)) { static $extendsField = 'extends'; $parentClass = false; $hasExtends = in_array($extendsField, $this->classLikeNode->getSubNodeNames(), true); $extendsNode = $hasExtends ? $this->classLikeNode->$extendsField : null; if ($extendsNode instanceof FullyQualified) { $extendsName = $extendsNode->toString(); $parentClass = $this->createReflectionForClass($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 ReflectionProperty[] */ public function getProperties($filter = null): array { 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} */ #[\ReturnTypeWillChange] public function getProperty(string $name) { $properties = $this->getProperties(); foreach ($properties as $property) { if ($property->getName() === $name) { return $property; } } return false; } /** * @inheritDoc */ public function getReflectionConstant($name): \ReflectionClassConstant|false { $classConstants = $this->getReflectionConstants(); foreach ($classConstants as $classConstant) { if ($classConstant->getName() === $name) { return $classConstant; } } return false; } /** * @inheritDoc */ public function getReflectionConstants(?int $filter = null): array { if (!isset($this->classConstants)) { $directClassConstants = ReflectionClassConstant::collectFromClassNode( $this->classLikeNode, $this->getName() ); $parentClassConstants = $this->recursiveCollect( function (array &$result, \ReflectionClass $instance, $isParent) { $reflectionClassConstants = []; foreach ($instance->getReflectionConstants() as $reflectionClassConstant) { if (!$isParent || !$reflectionClassConstant->isPrivate()) { $reflectionClassConstants[$reflectionClassConstant->name] = $reflectionClassConstant; } } $result += $reflectionClassConstants; } ); $classConstants = $directClassConstants + $parentClassConstants; $this->classConstants = $classConstants; } return array_values($this->classConstants); } /** * {@inheritDoc} */ public function getShortName(): string { return $this->className; } public function getStartLine(): int { if ($this->classLikeNode->attrGroups !== []) { $attrGroups = $this->classLikeNode->attrGroups; $lastAttrGroupsEndLine = end($attrGroups)->getAttribute('endLine'); return $lastAttrGroupsEndLine + 1; } 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(): array { $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(): array { return array_keys($this->getTraits()); } /** * Returns an array of traits used by this class * * @link http://php.net/manual/en/reflectionclass.gettraits.php * * @return \ReflectionClass[] */ public function getTraits(): array { if (!isset($this->traits)) { $traitAdaptations = []; $this->traits = ReflectionClass::collectTraitsFromClassNode( $this->classLikeNode, $traitAdaptations ); $this->traitAdaptations = $traitAdaptations; } return $this->traits; } /** * {@inheritDoc} */ public function hasConstant($name): bool { $constants = $this->getConstants(); $hasConstant = isset($constants[$name]) || array_key_exists($name, $constants); return $hasConstant; } /** * {@inheritdoc} * @param string $name */ public function hasMethod($name): bool { $methods = $this->getMethods(); foreach ($methods as $method) { if ($method->getName() === $name) { return true; } } return false; } /** * {@inheritdoc} */ public function hasProperty($name): bool { $properties = $this->getProperties(); foreach ($properties as $property) { if ($property->getName() === $name) { return true; } } return false; } /** * {@inheritDoc} * @param string $interfaceName */ public function implementsInterface($interfaceName): bool { $allInterfaces = $this->getInterfaces(); return isset($allInterfaces[$interfaceName]); } /** * {@inheritDoc} */ public function inNamespace(): bool { return !empty($this->namespaceName); } /** * {@inheritDoc} */ public function isAbstract(): bool { if ($this->classLikeNode instanceof Class_ && $this->classLikeNode->isAbstract()) { return true; } if ($this->isInterface() && !empty($this->getMethods())) { return true; } return false; } /** * Currently, anonymous classes aren't supported for parsed reflection */ public function isAnonymous(): bool { return false; } /** * {@inheritDoc} */ public function isCloneable(): bool { if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) { return false; } if ($this->hasMethod('__clone')) { return $this->getMethod('__clone') ->isPublic() ; } return true; } /** * {@inheritDoc} */ public function isFinal(): bool { $isFinal = $this->classLikeNode instanceof Class_ && $this->classLikeNode->isFinal(); return $isFinal; } /** * {@inheritDoc} */ public function isInstance($object): bool { 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(): bool { if ($this->isInterface() || $this->isTrait() || $this->isAbstract()) { return false; } if (null === ($constructor = $this->getConstructor())) { return true; } return $constructor->isPublic(); } /** * {@inheritDoc} */ public function isInterface(): bool { return $this->classLikeNode instanceof Interface_; } /** * {@inheritDoc} */ public function isInternal(): bool { // never can be an internal method return false; } /** * {@inheritDoc} */ public function isIterateable(): bool { return $this->implementsInterface('Traversable'); } /** * {@inheritDoc} */ public function isSubclassOf($class): bool { if (is_object($class)) { if ($class instanceof ReflectionClass) { $className = $class->name; } else { $className = get_class($class); } } else { $className = $class; } if (!$this->classLikeNode instanceof Class_) { return false; } $extends = $this->classLikeNode->extends; if ($extends && $extends->toString() === $className) { return true; } $parent = $this->getParentClass(); return false === $parent ? false : $parent->isSubclassOf($class); } /** * {@inheritDoc} */ public function isTrait(): bool { return $this->classLikeNode instanceof Trait_; } /** * {@inheritDoc} */ public function isUserDefined(): bool { // 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(): array { // In runtime static properties can be changed in any time if ($this->__isInitialized()) { return parent::getStaticProperties(); } $properties = []; $reflectionProperties = $this->getProperties(ReflectionProperty::IS_STATIC); foreach ($reflectionProperties as $reflectionProperty) { if (!$reflectionProperty instanceof ReflectionProperty && !$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 * * @throws ReflectionException If there is no such property and no default value was given */ public function getStaticPropertyValue($name, $default = null): mixed { $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 * * Signature was hacked to support both 5.6, 7.1.x and 7.2.0 versions * @see https://3v4l.org/hW9O9 * @see https://3v4l.org/sWT3j * @see https://3v4l.org/eeVf8 * * @param mixed $arg First argument * @param mixed $args Accepts a variable number of arguments which are passed to the class constructor */ public function newInstance($arg = null, ...$args): object { $args = array_slice(array_merge([$arg], $args), 0, func_num_args()); $this->initializeInternalReflection(); return parent::newInstance(...$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. */ public function newInstanceArgs(array $args = []): ?object { $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 */ public function newInstanceWithoutConstructor(): object { $function = __FUNCTION__; $this->initializeInternalReflection(); return parent::$function(); } /** * 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): void { $this->initializeInternalReflection(); parent::setStaticPropertyValue($name, $value); } private function recursiveCollect(Closure $collector): array { $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; } /** * Collects list of constants from the class itself */ private function collectSelfConstants() { $expressionSolver = new NodeExpressionResolver($this); $localConstants = []; // 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); $localConstants[$nodeConstant->name->toString()] = $expressionSolver->getValue(); $this->constants = $localConstants + $this->constants; } } } } } /** * Create a ReflectionClass for a given class name. * * @param string $className * The name of the class to create a reflection for. * * @return ReflectionClass * The appropriate reflection object. */ abstract protected function createReflectionForClass(string $className); } src/Traits/ReflectionFunctionLikeTrait.php000064400000020332146412133200014717 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Traits; use Go\ParserReflection\NodeVisitor\GeneratorDetector; use Go\ParserReflection\NodeVisitor\StaticVariablesCollector; use Go\ParserReflection\ReflectionNamedType; use Go\ParserReflection\ReflectionParameter; use PhpParser\Node\Expr\Closure; use PhpParser\Node\FunctionLike; use PhpParser\Node\Identifier; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\NodeTraverser; use ReflectionException; use ReflectionExtension; /** * 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(): ?\ReflectionClass { $this->initializeInternalReflection(); return parent::getClosureScopeClass(); } /** * {@inheritDoc} */ public function getClosureThis(): ?object { $this->initializeInternalReflection(); return parent::getClosureThis(); } public function getDocComment(): string|false { $docComment = $this->functionLikeNode->getDocComment(); return $docComment ? $docComment->getText() : false; } public function getEndLine(): int { return $this->functionLikeNode->getAttribute('endLine'); } public function getExtension(): ?ReflectionExtension { return null; } public function getExtensionName(): string|false { return false; } public function getFileName(): string|false { return $this->functionLikeNode->getAttribute('fileName'); } /** * {@inheritDoc} */ public function getName(): string { if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { $functionName = $this->functionLikeNode->name->toString(); return $this->namespaceName ? $this->namespaceName . '\\' . $functionName : $functionName; } return ''; } /** * {@inheritDoc} */ public function getNamespaceName(): string { 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 */ public function getNumberOfParameters(): int { return count($this->functionLikeNode->getParams()); } /** * Get the number of required parameters that a function defines. * * @link http://php.net/manual/en/reflectionfunctionabstract.getnumberofrequiredparameters.php */ public function getNumberOfRequiredParameters(): int { $requiredParameters = 0; foreach ($this->getParameters() as $parameter) { if (!$parameter->isOptional()) { $requiredParameters++; } } return $requiredParameters; } /** * {@inheritDoc} */ public function getParameters(): array { if (!isset($this->parameters)) { $parameters = []; foreach ($this->functionLikeNode->getParams() as $parameterIndex => $parameterNode) { $reflectionParameter = new ReflectionParameter( $this->getName(), (string)$parameterNode->var->name, $parameterNode, $parameterIndex, $this ); $parameters[] = $reflectionParameter; } $this->parameters = $parameters; } return $this->parameters; } /** * Gets the specified return type of a function * * @link http://php.net/manual/en/reflectionfunctionabstract.getreturntype.php */ public function getReturnType(): ?\ReflectionType { $isBuiltin = false; $returnType = $this->functionLikeNode->getReturnType(); $isNullable = $returnType instanceof NullableType; if ($isNullable) { $returnType = $returnType->type; } if ($returnType instanceof Identifier) { $isBuiltin = true; $returnType = $returnType->toString(); } elseif (is_object($returnType)) { $returnType = $returnType->toString(); } elseif (is_string($returnType)) { $isBuiltin = true; } else { return null; } if ($returnType === 'iterable') { $returnType = 'Traversable|array'; } return new ReflectionNamedType($returnType, $isNullable, $isBuiltin); } /** * {@inheritDoc} */ public function getShortName(): string { if ($this->functionLikeNode instanceof Function_ || $this->functionLikeNode instanceof ClassMethod) { return $this->functionLikeNode->name->toString(); } throw new ReflectionException('unable to get short name'); } public function getStartLine(): int { if ($this->functionLikeNode->attrGroups !== []) { $attrGroups = $this->functionLikeNode->attrGroups; $lastAttrGroupsEndLine = end($attrGroups)->getAttribute('endLine'); return $lastAttrGroupsEndLine + 1; } return $this->functionLikeNode->getAttribute('startLine'); } /** * {@inheritDoc} */ public function getStaticVariables(): array { $nodeTraverser = new NodeTraverser(); $variablesCollector = new StaticVariablesCollector($this); $nodeTraverser->addVisitor($variablesCollector); /* @see https://github.com/nikic/PHP-Parser/issues/235 */ $nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: []); 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(): bool { $returnType = $this->functionLikeNode->getReturnType(); return isset($returnType); } /** * {@inheritDoc} */ public function inNamespace(): bool { return !empty($this->namespaceName); } /** * {@inheritDoc} */ public function isClosure(): bool { return $this->functionLikeNode instanceof Closure; } /** * {@inheritDoc} */ public function isDeprecated(): bool { // user-land method/function/closure can not be deprecated return false; } /** * {@inheritDoc} */ public function isGenerator(): bool { $nodeTraverser = new NodeTraverser(); $nodeDetector = new GeneratorDetector(); $nodeTraverser->addVisitor($nodeDetector); /* @see https://github.com/nikic/PHP-Parser/issues/235 */ $nodeTraverser->traverse($this->functionLikeNode->getStmts() ?: []); return $nodeDetector->isGenerator(); } /** * {@inheritDoc} */ public function isInternal(): bool { // never can be an internal method return false; } /** * {@inheritDoc} */ public function isUserDefined(): bool { // always defined by user, because we parse the source code return true; } /** * {@inheritDoc} */ public function isVariadic(): bool { foreach ($this->getParameters() as $parameter) { if ($parameter->isVariadic()) { return true; } } return false; } /** * {@inheritDoc} */ public function returnsReference(): bool { return $this->functionLikeNode->returnsByRef(); } } src/ValueResolver/NodeExpressionResolver.php000064400000040325146412133200015331 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\ValueResolver; use Go\ParserReflection\ReflectionClass; use Go\ParserReflection\ReflectionException; use Go\ParserReflection\ReflectionFileNamespace; use PhpParser\Node; use PhpParser\Node\Const_; use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Param; use PhpParser\Node\Scalar\DNumber; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\MagicConst\Line; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\PropertyProperty; use PhpParser\PrettyPrinter\Standard; use ReflectionFunctionAbstract; use ReflectionMethod; /** * 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 ?string */ private $constantName; /** * Current reflection context for parsing * * @var mixed|ReflectionClass */ 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; private bool $isParameter; public function __construct($context, bool $isParameter = false) { $this->context = $context; $this->isParameter = $isParameter; } public function getConstantName(): ?string { return $this->constantName; } public function getValue() { return $this->value; } public function isConstant(): bool { return $this->isConstant; } public function process(Node $node): void { // Unwrap "expr;" statements. if ($node instanceof Expression) { $node = $node->expr; } $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; $methodName = $this->getDispatchMethodFor($node); if (method_exists($this, $methodName)) { $value = $this->$methodName($node); } } finally { --$this->nodeLevel; } return $value; } protected function resolveScalarDNumber(DNumber $node): float { return $node->value; } protected function resolveScalarLNumber(LNumber $node): int { return $node->value; } protected function resolveScalarString(String_ $node): string { return $node->value; } protected function resolveScalarMagicConstMethod(): string { if ($this->context instanceof ReflectionMethod) { $fullName = $this->context->getDeclaringClass()->name . '::' . $this->context->getShortName(); return $fullName; } return ''; } protected function resolveScalarMagicConstFunction(): string { if ($this->context instanceof ReflectionFunctionAbstract) { return $this->context->getName(); } return ''; } protected function resolveScalarMagicConstNamespace(): string { if (method_exists($this->context, 'getNamespaceName')) { return $this->context->getNamespaceName(); } if ($this->context instanceof ReflectionFileNamespace) { return $this->context->getName(); } return ''; } protected function resolveScalarMagicConstClass(): string { if ($this->context instanceof \ReflectionClass) { return $this->context->name; } if (method_exists($this->context, 'getDeclaringClass')) { $declaringClass = $this->context->getDeclaringClass(); if ($declaringClass instanceof \ReflectionClass) { return $declaringClass->name; } } return ''; } protected function resolveScalarMagicConstDir(): string { if (method_exists($this->context, 'getFileName')) { return dirname($this->context->getFileName()); } return ''; } protected function resolveScalarMagicConstFile(): string { if (method_exists($this->context, 'getFileName')) { return $this->context->getFileName(); } return ''; } protected function resolveScalarMagicConstLine(Line $node): int { return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0; } protected function resolveScalarMagicConstTrait(): string { if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) { return $this->context->name; } return ''; } protected function resolveExprConstFetch(Expr\ConstFetch $node) { $constantValue = null; $isResolved = false; $isFQNConstant = $node->name instanceof Node\Name\FullyQualified; $constantName = $node->name->toString(); if (!$isFQNConstant && method_exists($this->context, 'getFileName')) { $fileName = $this->context->getFileName(); $namespaceName = $this->resolveScalarMagicConstNamespace(); $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; if ($this->isParameter) { if (isset($namespaceName)) { return $namespaceName . '\\' . $this->constantName; } return $this->constantName; } } return $constantValue; } protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node) { $classToReflect = $node->class; if (!($classToReflect instanceof Node\Name)) { $classToReflect = $this->resolve($classToReflect) ?: $classToReflect; if (!is_string($classToReflect)) { $reason = 'Unable'; if ($classToReflect instanceof Expr) { $methodName = $this->getDispatchMethodFor($classToReflect); $reason = "Method " . __CLASS__ . "::{$methodName}() not found trying"; } throw new ReflectionException("$reason to resolve class constant."); } // Strings evaluated as class names are always treated as fully // qualified. $classToReflect = new Node\Name\FullyQualified(ltrim($classToReflect, '\\')); } $refClass = $this->fetchReflectionClass($classToReflect); $constantName = ($node->name instanceof Expr\Error) ? '' : $node->name->toString(); // special handling of ::class constants if ('class' === $constantName) { return $refClass->getName(); } if ($node->class instanceof Name && $node->class->isSpecialClassName() && $this->isParameter) { $this->isConstant = true; $this->constantName = $node->class . '::' . $constantName; return $this->constantName; } $this->isConstant = true; $this->constantName = $classToReflect . '::' . $constantName; return $refClass->getConstant($constantName); } protected function resolveExprArray(Expr\Array_ $node): array { $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; } protected function resolveExprBinaryOpPlus(Expr\BinaryOp\Plus $node) { return $this->resolve($node->left) + $this->resolve($node->right); } protected function resolveExprBinaryOpMinus(Expr\BinaryOp\Minus $node) { return $this->resolve($node->left) - $this->resolve($node->right); } protected function resolveExprBinaryOpMul(Expr\BinaryOp\Mul $node) { return $this->resolve($node->left) * $this->resolve($node->right); } protected function resolveExprBinaryOpPow(Expr\BinaryOp\Pow $node) { return $this->resolve($node->left) ** $this->resolve($node->right); } protected function resolveExprBinaryOpDiv(Expr\BinaryOp\Div $node) { return $this->resolve($node->left) / $this->resolve($node->right); } protected function resolveExprBinaryOpMod(Expr\BinaryOp\Mod $node): int { return $this->resolve($node->left) % $this->resolve($node->right); } protected function resolveExprBooleanNot(Expr\BooleanNot $node): bool { return !$this->resolve($node->expr); } protected function resolveExprBitwiseNot(Expr\BitwiseNot $node) { return ~$this->resolve($node->expr); } protected function resolveExprBinaryOpBitwiseOr(Expr\BinaryOp\BitwiseOr $node): int { return $this->resolve($node->left) | $this->resolve($node->right); } protected function resolveExprBinaryOpBitwiseAnd(Expr\BinaryOp\BitwiseAnd $node): int { return $this->resolve($node->left) & $this->resolve($node->right); } protected function resolveExprBinaryOpBitwiseXor(Expr\BinaryOp\BitwiseXor $node): int { return $this->resolve($node->left) ^ $this->resolve($node->right); } protected function resolveExprBinaryOpShiftLeft(Expr\BinaryOp\ShiftLeft $node): int { return $this->resolve($node->left) << $this->resolve($node->right); } protected function resolveExprBinaryOpShiftRight(Expr\BinaryOp\ShiftRight $node): int { return $this->resolve($node->left) >> $this->resolve($node->right); } protected function resolveExprBinaryOpConcat(Expr\BinaryOp\Concat $node): string { if ($this->context instanceof \ReflectionClass && $this->isParameter) { $printer = new Standard(); return $printer->prettyPrintExpr($node); } return $this->resolve($node->left) . $this->resolve($node->right); } protected function resolveExprTernary(Expr\Ternary $node) { if (isset($node->if)) { // Full syntax $a ? $b : $c; return $this->resolve($node->cond) ? $this->resolve($node->if) : $this->resolve($node->else); } return $this->resolve($node->cond) ?: $this->resolve($node->else); } protected function resolveExprBinaryOpSmallerOrEqual(Expr\BinaryOp\SmallerOrEqual $node): bool { return $this->resolve($node->left) <= $this->resolve($node->right); } protected function resolveExprBinaryOpGreaterOrEqual(Expr\BinaryOp\GreaterOrEqual $node): bool { return $this->resolve($node->left) >= $this->resolve($node->right); } protected function resolveExprBinaryOpEqual(Expr\BinaryOp\Equal $node): bool { /** @noinspection TypeUnsafeComparisonInspection */ return $this->resolve($node->left) == $this->resolve($node->right); } protected function resolveExprBinaryOpNotEqual(Expr\BinaryOp\NotEqual $node): bool { /** @noinspection TypeUnsafeComparisonInspection */ return $this->resolve($node->left) != $this->resolve($node->right); } protected function resolveExprBinaryOpSmaller(Expr\BinaryOp\Smaller $node): bool { return $this->resolve($node->left) < $this->resolve($node->right); } protected function resolveExprBinaryOpGreater(Expr\BinaryOp\Greater $node): bool { return $this->resolve($node->left) > $this->resolve($node->right); } protected function resolveExprBinaryOpIdentical(Expr\BinaryOp\Identical $node): bool { return $this->resolve($node->left) === $this->resolve($node->right); } protected function resolveExprBinaryOpNotIdentical(Expr\BinaryOp\NotIdentical $node): bool { return $this->resolve($node->left) !== $this->resolve($node->right); } protected function resolveExprBinaryOpBooleanAnd(Expr\BinaryOp\BooleanAnd $node): bool { return $this->resolve($node->left) && $this->resolve($node->right); } protected function resolveExprBinaryOpLogicalAnd(Expr\BinaryOp\LogicalAnd $node): bool { return $this->resolve($node->left) and $this->resolve($node->right); } protected function resolveExprBinaryOpBooleanOr(Expr\BinaryOp\BooleanOr $node): bool { return $this->resolve($node->left) || $this->resolve($node->right); } protected function resolveExprBinaryOpLogicalOr(Expr\BinaryOp\LogicalOr $node): bool { return $this->resolve($node->left) or $this->resolve($node->right); } protected function resolveExprBinaryOpLogicalXor(Expr\BinaryOp\LogicalXor $node): bool { return $this->resolve($node->left) xor $this->resolve($node->right); } private function getDispatchMethodFor(Node $node): string { $nodeType = $node->getType(); return 'resolve' . str_replace('_', '', $nodeType); } /** * 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) { // check to see if the class is already loaded and is safe to use // PHP's ReflectionClass to determine if the class is user defined if (class_exists($className, false)) { $refClass = new \ReflectionClass($className); if (!$refClass->isUserDefined()) { return $refClass; } } return new ReflectionClass($className); } if ('self' === $className) { if ($this->context instanceof \ReflectionClass) { return $this->context; } if (method_exists($this->context, 'getDeclaringClass')) { return $this->context->getDeclaringClass(); } } if ('parent' === $className) { if ($this->context instanceof \ReflectionClass) { return $this->context->getParentClass(); } if (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->resolveScalarMagicConstNamespace(); $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); return $fileNamespace->getClass($className); } throw new ReflectionException("Can not resolve class $className"); } } src/bootstrap.php000064400000000767146412133200010067 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ use Go\ParserReflection\Locator\ComposerLocator; use Go\ParserReflection\ReflectionEngine; /** * This file is used for automatic configuration of * Go\ParserReflection\ReflectionEngine class */ ReflectionEngine::init(new ComposerLocator()); tests/AbstractTestCase.php000064400000006323146412133200011616 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection; use PHPUnit\Framework\TestCase; use Go\ParserReflection\Stub\AbstractClassWithMethods; abstract class AbstractTestCase extends TestCase { const DEFAULT_STUB_FILENAME = '/Stub/FileWithClasses55.php'; /** * @var ReflectionFileNamespace */ protected $parsedRefFileNamespace; /** * @var ReflectionClass */ protected $parsedRefClass; /** * Name of the class to compare * * @var string */ protected static $reflectionClassToTest = \Reflection::class; /** * Name of the class to load for default tests * * @var string */ protected static $defaultClassToLoad = AbstractClassWithMethods::class; #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testCoverAllMethods() { $allInternalMethods = get_class_methods(static::$reflectionClassToTest); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(__NAMESPACE__ . '\\' . static::$reflectionClassToTest, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, __NAMESPACE__) !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . join(', ', $allMissedMethods) . ' are not implemented'); } } /** * Provides a list of files for analysis * * @return array */ public static function getFilesToAnalyze() { $files = ['PHP5.5' => [__DIR__ . '/Stub/FileWithClasses55.php']]; $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithClasses56.php']; $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithClasses70.php']; $files['PHP7.1'] = [__DIR__ . '/Stub/FileWithClasses71.php']; return $files; } /** * Returns list of ReflectionMethod getters that be checked directly without additional arguments * * @return array */ abstract static protected function getGettersToCheck(); /** * Setups file for parsing * * @param string $fileName File to use */ protected function setUpFile($fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); $parsedFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub'); $this->parsedRefFileNamespace = $parsedFileNamespace; $this->parsedRefClass = $parsedFileNamespace->getClass(static::$defaultClassToLoad); include_once $fileName; } protected function setUp(): void { $this->setUpFile(__DIR__ . self::DEFAULT_STUB_FILENAME); } } tests/Locator/CallableLocatorTest.php000064400000000764146412133200013710 0ustar00assertSame('class.php', $locator->locateClass('class')); $this->assertSame('class.php', $locator->locateClass('\class')); } } tests/Locator/ComposerLocatorTest.php000064400000001753146412133200013777 0ustar00assertSame( $reflectionClass->getFileName(), $locator->locateClass(ReflectionClass::class) ); $this->assertSame( $reflectionClass->getFileName(), $locator->locateClass('\\' . ReflectionClass::class) ); } public function testLocateClassWithAttributes() { ReflectionEngine::init(new ComposerLocator()); $parsedClass = new \Go\ParserReflection\ReflectionClass(\Go\ParserReflection\Stub\RandomClassWithAttribute::class); $this->assertIsArray($parsedClass->getAttributes()); } } tests/ReflectionAttributesTest.php000064400000020107146412133200013414 0ustar00setUpFile(__DIR__ . '/Stub/FileWithFunction80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $function = $fileNamespace->getFunction('function_with_attribute'); $attributes = $function->getAttributes(); $originalReflection = new \ReflectionFunction('Go\ParserReflection\Stub\function_with_attribute'); foreach ($attributes as $attribute) { $originalAttribute = current($originalReflection->getAttributes($attribute->getName())); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame($originalAttribute->getName(), $attribute->getName()); $this->assertSame($originalAttribute->getArguments(), $attribute->getArguments()); $this->assertSame($originalAttribute->isRepeated(), $attribute->isRepeated()); } } public function testGetAttributeOnClassMethod() { $this->setUpFile(__DIR__ . '/Stub/FileWithClassMethod80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $class = $fileNamespace->getClass('Go\ParserReflection\Stub\FileWithClassMethod'); foreach ($class->getMethods() as $method) { $attributes = $method->getAttributes(); $originalReflection = new \ReflectionMethod('Go\ParserReflection\Stub\FileWithClassMethod', $method->getName()); foreach ($attributes as $attribute) { $originalAttribute = current($originalReflection->getAttributes($attribute->getName())); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame($originalAttribute->getName(), $attribute->getName()); $this->assertSame($originalAttribute->getArguments(), $attribute->getArguments()); $this->assertSame($originalAttribute->isRepeated(), $attribute->isRepeated()); } $this->assertSame($originalReflection->__toString(), $method->__toString()); } } public function testGetAttributeOnParameters() { $this->setUpFile(__DIR__ . '/Stub/FileWithParameters80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parameters = $fileNamespace->getFunction('authenticate')->getParameters(); foreach ($parameters as $parameter) { $attributes = $parameter->getAttributes(); $originalReflection = new \ReflectionParameter('Go\ParserReflection\Stub\authenticate', $parameter->getName()); foreach ($attributes as $attribute) { $originalAttribute = current($originalReflection->getAttributes($attribute->getName())); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame($originalAttribute->getName(), $attribute->getName()); $this->assertSame($originalAttribute->getArguments(), $attribute->getArguments()); $this->assertSame($originalAttribute->isRepeated(), $attribute->isRepeated()); } $this->assertSame($originalReflection->__toString(), $parameter->__toString()); } } public function testGetAttributeOnClassConst() { $this->setUpFile(__DIR__ . '/Stub/FileWithClassConst80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $constants = $fileNamespace->getClass('Go\ParserReflection\Stub\FileWithClassConstAttribute')->getConstants(); foreach (array_keys($constants) as $constant) { $reflectionClassConst = new ReflectionClassConstant('Go\ParserReflection\Stub\FileWithClassConstAttribute', $constant); $attributes = $reflectionClassConst->getAttributes(); $originalReflection = new \ReflectionClassConstant('Go\ParserReflection\Stub\FileWithClassConstAttribute', $constant); foreach ($attributes as $key => $attribute) { $originalAttributes = $originalReflection->getAttributes($attribute->getName()); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame(current($originalAttributes)->getName(), $attribute->getName()); $this->assertSame(current($originalAttributes)->isRepeated(), $attribute->isRepeated()); // test repeated on constant stub if ($attribute->isRepeated()) { $this->assertSame($originalAttributes[$key]->getArguments(), $attribute->getArguments()); } } $this->assertSame($originalReflection->__toString(), $reflectionClassConst->__toString()); } } public function testGetAttributeOnClass() { $this->setUpFile(__DIR__ . '/Stub/FileWithClass80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $class = $fileNamespace->getClass('Go\ParserReflection\Stub\FileWithClassAttribute'); $attributes = $class->getAttributes(); $originalReflection = new \ReflectionClass($class->getName()); foreach ($attributes as $attribute) { $originalAttribute = current($originalReflection->getAttributes($attribute->getName())); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame($originalAttribute->getName(), $attribute->getName()); $this->assertSame($originalAttribute->getArguments(), $attribute->getArguments()); $this->assertSame($originalAttribute->isRepeated(), $attribute->isRepeated()); } $this->assertSame($originalReflection->__toString(), $class->__toString()); } public function testGetAttributeOnProperty() { $this->setUpFile(__DIR__ . '/Stub/FileWithClassProperty80.php'); $fileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $properties = $fileNamespace->getClass('Go\ParserReflection\Stub\FileWithClassProperty')->getProperties(); foreach ($properties as $property) { $attributes = $property->getAttributes(); $originalReflection = new \ReflectionProperty('Go\ParserReflection\Stub\FileWithClassProperty', $property->getName()); foreach ($attributes as $attribute) { $originalAttribute = current($originalReflection->getAttributes($attribute->getName())); $this->assertInstanceOf(ReflectionAttribute::class, $attribute); $this->assertInstanceOf(Attribute::class, $attribute->getNode()); $this->assertSame($originalAttribute->getName(), $attribute->getName()); $this->assertSame($originalAttribute->getArguments(), $attribute->getArguments()); $this->assertSame($originalAttribute->isRepeated(), $attribute->isRepeated()); } $this->assertSame($originalReflection->__toString(), $property->__toString()); } } /** * Setups file for parsing * * @param string $fileName File name to use */ private function setUpFile($fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); $this->parsedRefFile = $reflectionFile; include_once $fileName; } } tests/ReflectionClassConstantTest.php000064400000010444146412133200014050 0ustar00setUpFile(__DIR__ . '/Stub/FileWithClasses71.php'); } public function testGeneralInfoGetters() { $allNameGetters = [ 'getDocComment', 'getModifiers', 'getName', 'getValue', 'isPrivate', 'isProtected', 'isPublic', '__toString' ]; foreach ($this->parsedRefFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getClasses() as $refClass) { $className = $refClass->getName(); foreach ($refClass->getReflectionConstants() as $refReflectionConstant) { $classConstantName = $refReflectionConstant->getName(); $originalRefParameter = new \ReflectionClassConstant($className, $classConstantName); foreach ($allNameGetters as $getterName) { $expectedValue = $originalRefParameter->$getterName(); $actualValue = $refReflectionConstant->$getterName(); $this->assertSame( $expectedValue, $actualValue, "{$getterName}() for parameter {$className}::{$classConstantName} should be equal" ); } } } } } public function testGetClassConstantProperties() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(ClassWithPhp71Features::class); $constant = $parsedClass->getReflectionConstant('PUBLIC_CONST_A'); $this->assertSame('PUBLIC_CONST_A', $constant->name); $this->assertSame(ClassWithPhp71Features::class, $constant->class); } public function testGetClassConstant() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(ClassWithPhp71Features::class); $classConstants = $parsedClass->getReflectionConstants(); $this->assertSame($classConstants[0], $parsedClass->getReflectionConstant('PUBLIC_CONST_A')); $this->assertSame($classConstants[1], $parsedClass->getReflectionConstant('PUBLIC_CONST_B')); $this->assertSame($classConstants[2], $parsedClass->getReflectionConstant('PROTECTED_CONST')); $this->assertSame($classConstants[3], $parsedClass->getReflectionConstant('PRIVATE_CONST')); $this->assertSame($classConstants[4], $parsedClass->getReflectionConstant('CALCULATED_CONST')); } #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testCoverAllMethods() { $allInternalMethods = get_class_methods(\ReflectionClassConstant::class); $allMissedMethods = []; foreach ($allInternalMethods as $internalMethodName) { if ('export' === $internalMethodName) { continue; } $refMethod = new \ReflectionMethod(ReflectionClassConstant::class, $internalMethodName); $definerClass = $refMethod->getDeclaringClass()->getName(); if (strpos($definerClass, 'Go\\ParserReflection') !== 0) { $allMissedMethods[] = $internalMethodName; } } if ($allMissedMethods) { $this->markTestIncomplete('Methods ' . implode(', ', $allMissedMethods) . ' are not implemented'); } } /** * Setups file for parsing * * @param string $fileName File name to use */ private function setUpFile($fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); $this->parsedRefFile = $reflectionFile; include_once $fileName; } } tests/ReflectionClassTest.php000064400000032717146412133200012345 0ustar00setUpFile($fileName); $parsedClasses = $this->parsedRefFileNamespace->getClasses(); foreach ($parsedClasses as $parsedRefClass) { $originalRefClass = new \ReflectionClass($parsedRefClass->getName()); $parsedModifiers = $parsedRefClass->getModifiers() & $mask; $originalModifiers = $originalRefClass->getModifiers() & $mask; $this->assertEquals($originalModifiers, $parsedModifiers); } } /** * Performs method-by-method comparison with original reflection * * * @param ReflectionClass $parsedClass Parsed class * @param \ReflectionMethod $refMethod Method to analyze * @param string $getterName Name of the reflection method to test */ #[\PHPUnit\Framework\Attributes\DataProvider('caseProvider')] public function testReflectionMethodParity( ReflectionClass $parsedClass, $getterName ) { $className = $parsedClass->getName(); $refClass = new \ReflectionClass($className); $expectedValue = $refClass->$getterName(); $actualValue = $parsedClass->$getterName(); $this->assertSame( $expectedValue, $actualValue, "{$getterName}() for class {$className} should be equal" ); } /** * Provides full test-case list in the form [ParsedClass, ReflectionMethod, getter name to check] * * @return array */ public static function caseProvider() { $allNameGetters = static::getGettersToCheck(); $testCases = []; $files = static::getFilesToAnalyze(); foreach ($files as $fileList) { foreach ($fileList as $fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); include_once $fileName; foreach ($reflectionFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getClasses() as $parsedClass) { $caseName = $parsedClass->getName(); foreach ($allNameGetters as $getterName) { $testCases[$caseName . ', ' . $getterName] = [ $parsedClass, $getterName ]; } } } } } return $testCases; } /** * Tests getMethods() returns correct number of methods for the class * * * @param string $fileName File name to test */ #[\PHPUnit\Framework\Attributes\DataProvider('getFilesToAnalyze')] public function testGetMethodCount($fileName) { $this->setUpFile($fileName); $parsedClasses = $this->parsedRefFileNamespace->getClasses(); foreach ($parsedClasses as $parsedRefClass) { $originalRefClass = new \ReflectionClass($parsedRefClass->getName()); $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 getReflectionConstants() returns correct number of reflectionConstants for the class * * * @param string $fileName File name to test */ #[\PHPUnit\Framework\Attributes\DataProvider('getFilesToAnalyze')] public function testGetReflectionConstantCount($fileName) { $this->setUpFile($fileName); $parsedClasses = $this->parsedRefFileNamespace->getClasses(); foreach ($parsedClasses as $parsedRefClass) { $originalRefClass = new \ReflectionClass($parsedRefClass->getName()); $parsedReflectionConstants = $parsedRefClass->getReflectionConstants(); $originalReflectionConstants = $originalRefClass->getReflectionConstants(); $this->assertCount(count($originalReflectionConstants), $parsedReflectionConstants); } } /** * Tests getProperties() returns correct number of properties for the class * * * @param string $fileName File name to test */ #[\PHPUnit\Framework\Attributes\DataProvider('getFilesToAnalyze')] public function testGetProperties($fileName) { $this->setUpFile($fileName); $parsedClasses = $this->parsedRefFileNamespace->getClasses(); foreach ($parsedClasses as $parsedRefClass) { $originalRefClass = new \ReflectionClass($parsedRefClass->getName()); $parsedProperties = $parsedRefClass->getProperties(); $originalProperties = $originalRefClass->getProperties(); $this->assertCount(count($originalProperties), $parsedProperties); } } 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() { $parsedRefClass1 = $this->parsedRefFileNamespace->getClass(ClassWithConstantsAndInheritance::class); $originalRefClass1 = new \ReflectionClass(ClassWithConstantsAndInheritance::class); $parsedRefClass2 = $this->parsedRefFileNamespace->getClass(ClassWithMagicConstants::class); $originalRefClass2 = new \ReflectionClass(ClassWithMagicConstants::class); $defaultProp1Value = $originalRefClass1->getStaticPropertyValue('h'); $defaultProp2Value = $originalRefClass2->getStaticPropertyValue('a'); $ex = null; try { $this->assertEqualsWithDelta(M_PI, $parsedRefClass1->getStaticPropertyValue('h'), 0.0001, 'Close to expected value of M_PI'); $this->assertEqualsWithDelta(M_PI, $originalRefClass1->getStaticPropertyValue('h'), 0.0001, 'Close to expected value of M_PI'); $this->assertEquals( realpath(dirname(__DIR__ . parent::DEFAULT_STUB_FILENAME)), realpath($parsedRefClass2->getStaticPropertyValue('a')), 'Expected value'); $this->assertEquals( $originalRefClass2->getStaticPropertyValue('a'), $parsedRefClass2->getStaticPropertyValue('a'), 'Same as native implementation'); $parsedRefClass1->setStaticPropertyValue('h', 'test'); $parsedRefClass2->setStaticPropertyValue('a', 'different value'); $this->assertSame('test', $parsedRefClass1->getStaticPropertyValue('h')); $this->assertSame('test', $originalRefClass1->getStaticPropertyValue('h')); $this->assertSame('different value', $parsedRefClass2->getStaticPropertyValue('a')); $this->assertSame('different value', $originalRefClass2->getStaticPropertyValue('a')); } catch (\Exception $e) { $ex = $e; } // I didn't want to write a tearDown() for one test. $originalRefClass1->setStaticPropertyValue('h', $defaultProp1Value); $originalRefClass2->setStaticPropertyValue('a', $defaultProp2Value); if ($ex) { throw $ex; } } 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() { $this->markTestIncomplete("See https://github.com/goaop/parser-reflection/issues/55"); $parsedRefClass = $this->parsedRefFileNamespace->getClass(SimpleAbstractInheritance::class); $originalRefClass = new \ReflectionClass(SimpleAbstractInheritance::class); $this->assertEquals($originalRefClass->hasMethod('test'), $parsedRefClass->hasMethod('test')); } public function testHasConstant() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ClassWithScalarConstants::class); $originalRefClass = new \ReflectionClass(ClassWithScalarConstants::class); $this->assertSame($originalRefClass->hasConstant('D'), $parsedRefClass->hasConstant('D')); $this->assertSame($originalRefClass->hasConstant('E'), $parsedRefClass->hasConstant('E')); } public function testGetConstant() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ClassWithScalarConstants::class); $originalRefClass = new \ReflectionClass(ClassWithScalarConstants::class); $this->assertSame($originalRefClass->getConstant('D'), $parsedRefClass->getConstant('D')); $this->assertSame($originalRefClass->getConstant('E'), $parsedRefClass->getConstant('E')); } public function testGetReflectionConstant() { $parsedRefClass = $this->parsedRefFileNamespace->getClass(ClassWithScalarConstants::class); $originalRefClass = new \ReflectionClass(ClassWithScalarConstants::class); $this->assertFalse($parsedRefClass->getReflectionConstant('NOT_EXISTING')); $this->assertSame( (string) $originalRefClass->getReflectionConstant('D'), (string) $parsedRefClass->getReflectionConstant('D') ); $this->assertSame( (string) $originalRefClass->getReflectionConstant('E'), (string) $parsedRefClass->getReflectionConstant('E') ); } /** * Returns list of ReflectionMethod getters that be checked directly without additional arguments * * @return array */ protected static function getGettersToCheck() { $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' ]; return $allNameGetters; } } tests/ReflectionFileNamespaceTest.php000064400000016776146412133200014003 0ustar00getFileNamespace('Go\ParserReflection\Stub'); $this->parsedRefFileNamespace = $parsedFileNamespace; include_once $fileName; } public function testGetClass() { $refClass = $this->parsedRefFileNamespace->getClass('Unknown'); $this->assertFalse($refClass); $refClass = $this->parsedRefFileNamespace->getClass(TestNamespaceClassFoo::class); $this->assertInstanceOf(\ReflectionClass::class, $refClass); $this->assertEquals(TestNamespaceClassFoo::class, $refClass->name); } public function testGetClasses() { $refClasses = $this->parsedRefFileNamespace->getClasses(); $this->assertCount(2, $refClasses); } public function testGetConstant() { $constValue = $this->parsedRefFileNamespace->getConstant('Unknown'); $this->assertFalse($constValue); $constValue = $this->parsedRefFileNamespace->getConstant('NAMESPACE_NAME'); $this->assertNotFalse($constValue); $this->assertEquals(\Go\ParserReflection\Stub\NAMESPACE_NAME, $constValue); } public function testGetConstants() { $constValue = $this->parsedRefFileNamespace->getConstants(true); $this->assertEquals( array( 'START_MARKER' => 9, 'NAMESPACE_NAME' => 'Go\ParserReflection\Stub', 'FILE_NAME' => __DIR__ . self::STUB_FILE, 'END_MARKER' => 26, 'INT_CONST' => 5, ), $constValue ); $constValue = $this->parsedRefFileNamespace->getConstants(false); $this->assertNotFalse($constValue); $this->assertEquals( array( 'START_MARKER' => 9, 'NAMESPACE_NAME' => 'Go\ParserReflection\Stub', 'FILE_NAME' => __DIR__ . self::STUB_FILE, 'END_MARKER' => 26, ), $constValue ); } public function testGetConstantsCacheIndependence() { $globalConstants = $this->parsedRefFileNamespace->getConstants(true); $this->assertArrayHasKey('FILE_NAME', $globalConstants, 'Namespaced constant found.'); $this->assertArrayHasKey('INT_CONST', $globalConstants, 'Global constant found.'); $constants = $this->parsedRefFileNamespace->getConstants(); $this->assertArrayHasKey('FILE_NAME', $constants, 'Namespaced constant found.'); $this->assertArrayNotHasKey('INT_CONST', $constants, 'Global constant not found.'); $this->assertNotEmpty($this->parsedRefFileNamespace->getConstant('FILE_NAME'), 'Namespaced constant found.'); $this->assertTrue($this->parsedRefFileNamespace->hasConstant('FILE_NAME'), 'Namespaced constant found.'); $this->assertFalse($this->parsedRefFileNamespace->hasConstant('INT_CONST'), 'Global constant not found.'); $this->assertFalse($this->parsedRefFileNamespace->getConstant('INT_CONST'), 'Global constant not found.'); } public function testGetGlobalConstants() { $fileName = stream_resolve_include_path(__DIR__ . self::STUB_GLOBAL_FILE); $reflectionFile = new ReflectionFile($fileName); $reflectionFileNamespace = $reflectionFile->getFileNamespace(''); $this->assertSame( array( // Scalar types are handled. 'INT_CONST' => 5, 'STRING_CONST' => 'text', 'BOOLEAN_CONST' => true, // Expressions are handled partially. 'EXPRESSION_CONST' => false, 'FUNCTION_CONST' => null, ), $reflectionFileNamespace->getConstants(true) ); } 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 testGetNameOfGlobalNamespace() { $fileName = stream_resolve_include_path(__DIR__ . self::STUB_GLOBAL_FILE); $reflectionFile = new ReflectionFile($fileName); $reflectionFileNamespace = $reflectionFile->getFileNamespace(''); $this->assertSame('', $reflectionFileNamespace->getName()); } public function testGetNamespaceAliases() { $expectedAliases = [ 'SomeClass\WithoutAlias' => 'WithoutAlias', '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.php000064400000007203146412133200012147 0ustar00parsedRefFile = $reflectionFile; } public function testGetName() { $fileName = $this->parsedRefFile->getName(); $expectedName = stream_resolve_include_path(__DIR__ . self::STUB_FILE); $this->assertEquals($expectedName, $fileName); } public function testGetFileNamespaces() { $reflectionFileNamespaces = $this->parsedRefFile->getFileNamespaces(); $this->assertCount(3, $reflectionFileNamespaces); } public function testGetFileNamespace() { $reflectionFileNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $this->assertInstanceOf(ReflectionFileNamespace::class, $reflectionFileNamespace); $reflectionFileNamespace = $this->parsedRefFile->getFileNamespace('Unknown'); $this->assertFalse($reflectionFileNamespace); } public function testHasFileNamespace() { $hasFileNamespace = $this->parsedRefFile->hasFileNamespace('Go\ParserReflection\Stub'); $this->assertTrue($hasFileNamespace); $hasFileNamespace = $this->parsedRefFile->hasFileNamespace('Unknown'); $this->assertFalse($hasFileNamespace); } public function testGetGlobalFileNamespace() { $fileName = stream_resolve_include_path(__DIR__ . self::STUB_GLOBAL_FILE); $reflectionFile = new ReflectionFile($fileName); $reflectionFileNamespace = $reflectionFile->getFileNamespace(''); $this->assertInstanceOf(ReflectionFileNamespace::class, $reflectionFileNamespace); } /** * Tests if strict mode detected correctly * * @param string $fileName Filename to analyse * @param bool $shouldBeStrict */ #[\PHPUnit\Framework\Attributes\DataProvider('fileNameProvider')] public function testIsStrictType($fileName, $shouldBeStrict) { $fileName = stream_resolve_include_path(__DIR__ . $fileName); $reflectionFile = new ReflectionFile($fileName); $this->assertSame($shouldBeStrict, $reflectionFile->isStrictMode()); } public static function fileNameProvider() { return [ '/Stub/FileWithClasses56.php' => ['/Stub/FileWithClasses56.php', false], '/Stub/FileWithClasses70.php' => ['/Stub/FileWithClasses70.php', false], '/Stub/FileWithClasses71.php' => ['/Stub/FileWithClasses71.php', true], '/Stub/FileWithGlobalNamespace.php' => ['/Stub/FileWithGlobalNamespace.php', true], ]; } public function testGetInterfaceNamesWithExtends() { $fileName = __DIR__ . '/Stub/Issue44/ClassWithoutNamespace.php'; require_once __DIR__ . '/Stub/Issue44/Locator.php'; ReflectionEngine::init(new Locator()); $reflectedFile = new ReflectionFile($fileName); $namespaces = $reflectedFile->getFileNamespaces(); $namespace = array_pop($namespaces); $classes = $namespace->getClasses(); $class = array_pop($classes); $interfaceNames = $class->getInterfaceNames(); $this->assertEquals([], $interfaceNames); } } tests/ReflectionFunctionTest.php000064400000012401146412133200013051 0ustar00parsedRefFile = $reflectionFile; include_once $fileName; } public function testGeneralInfoGetters() { $allNameGetters = [ 'getStartLine', 'getEndLine', 'getDocComment', 'getExtension', 'getExtensionName', 'getName', 'getNamespaceName', 'getShortName', 'inNamespace', 'getStaticVariables', 'getNumberOfParameters', 'getNumberOfRequiredParameters', '__toString', 'isDisabled', 'returnsReference', 'getClosureScopeClass', 'getClosureThis' ]; $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" ); } } } } #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] 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 ' . implode(', ', $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() { $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->getName(), $parsedReturnType->__toString()); } else { $this->assertSame( $originalRefFunction->getReturnType(), $refFunction->getReturnType() ); } } } } } tests/ReflectionMethodTest.php000064400000013332146412133200012510 0ustar00parsedRefClass->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 = $this->parsedRefClass->getMethod('funcWithDocAndBody'); $originalRefMethod = new \ReflectionMethod($this->parsedRefClass->getName(), '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($this->parsedRefClass->getName(), $retValue); $prototype = $refMethod->getPrototype(); $this->assertInstanceOf(\ReflectionMethod::class, $prototype); $prototype->setAccessible(true); $retValue = $prototype->invokeArgs(null, []); $this->assertNotEquals($this->parsedRefClass->getName(), $retValue); } /** * Performs method-by-method comparison with original reflection * * * @param ReflectionClass $parsedClass Parsed class * @param \ReflectionMethod $refMethod Method to analyze * @param string $getterName Name of the reflection method to test */ #[\PHPUnit\Framework\Attributes\DataProvider('caseProvider')] public function testReflectionMethodParity( ReflectionClass $parsedClass, \ReflectionMethod $refMethod, $getterName ) { $methodName = $refMethod->getName(); $className = $parsedClass->getName(); $parsedMethod = $parsedClass->getMethod($methodName); if (empty($parsedMethod)) { echo "Couldn't find method $methodName in the $className", PHP_EOL; return; } $expectedValue = $refMethod->$getterName(); $actualValue = $parsedMethod->$getterName(); $this->assertSame( $expectedValue, $actualValue, "$getterName() for method $className->$methodName() should be equal" ); } /** * Provides full test-case list in the form [ParsedClass, ReflectionMethod, getter name to check] * * @return array */ public static function caseProvider() { $allNameGetters = static::getGettersToCheck(); $testCases = []; $files = static::getFilesToAnalyze(); foreach ($files as $fileList) { foreach ($fileList as $fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); include_once $fileName; foreach ($reflectionFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getClasses() as $parsedClass) { $refClass = new \ReflectionClass($parsedClass->getName()); foreach ($refClass->getMethods() as $classMethod) { $caseName = $parsedClass->getName() . '->' . $classMethod->getName() . '()'; foreach ($allNameGetters as $getterName) { $testCases[$caseName . ', ' . $getterName] = [ $parsedClass, $classMethod, $getterName ]; } } } } } } return $testCases; } /** * Returns list of ReflectionMethod getters that be checked directly without additional arguments * * @return array */ protected static function getGettersToCheck() { $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' ]; $allNameGetters[] = 'isVariadic'; $allNameGetters[] = 'isGenerator'; $allNameGetters[] = 'hasReturnType'; return $allNameGetters; } } tests/ReflectionParameterTest.php000064400000031243146412133200013211 0ustar00setUpFile(__DIR__ . '/Stub/FileWithParameters55.php'); } #[\PHPUnit\Framework\Attributes\DataProvider('fileProvider')] public function testGeneralInfoGetters($fileName) { $this->setUpFile($fileName); $allNameGetters = [ 'isArray', 'isCallable', 'isOptional', 'isPassedByReference', 'isDefaultValueAvailable', 'getPosition', 'canBePassedByValue', 'allowsNull', 'getDefaultValue', 'getDefaultValueConstantName', 'isDefaultValueConstant', '__toString' ]; $onlyWithDefaultValues = array_flip([ 'getDefaultValue', 'getDefaultValueConstantName', 'isDefaultValueConstant' ]); $allNameGetters[] = 'isVariadic'; $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" ); } } } } } /** * Provides a list of files for analysis * * @return array */ public static function fileProvider() { $files = ['PHP5.5' => [__DIR__ . '/Stub/FileWithParameters55.php']]; $files['PHP5.6'] = [__DIR__ . '/Stub/FileWithParameters56.php']; $files['PHP7.0'] = [__DIR__ . '/Stub/FileWithParameters70.php']; return $files; } 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()); $internalInterfaceParam = $parameters[12 /* \Traversable $traversable */]->getClass(); $this->assertInstanceOf(\ReflectionClass::class, $internalInterfaceParam); $this->assertSame(\Traversable::class, $internalInterfaceParam->getName()); } public function testGetClassMethodReturnsSelfAndParent() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(SubFoo::class); $parsedFunction = $parsedClass->getMethod('anotherMethodParam'); $parameters = $parsedFunction->getParameters(); $selfParam = $parameters[0 /* self $selfParam */]->getClass(); $this->assertInstanceOf(\ReflectionClass::class, $selfParam); $this->assertSame(SubFoo::class, $selfParam->getName()); $parentParam = $parameters[1 /* parent $parentParam */]->getClass(); $this->assertInstanceOf(\ReflectionClass::class, $parentParam); $this->assertSame(Foo::class, $parentParam->getName()); } public function testNonConstantsResolvedForGlobalNamespace() { $parsedNamespace = $this->parsedRefFile->getFileNamespace(''); $parsedClass = $parsedNamespace->getClass(TestParametersForRootNsClass::class); $parsedFunction = $parsedClass->getMethod('foo'); $parameters = $parsedFunction->getParameters(); $this->assertSame(null, $parameters[0]->getDefaultValue()); $this->assertSame(false, $parameters[1]->getDefaultValue()); $this->assertSame(true, $parameters[2]->getDefaultValue()); } 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 testParamWithDefaultConstValue() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(Foo::class); $parsedFunction = $parsedClass->getMethod('methodParamConst'); $parameters = $parsedFunction->getParameters(); $this->assertTrue($parameters[0]->isDefaultValueConstant()); $this->assertSame('self::CLASS_CONST', $parameters[0]->getDefaultValueConstantName()); $this->assertTrue($parameters[2]->isDefaultValueConstant()); $this->assertSame('Go\ParserReflection\Stub\TEST_PARAMETER', $parameters[2]->getDefaultValueConstantName()); $this->assertTrue($parameters[3]->isDefaultValueConstant()); $this->assertSame('Go\ParserReflection\Stub\SubFoo::ANOTHER_CLASS_CONST', $parameters[3]->getDefaultValueConstantName()); } public function testParamBuiltInClassConst() { $parsedNamespace = $this->parsedRefFile->getFileNamespace('Go\ParserReflection\Stub'); $parsedClass = $parsedNamespace->getClass(Foo::class); $parsedFunction = $parsedClass->getMethod('methodParamBuiltInClassConst'); $parameters = $parsedFunction->getParameters(); $this->assertTrue($parameters[0]->isDefaultValueConstant()); $this->assertSame('DateTime::ATOM', $parameters[0]->getDefaultValueConstantName()); } 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()); } /** * @param string $getterName Name of the getter to call */ #[\PHPUnit\Framework\Attributes\DataProvider('listOfDefaultGetters')] 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 static function listOfDefaultGetters() { return [ ['getDefaultValue'], ['getDefaultValueConstantName'] ]; } #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] 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 ' . implode(', ', $allMissedMethods) . ' are not implemented'); } } public function testGetTypeMethod() { $this->setUpFile(__DIR__ . '/Stub/FileWithParameters70.php'); 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" ); $message= "Parameter $functionName:$parameterName not equals to the original reflection"; if ($hasType) { $parsedReturnType = $refParameter->getType(); $originalReturnType = $originalRefParameter->getType(); $this->assertSame($originalReturnType->allowsNull(), $parsedReturnType->allowsNull(), $message); $this->assertSame($originalReturnType->isBuiltin(), $parsedReturnType->isBuiltin(), $message); $this->assertSame($originalReturnType->getName(), $parsedReturnType->__toString(), $message); } else { $this->assertSame( $originalRefParameter->getType(), $refParameter->getType(), $message ); } } } } } /** * Setups file for parsing * * @param string $fileName File name to use */ private function setUpFile($fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); $this->parsedRefFile = $reflectionFile; include_once $fileName; } } tests/ReflectionPropertyTest.php000064400000011555146412133200013121 0ustar00getName(); $className = $parsedClass->getName(); $parsedProperty = $parsedClass->getProperty($propertyName); $expectedValue = $refProperty->$getterName(); $actualValue = $parsedProperty->$getterName(); $this->assertSame( $expectedValue, $actualValue, "{$getterName}() for property {$className}->{$propertyName} should be equal" ); } /** * Provides full test-case list in the form [ParsedClass, ReflectionMethod, getter name to check] * * @return array */ public static function caseProvider() { $allNameGetters = static::getGettersToCheck(); $testCases = []; $files = static::getFilesToAnalyze(); foreach ($files as $fileList) { foreach ($fileList as $fileName) { $fileName = stream_resolve_include_path($fileName); $fileNode = ReflectionEngine::parseFile($fileName); $reflectionFile = new ReflectionFile($fileName, $fileNode); include_once $fileName; foreach ($reflectionFile->getFileNamespaces() as $fileNamespace) { foreach ($fileNamespace->getClasses() as $parsedClass) { $refClass = new \ReflectionClass($parsedClass->getName()); foreach ($refClass->getProperties() as $classProperty) { $caseName = $parsedClass->getName() . '->' . $classProperty->getName(); foreach ($allNameGetters as $getterName) { $testCases[$caseName . ', ' . $getterName] = [ $parsedClass, $classProperty, $getterName ]; } } } } } } return $testCases; } 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 = $this->parsedRefClass->getName(); $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($this->parsedRefClass->getName(), 'publicStaticProperty'); $originalValue = $parsedRefProperty->getValue(); $this->assertSame(M_PI, $originalValue); } public function testDebugInfoMethod() { $parsedRefProperty = $this->parsedRefClass->getProperty('publicStaticProperty'); $originalRefProperty = new \ReflectionProperty($this->parsedRefClass->getName(), 'publicStaticProperty'); $expectedValue = (array) $originalRefProperty; $this->assertSame($expectedValue, $parsedRefProperty->__debugInfo()); } /** * Returns list of ReflectionMethod getters that be checked directly without additional arguments * * @return array */ protected static function getGettersToCheck() { $allNameGetters = [ 'isDefault', 'getName', 'getModifiers', 'getDocComment', 'isPrivate', 'isProtected', 'isPublic', 'isStatic', '__toString' ]; return $allNameGetters; } } tests/ReflectionTypeTest.php000064400000011663146412133200012216 0ustar00getMethod('acceptsDefaultString'); $this->assertEquals(\ReflectionMethod::class, get_class($nativeMethodRef)); $nativeParamRefArr = $nativeMethodRef->getParameters(); $this->assertCount(2, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); $this->assertEquals('string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } /** * Testing convertToDisplayType() with native \ReflectionType * * We're already testing it with Go\ParserReflection\ReflectionType * elsewhere. */ public function testTypeConvertToDisplayTypeWithNullableNativeType() { $nativeClassRef = new \ReflectionClass('Go\\ParserReflection\\Stub\\ClassWithNullableScalarTypeHints'); $nativeMethodRef = $nativeClassRef->getMethod('acceptsDefaultString'); $this->assertEquals(\ReflectionMethod::class, get_class($nativeMethodRef)); $nativeParamRefArr = $nativeMethodRef->getParameters(); $this->assertCount(2, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); $this->assertEquals('?string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } /** * Testing convertToDisplayType() with native \ReflectionType * * We're already testing it with Go\ParserReflection\ReflectionType * elsewhere. */ public function testTypeConvertToDisplayTypeImplicitlyNullable() { $nativeClassRef = new \ReflectionClass('Go\\ParserReflection\\Stub\\ClassWithScalarTypeHints'); $nativeMethodRef = $nativeClassRef->getMethod('acceptsStringDefaultToNull'); $this->assertEquals(\ReflectionMethod::class, get_class($nativeMethodRef)); $nativeParamRefArr = $nativeMethodRef->getParameters(); $this->assertCount(1, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); $this->assertTrue($nativeTypeRef->allowsNull()); $this->assertEquals('string', $nativeTypeRef->getName()); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); $this->assertEquals('?string', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } /** * Testing convertToDisplayType() with native \ReflectionType * * We're already testing it with Go\ParserReflection\ReflectionType * elsewhere. */ public function testTypeConvertToDisplayTypeImplicitlyUnionNullable() { $nativeClassRef = new \ReflectionClass('Go\\ParserReflection\\Stub\\ClassWithPhp80Features'); $nativeMethodRef = $nativeClassRef->getMethod('acceptsStringArrayDefaultToNull'); $this->assertEquals(\ReflectionMethod::class, get_class($nativeMethodRef)); $nativeParamRefArr = $nativeMethodRef->getParameters(); $this->assertCount(1, $nativeParamRefArr); $this->assertEquals(\ReflectionParameter::class, get_class($nativeParamRefArr[0])); $nativeTypeRef = $nativeParamRefArr[0]->getType(); $this->assertTrue($nativeTypeRef->allowsNull()); $this->assertEquals('array|string|null', $nativeTypeRef); $this->assertStringNotContainsString('\\', get_class($nativeTypeRef)); $this->assertInstanceOf(\ReflectionType::class, $nativeTypeRef); $this->assertEquals('array|string|null', \Go\ParserReflection\ReflectionType::convertToDisplayType($nativeTypeRef)); } } tests/Stub/FileWithClass80.php000064400000000545146412133200012205 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; #[\Doctrine\ORM\Mapping\Entity] class FileWithClassAttribute { }tests/Stub/FileWithClassConst80.php000064400000000643146412133200013213 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; class FileWithClassConstAttribute { #[\SomeAttribute('first')] #[\SomeAttribute('second')] public const FOO = 1; }tests/Stub/FileWithClassMethod80.php000064400000000614146412133200013343 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; class FileWithClassMethod { #[SomeAttribute] function function_with_attribute() { } } tests/Stub/FileWithClassProperty80.php000064400000000565146412133200013754 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; class FileWithClassProperty { #[\Doctrine\ORM\Mapping\Id] public $id; } tests/Stub/FileWithClasses55.php000064400000013001146412133200012526 0ustar00args = array_slice(array($a, &$b), 0, func_num_args()); } } class BaseClass { protected static function prototypeMethod() { return __CLASS__; } } /** * @link https://bugs.php.net/bug.php?id=70957 self::class can not be resolved with reflection for abstract class */ abstract class AbstractClassWithMethods extends BaseClass { const TEST = 5; public function __construct(){} public function __destruct(){} public function explicitPublicFunc(){} function implicitPublicFunc(){} protected function protectedFunc(){} private function privateFunc(){} static function staticFunc(){} protected static function protectedStaticFunc(){} abstract function abstractFunc(); final function finalFunc(){} /** * @return string */ public static function funcWithDocAndBody() { static $a =5, $test = '1234'; return 'hello'; } public static function funcWithReturnArgs($a, $b = 100, $c = 10.0) { return [$a, $b, $c]; } public static function prototypeMethod() { return __CLASS__; } /** * @return \Generator */ public function generatorYieldFunc() { $index = 0; while ($index < 1e3) { yield $index; } } /** * @return int */ public function noGeneratorFunc() { $gen = function () { yield 10; }; return 10; } private function testParam($a, $b = null, $d = self::TEST, $d2 = self::TEST . '_append', $e = M_PI, $f = \M_PI, $g = ['test'], $h = false, $i = true, $j = array('yoo')) {} } class ClassWithProperties { private $privateProperty = 123; protected $protectedProperty = 'a'; public $publicProperty = 42.0; /** * Some message to test docBlock * * @var int */ private static $privateStaticProperty = 1; protected static $protectedStaticProperty = 'foo'; public static $publicStaticProperty = M_PI; } 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 {} /* * Current implementation returns wrong __toString description for the parent methods * @see https://github.com/goaop/parser-reflection/issues/55 abstract class SimpleAbstractInheritance extends ImplicitAbstractClass { public $b = 'bar1'; public $d = 'foobar'; private $e = 'foobaz'; } */ class ClassWithInterface implements SimpleInterface {} class ClassWithTrait { use SimpleTrait; } /* * Current implementation doesn't support trait adaptation, * @see https://github.com/goaop/parser-reflection/issues/54 * 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; } } */ /* * Logic of prototype methods for interface and traits was changed since 7.0.6 * @see https://github.com/goaop/parser-reflection/issues/56 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; const E = null; } 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.php000064400000004535146412133200012543 0ustar00> - Bitwise Shift Right * . - Concatenation * ?: - Ternary Operator * <= - Smaller or Equal * => - Greater or Equal * == - Equal * != - Not Equal * < - Smaller * > - Greater * === - Identical * !== - Not Identical * && / and - Boolean AND * || / or - Boolean OR * xor - Boolean XOR * * Also supported is grouping static operations: (1 + 2) * 3. */ class ClassWithConstantExpressions { const ADDITION = ONE + 1; const SUBTRACTION = 2 - ONE; const MULTIPLICATION = ONE * 2; const DIVISION = ONE / 2; const MODULUS = ONE % 2; const BOOLEAN_NEGATION = !ONE; const BITWISE_NEGATION = ~ONE; const BITWISE_OR = ONE | 2; const BITWISE_AND = ONE & 2; const BITWISE_XOR = ONE ^ 2; const BITWISE_SHIFT_LEFT = ONE << 3; const BITWISE_SHIFT_RIGHT = ONE >> 3; const CONCATENATION = 'Value of shift left is ' . self::BITWISE_SHIFT_LEFT; const TERNARY_OPERATOR = true ?: false; const SMALLER_OR_EQUAL = ONE <= 2; const GREATER_OR_EQUAL = ONE >= 2; const EQUAL = ONE == true; const NOT_EQUAL = ONE != 2; const SMALLER = ONE < 2; const GREATER = ONE > 2; const IDENTICAL = ONE === true; const NOT_IDENTICAL = ONE !== true; const BOOLEAN_AND = ONE && false; const LOGICAL_AND = ONE and false; const BOOLEAN_OR = ONE || true; const LOGICAL_OR = ONE or true; const LOGICAL_XOR = ONE xor true; const GROUPING = (ONE + 2) * 3; const REFERENCE = self::ADDITION; } tests/Stub/FileWithClasses70.php000064400000002242146412133200012530 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ declare(strict_types=0); namespace Go\ParserReflection\Stub; // PHP 7+ code use Go\ParserReflection\{ReflectionMethod, ReflectionProperty as P}; class ClassWithScalarTypeHints { public function acceptsInteger(int $value) {} public function acceptsString(string $value) {} public function acceptsFloat(float $value) {} public function acceptsBool(bool $value) {} public function acceptsVariadicInteger(int ...$values) {} public function acceptsDefaultString(string $class = ReflectionMethod::class, string $name = P::class) {} public function acceptsStringDefaultToNull(string $someName = null) {} } class ClassWithReturnTypeHints { public function returnsInteger() : int {} public function returnsString() : string {} public function returnsFloat() : float {} public function returnsBool() : bool {} public function returnsObject() : ReflectionMethod {} public function returnsNamedObject() : P {} } tests/Stub/FileWithClasses71.php000064400000003244146412133200012534 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ declare(strict_types=1); namespace Go\ParserReflection\Stub; use Go\ParserReflection\{ReflectionMethod, ReflectionProperty as P}; // PHP 7.1+ code class ClassWithNullableScalarTypeHints { public function acceptsInteger(?int $value) {} public function acceptsString(?string $value) {} public function acceptsFloat(?float $value) {} public function acceptsBool(?bool $value) {} public function acceptsVariadicInteger(?int ...$values) {} public function acceptsDefaultString(?string $class = ReflectionMethod::class, ?string $name = P::class) {} } class ClassWithNullableReturnTypeHints { public function returnsInteger() : ?int {} public function returnsString() : ?string {} public function returnsFloat() : ?float {} public function returnsBool() : ?bool {} public function returnsObject() : ?ReflectionMethod {} public function returnsNamedObject() : ?P {} } class ClassWithPhp71Features { /** * Description for PUBLIC_CONST_A */ const PUBLIC_CONST_A = 1; /** * Description for PUBLIC_CONST_B */ public const PUBLIC_CONST_B = 2; /** * Description for PROTECTED_CONST */ protected const PROTECTED_CONST = 3; /** * Description for PRIVATE_CONST */ private const PRIVATE_CONST = 4; public const CALCULATED_CONST = 1 + 1; public function returnsVoid() : void {} public function acceptsIterable(iterable $iterable) : iterable {} } tests/Stub/FileWithClasses80.php000064400000000752146412133200012535 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ declare(strict_types=1); namespace Go\ParserReflection\Stub; use Go\ParserReflection\{ReflectionMethod, ReflectionProperty as P}; class ClassWithPhp80Features { public function acceptsStringArrayDefaultToNull(array|string $iterable = null) : array {} } tests/Stub/FileWithFunction80.php000064400000000535146412133200012724 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; #[SomeAttribute] function function_with_attribute() { } tests/Stub/FileWithFunctions55.php000064400000001270146412133200013106 0ustar00 7); define('FUNCTION_CONST', mktime(12, 33)); // Conditionally defined constant won't be detected. if (true) { define('TRUE_CONST', 1); } // Constants with names, that can't be resolved are ignored. define(get_name(), true); // Ignore function calls with dynamic function names. $define('TEST', 5); tests/Stub/FileWithNamespaces.php000064400000001400146412133200013036 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; function arrayVariadicParameters(array ...$acceptsArrays) {} function callableVariadicParameters(callable ...$acceptsArrays) {} function constantExpressionAsDefault($value = 0.5 * 2 * 10, $another = __FUNCTION__ . 'test', $anotherOne = ['test'], $anotherTwo = array('yoo')) {} function constantExponentiation($value = 0.5 * 2**2) {} tests/Stub/FileWithParameters70.php000064400000000775146412133200013247 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; function miscScalarParameters( int $acceptsInteger, string $acceptsString, float $acceptsFloat = \INF, boolean $acceptsBool, int $acceptsVariadicInteger, string ...$acceptsDefaultString ) { } tests/Stub/FileWithParameters80.php000064400000000610146412133200013234 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; function authenticate( string $username, #[\SensitiveParameter] string $password ) { } tests/Stub/Issue44/ClassWithNamespace.php000064400000000126146412133200014305 0ustar00 * * This source file is subject to the license that is bundled * with this source code in the file LICENSE. */ namespace Go\ParserReflection\Stub; use JetBrains\PhpStorm\ArrayShape; use JetBrains\PhpStorm\Deprecated; #[Deprecated('some arg')] class RandomClassWithAttribute { #[ArrayShape([ 'token' => 'string', 'code' => 'integer' ])] public $foo; } throw new \RuntimeException('test');tests/ValueResolver/NodeExpressionResolverTest.php000064400000005043146412133200016542 0ustar00parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); } /** * Testing passing PhpParser\Node\Expr as class for constant fetch * * We're already testing constant fetch with a explicit class name * elsewhere. */ public function testResolveConstFetchFromExpressionAsClass() { $expressionNodeTree = $this->parser->parse("process($expressionNodeTree[0]); $this->assertEquals(\DateTime::ATOM, $expressionSolver->getValue()); $this->assertTrue($expressionSolver->isConstant()); $this->assertEquals('DateTime::ATOM', $expressionSolver->getConstantName()); } /** * Testing passing PhpParser\Node\Expr as class for constant fetch * * Evaluating a run-time value like a variable should throw an exception. */ public function testResolveConstFetchFromVariableAsClass() { $this->expectException(\Go\ParserReflection\ReflectionException::class); $this->expectExceptionMessage('Method Go\ParserReflection\ValueResolver\NodeExpressionResolver::resolveExprVariable() not found trying to resolve class constant'); $expressionNodeTree = $this->parser->parse("process($expressionNodeTree[0]); } /** * Testing passing non-expression as class for constant fetch * * Non-expressions should be invalid. */ public function testResolveConstFetchFromNonExprAsClass() { $this->expectException(\Go\ParserReflection\ReflectionException::class); $this->expectExceptionMessage('Unable to resolve class constant'); $expressionNodeTree = $this->parser->parse("parser->parse("expr->class = $notAnExpressionNodeTree[0]; $expressionSolver = new NodeExpressionResolver(NULL); $expressionSolver->process($expressionNodeTree[0]); } }