From c0a80131a72f45c6827325480e49026061f3c832 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 00:03:03 +0900 Subject: [PATCH 01/10] Refactor `ClassesInDirectories` to remove BetterReflection dependency Replaced BetterReflection with native PHP directory and token functions for class discovery in `ClassesInDirectories.php`. This simplifies the dependency tree by removing the `roave/better-reflection` package from `composer.json`. --- composer.json | 1 - src/ClassesInDirectories.php | 107 +++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 2930e01e..39102f86 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,6 @@ "ray/aop": "^2.10.4", "ray/aura-sql-module": "^1.12.0", "ray/di": "^2.14", - "roave/better-reflection": "^4.12 || ^5.6 || ^6.25", "symfony/polyfill-php81": "^1.24" }, "require-dev": { diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index c814c686..1d34dbd7 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -5,51 +5,84 @@ namespace Ray\MediaQuery; use Generator; -use Roave\BetterReflection\BetterReflection; -use Roave\BetterReflection\Reflector\DefaultReflector; -use Roave\BetterReflection\SourceLocator\Type\AggregateSourceLocator; -use Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator; -use Roave\BetterReflection\SourceLocator\Type\DirectoriesSourceLocator; - -use function assert; -use function class_exists; -use function interface_exists; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; final class ClassesInDirectories { /** - * get a list of all classes in the given directories. - * - * Based on: https://github.com/Roave/BetterReflection/blob/396a07c9d276cb9ffba581b24b2dadbb542d542e/demo/parsing-whole-directory/example2.php. - * * @param list $directories - * * @return Generator - * - * This function code is taken from https://github.com/WyriHaximus/php-list-classes-in-directory/blob/master/src/functions.php - * and modified for roave/better-reflection 5.x - * - * @see https://github.com/WyriHaximus/php-list-classes-in-directory - * @psalm-suppress MixedReturnTypeCoercion - * @phpstan-ignore-next-line/ */ - public static function list(string ...$directories): iterable + public static function list(string ...$directories): Generator + { + foreach ($directories as $directory) { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory) + ); + + foreach ($iterator as $file) { + if (! $file instanceof SplFileInfo) { + continue; + } + + if ($file->getExtension() !== 'php') { + continue; + } + + $className = self::getClassFromFile($file->getRealPath()); + if ($className === null) { + continue; + } + + if (class_exists($className) || interface_exists($className)) { + yield $className; + } + } + } + } + + private static function getClassFromFile(string $filePath): ?string { - /** @var list $directories */ - $sourceLocator = new AggregateSourceLocator([ - new DirectoriesSourceLocator( - $directories, - (new BetterReflection())->astLocator(), - ), - // ↓ required to autoload parent classes/interface from another directory than /src (e.g. /vendor) - new AutoloadSourceLocator((new BetterReflection())->astLocator()), - ]); - - foreach ((new DefaultReflector($sourceLocator))->reflectAllClasses() as $class) { - $className = $class->getName(); - assert(class_exists($className) || interface_exists($className)); - - yield $className; + $content = file_get_contents($filePath); + if ($content === false) { + return null; } + + $namespace = ''; + $class = ''; + $tokens = token_get_all($content); + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + if (!isset($tokens[$i][0])) { + continue; + } + + if ($tokens[$i][0] === T_NAMESPACE) { + for ($j = $i + 1; $j < $count; $j++) { + if ($tokens[$j][0] === T_NAME_QUALIFIED) { + $namespace = $tokens[$j][1]; + break; + } + } + } + + if ($tokens[$i][0] === T_CLASS || $tokens[$i][0] === T_INTERFACE) { + for ($j = $i + 1; $j < $count; $j++) { + if ($tokens[$j][0] === T_STRING) { + $class = $tokens[$j][1]; + break 2; + } + } + } + } + + if ($class === '') { + return null; + } + + return $namespace ? $namespace . '\\' . $class : $class; } } From 9405c99636c0a5e9cd049a26ea22efb83ba7f718 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 00:19:05 +0900 Subject: [PATCH 02/10] fixup! Refactor `ClassesInDirectories` to remove BetterReflection dependency --- src/ClassesInDirectories.php | 39 ++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 1d34dbd7..9e15ddd7 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -9,17 +9,30 @@ use RecursiveIteratorIterator; use SplFileInfo; +use function class_exists; +use function count; +use function file_get_contents; +use function interface_exists; +use function token_get_all; + +use const T_CLASS; +use const T_INTERFACE; +use const T_NAME_QUALIFIED; +use const T_NAMESPACE; +use const T_STRING; + final class ClassesInDirectories { /** * @param list $directories + * * @return Generator */ public static function list(string ...$directories): Generator { foreach ($directories as $directory) { $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($directory) + new RecursiveDirectoryIterator($directory), ); foreach ($iterator as $file) { @@ -36,14 +49,16 @@ public static function list(string ...$directories): Generator continue; } - if (class_exists($className) || interface_exists($className)) { - yield $className; + if (! class_exists($className) && ! interface_exists($className)) { + continue; } + + yield $className; } } } - private static function getClassFromFile(string $filePath): ?string + private static function getClassFromFile(string $filePath): string|null { $content = file_get_contents($filePath); if ($content === false) { @@ -56,7 +71,7 @@ private static function getClassFromFile(string $filePath): ?string $count = count($tokens); for ($i = 0; $i < $count; $i++) { - if (!isset($tokens[$i][0])) { + if (! isset($tokens[$i][0])) { continue; } @@ -69,12 +84,14 @@ private static function getClassFromFile(string $filePath): ?string } } - if ($tokens[$i][0] === T_CLASS || $tokens[$i][0] === T_INTERFACE) { - for ($j = $i + 1; $j < $count; $j++) { - if ($tokens[$j][0] === T_STRING) { - $class = $tokens[$j][1]; - break 2; - } + if ($tokens[$i][0] !== T_CLASS && $tokens[$i][0] !== T_INTERFACE) { + continue; + } + + for ($j = $i + 1; $j < $count; $j++) { + if ($tokens[$j][0] === T_STRING) { + $class = $tokens[$j][1]; + break 2; } } } From fa5013ac77c93663451ba318a3a471bbb97f52d2 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 00:22:27 +0900 Subject: [PATCH 03/10] Add type assertions and suppressions for static analysis Introduced `@phpstan-ignore-line` and `@psalm-suppress` to suppress static analysis warnings. Also added an explicit `assert` to ensure type checking for class and interface existence. These changes enhance code safety and maintainability by preventing potential runtime errors. --- src/ClassesInDirectories.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 9e15ddd7..40785ad2 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -28,13 +28,14 @@ final class ClassesInDirectories * * @return Generator */ - public static function list(string ...$directories): Generator + public static function list(string ...$directories): Generator // @phpstan-ignore-line { foreach ($directories as $directory) { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory), ); + /** @psalm-suppress MixedAssignment */ foreach ($iterator as $file) { if (! $file instanceof SplFileInfo) { continue; @@ -52,7 +53,7 @@ public static function list(string ...$directories): Generator if (! class_exists($className) && ! interface_exists($className)) { continue; } - + assert(class_exists($className) || interface_exists($className)); yield $className; } } From aa0b53aa905ad8c593eba24183ee8b0ca39b3de4 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 00:23:27 +0900 Subject: [PATCH 04/10] fixup! Add type assertions and suppressions for static analysis --- src/ClassesInDirectories.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 40785ad2..8d3ae0e4 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -9,6 +9,7 @@ use RecursiveIteratorIterator; use SplFileInfo; +use function assert; use function class_exists; use function count; use function file_get_contents; @@ -53,7 +54,9 @@ public static function list(string ...$directories): Generator // @phpstan-ignor if (! class_exists($className) && ! interface_exists($className)) { continue; } + assert(class_exists($className) || interface_exists($className)); + yield $className; } } From 427290a83ba9e5c9a3573a8103e956b2357c7585 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 00:58:31 +0900 Subject: [PATCH 05/10] Refactor class and namespace extraction Separated class and namespace extraction into distinct methods, improving readability and maintainability. Added assertions to ensure token types and values during extraction. --- src/ClassesInDirectories.php | 68 +++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 8d3ae0e4..bee56692 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -14,6 +14,8 @@ use function count; use function file_get_contents; use function interface_exists; +use function is_array; +use function is_string; use function token_get_all; use const T_CLASS; @@ -69,41 +71,65 @@ private static function getClassFromFile(string $filePath): string|null return null; } - $namespace = ''; - $class = ''; $tokens = token_get_all($content); - $count = count($tokens); + /** @var array $tokens */ - for ($i = 0; $i < $count; $i++) { - if (! isset($tokens[$i][0])) { + $namespace = self::extractNamespace($tokens); + $class = self::extractClassName($tokens); + + if ($class === null) { + return null; + } + + if ($namespace === null) { + return $class; + } + + return $namespace . '\\' . $class; + } + + /** @param array $tokens*/ + private static function extractNamespace(array $tokens): string|null + { + /** @psalm-suppress MixedAssignment */ + foreach ($tokens as $index => $token) { + if (is_array($token) && $token[0] !== T_NAMESPACE) { continue; } - if ($tokens[$i][0] === T_NAMESPACE) { - for ($j = $i + 1; $j < $count; $j++) { - if ($tokens[$j][0] === T_NAME_QUALIFIED) { - $namespace = $tokens[$j][1]; - break; - } + for ($j = $index + 1, $count = count($tokens); $j < $count; $j++) { + assert(is_array($tokens[$j]) && isset($tokens[$j][0])); + if ($tokens[$j][0] === T_NAME_QUALIFIED) { + $string = $tokens[$j][1]; + assert(is_string($string)); + + return $string; } } + } - if ($tokens[$i][0] !== T_CLASS && $tokens[$i][0] !== T_INTERFACE) { + return null; + } + + /** @param array $tokens */ + private static function extractClassName(array $tokens): string|null + { + foreach ($tokens as $index => $token) { + assert(is_array($token)); + if ($token[0] !== T_CLASS && $token[0] !== T_INTERFACE) { continue; } - for ($j = $i + 1; $j < $count; $j++) { - if ($tokens[$j][0] === T_STRING) { - $class = $tokens[$j][1]; - break 2; + for ($j = $index + 1, $count = count($tokens); $j < $count; $j++) { + if (is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) { + $string = $tokens[$j][1]; + assert(is_string($string)); + + return $string; } } } - if ($class === '') { - return null; - } - - return $namespace ? $namespace . '\\' . $class : $class; + return null; } } From 356983f20e3fdd0513e9f9ac8c70f2c45449d0fc Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 01:40:40 +0900 Subject: [PATCH 06/10] Remove redundant assertions in ClassesInDirectories.php Eliminated unnecessary assert statements that checked for array type, which are verified earlier in the code. This improves code clarity and reduces redundant operations. --- src/ClassesInDirectories.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index bee56692..5cb706be 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -98,7 +98,6 @@ private static function extractNamespace(array $tokens): string|null } for ($j = $index + 1, $count = count($tokens); $j < $count; $j++) { - assert(is_array($tokens[$j]) && isset($tokens[$j][0])); if ($tokens[$j][0] === T_NAME_QUALIFIED) { $string = $tokens[$j][1]; assert(is_string($string)); @@ -115,7 +114,6 @@ private static function extractNamespace(array $tokens): string|null private static function extractClassName(array $tokens): string|null { foreach ($tokens as $index => $token) { - assert(is_array($token)); if ($token[0] !== T_CLASS && $token[0] !== T_INTERFACE) { continue; } From ad48c3ac5894167cc2736fae8f71b4c7725065bd Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 01:47:06 +0900 Subject: [PATCH 07/10] Add ext-tokenizer to composer.json dependencies This change ensures that the tokenizer extension is available for use in the project. The tokenizer extension is crucial for some of the functionalities and its absence could lead to runtime errors. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 39102f86..d85fd93e 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "php": "^8.1", "ext-pdo": "*", "ext-mbstring": "*", + "ext-tokenizer": "*", "aura/sql": "^4.0 || ^5.0", "doctrine/annotations": "^1.12 || ^2.0", "guzzlehttp/guzzle": "^6.3 || ^7.2", From 4dab18d014b653f87e5f0ae64110af234ca3aba5 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 01:47:18 +0900 Subject: [PATCH 08/10] Fix potential PHP warnings due to undefined token elements Ensure token elements are defined before accessing them to prevent potential PHP warnings. Added checks using `isset` to verify element existence and included `@phpstan-ignore-line` comments for static analysis tools. --- src/ClassesInDirectories.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 5cb706be..4cd809fc 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -98,7 +98,8 @@ private static function extractNamespace(array $tokens): string|null } for ($j = $index + 1, $count = count($tokens); $j < $count; $j++) { - if ($tokens[$j][0] === T_NAME_QUALIFIED) { + if (isset($tokens[$j][0]) && $tokens[$j][0] === T_NAME_QUALIFIED) { // @phpstan-ignore-line + assert(isset($tokens[$j][1])); // @phpstan-ignore-line $string = $tokens[$j][1]; assert(is_string($string)); @@ -113,8 +114,9 @@ private static function extractNamespace(array $tokens): string|null /** @param array $tokens */ private static function extractClassName(array $tokens): string|null { + /** @psalm-suppress MixedAssignment */ foreach ($tokens as $index => $token) { - if ($token[0] !== T_CLASS && $token[0] !== T_INTERFACE) { + if (isset($token[0]) && $token[0] !== T_CLASS && $token[0] !== T_INTERFACE) { // @phpstan-ignore-line continue; } From 1966cafd8e2effd9457aef92d7aaf20105a6fab5 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 01:58:25 +0900 Subject: [PATCH 09/10] Update list method signature and remove unnecessary assertion Changed the list method to accept variadic string parameters for directories. Removed an assertion for class/interface existence that is deemed unnecessary. --- src/ClassesInDirectories.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 4cd809fc..5eb3631b 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -27,11 +27,11 @@ final class ClassesInDirectories { /** - * @param list $directories + + * @param string ...$directories * * @return Generator */ - public static function list(string ...$directories): Generator // @phpstan-ignore-line + public static function list(string ...$directories): Generator { foreach ($directories as $directory) { $iterator = new RecursiveIteratorIterator( @@ -57,8 +57,6 @@ public static function list(string ...$directories): Generator // @phpstan-ignor continue; } - assert(class_exists($className) || interface_exists($className)); - yield $className; } } From 3695ce24b62548eb0ec045404f005d21e580ef1d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 3 Nov 2024 02:05:18 +0900 Subject: [PATCH 10/10] Simplify file iteration and class validation logic Combined the checks for file type and extension into a single condition. Additionally, consolidated the class name validation to reduce redundancy and improve readability. --- src/ClassesInDirectories.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ClassesInDirectories.php b/src/ClassesInDirectories.php index 5eb3631b..6064d273 100644 --- a/src/ClassesInDirectories.php +++ b/src/ClassesInDirectories.php @@ -40,20 +40,13 @@ public static function list(string ...$directories): Generator /** @psalm-suppress MixedAssignment */ foreach ($iterator as $file) { - if (! $file instanceof SplFileInfo) { - continue; - } - - if ($file->getExtension() !== 'php') { + if (! $file instanceof SplFileInfo || $file->getExtension() !== 'php') { continue; } $className = self::getClassFromFile($file->getRealPath()); - if ($className === null) { - continue; - } - if (! class_exists($className) && ! interface_exists($className)) { + if ($className === null || ! class_exists($className) && ! interface_exists($className)) { continue; }