From c90583a99cb9122224b6c88f9569c3ef8c86cf42 Mon Sep 17 00:00:00 2001 From: Alexander Lisachenko Date: Sun, 25 Sep 2016 13:35:33 +0300 Subject: [PATCH] Resolve all relative paths to be compatibe with original reflection filenames, thanks to @aik099. Resolves #31 --- src/Instrument/PathResolver.php | 83 +++++++++++++++++++++++++++ src/Locator/ComposerLocator.php | 8 ++- src/ReflectionEngine.php | 2 + src/ReflectionFile.php | 2 + src/ReflectionFileNamespace.php | 2 + tests/Locator/ComposerLocatorTest.php | 17 ++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/Instrument/PathResolver.php create mode 100644 tests/Locator/ComposerLocatorTest.php diff --git a/src/Instrument/PathResolver.php b/src/Instrument/PathResolver.php new file mode 100644 index 00000000..9ed787fc --- /dev/null +++ b/src/Instrument/PathResolver.php @@ -0,0 +1,83 @@ + + * + * 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(array(__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); + list ($pathScheme, $path) = isset($components[1]) ? $components : array(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(array('/', '\\'), DIRECTORY_SEPARATOR, $path); + if (strpos($path, '.') !== false) { + $parts = explode(DIRECTORY_SEPARATOR, $path); + $absolutes = []; + foreach ($parts as $part) { + if ('.' == $part) { + continue; + } elseif ('..' == $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; + } +} diff --git a/src/Locator/ComposerLocator.php b/src/Locator/ComposerLocator.php index 571b1ecc..d402cae5 100644 --- a/src/Locator/ComposerLocator.php +++ b/src/Locator/ComposerLocator.php @@ -11,6 +11,7 @@ namespace Go\ParserReflection\Locator; use Composer\Autoload\ClassLoader; +use Go\ParserReflection\Instrument\PathResolver; use Go\ParserReflection\LocatorInterface; use Go\ParserReflection\ReflectionException; @@ -50,6 +51,11 @@ public function __construct(ClassLoader $loader = null) */ public function locateClass($className) { - return $this->loader->findFile($className); + $filePath = $this->loader->findFile($className); + if (!empty($filePath)) { + $filePath = PathResolver::realpath($filePath); + } + + return $filePath; } } diff --git a/src/ReflectionEngine.php b/src/ReflectionEngine.php index 5c5ffc05..90b3efe3 100644 --- a/src/ReflectionEngine.php +++ b/src/ReflectionEngine.php @@ -10,6 +10,7 @@ namespace Go\ParserReflection; +use Go\ParserReflection\Instrument\PathResolver; use PhpParser\Lexer; use PhpParser\Node; use PhpParser\Node\Stmt\ClassLike; @@ -204,6 +205,7 @@ public static function parseClassProperty($fullClassName, $propertyName) */ public static function parseFile($fileName, $fileContent = null) { + $fileName = PathResolver::realpath($fileName); if (isset(self::$parsedFiles[$fileName])) { return self::$parsedFiles[$fileName]; } diff --git a/src/ReflectionFile.php b/src/ReflectionFile.php index f2a5f8d5..bb61a419 100644 --- a/src/ReflectionFile.php +++ b/src/ReflectionFile.php @@ -11,6 +11,7 @@ namespace Go\ParserReflection; +use Go\ParserReflection\Instrument\PathResolver; use PhpParser\Node; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Namespace_; @@ -50,6 +51,7 @@ class ReflectionFile */ public function __construct($fileName, $topLevelNodes = null) { + $fileName = PathResolver::realpath($fileName); $this->fileName = $fileName; $this->topLevelNodes = $topLevelNodes ?: ReflectionEngine::parseFile($fileName); } diff --git a/src/ReflectionFileNamespace.php b/src/ReflectionFileNamespace.php index 6a717e3d..a3d148d8 100644 --- a/src/ReflectionFileNamespace.php +++ b/src/ReflectionFileNamespace.php @@ -10,6 +10,7 @@ namespace Go\ParserReflection; +use Go\ParserReflection\Instrument\PathResolver; use Go\ParserReflection\ValueResolver\NodeExpressionResolver; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Const_; @@ -73,6 +74,7 @@ class ReflectionFileNamespace */ public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null) { + $fileName = PathResolver::realpath($fileName); if (!$namespaceNode) { $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName); } diff --git a/tests/Locator/ComposerLocatorTest.php b/tests/Locator/ComposerLocatorTest.php new file mode 100644 index 00000000..e81691b6 --- /dev/null +++ b/tests/Locator/ComposerLocatorTest.php @@ -0,0 +1,17 @@ +assertSame( + $reflectionClass->getFileName(), + $locator->locateClass(ReflectionClass::class) + ); + } +}