From 19821ab5c0df21b3a9a303305da07e8e0a9d6956 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Fri, 22 Mar 2024 16:07:52 +0100 Subject: [PATCH] feat: add skipBogonFiles method to reflection finders to prevent fatal errors in well-known cases --- lib/Finder/ComposerFinder.php | 5 ++++ lib/Finder/Psr0Finder.php | 8 +++++- lib/Finder/Psr4Finder.php | 8 +++++- lib/Finder/RecursiveFinder.php | 8 +++++- lib/Finder/ReflectionFilterTrait.php | 12 +++++++++ lib/Iterator/ComposerIterator.php | 3 ++- lib/Iterator/PhpDocumentorIterator.php | 2 +- lib/Iterator/Psr0Iterator.php | 1 + lib/Iterator/Psr4Iterator.php | 1 + lib/Iterator/RecursiveIterator.php | 1 + lib/Util/BogonFilesFilter.php | 30 +++++++++++++++++++++ phpunit.xml.dist | 1 + tests/issue-13/test-bogon-files-filter.phpt | 18 +++++++++++++ 13 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 lib/Util/BogonFilesFilter.php create mode 100644 tests/issue-13/test-bogon-files-filter.phpt diff --git a/lib/Finder/ComposerFinder.php b/lib/Finder/ComposerFinder.php index 0141557..6d7ee49 100644 --- a/lib/Finder/ComposerFinder.php +++ b/lib/Finder/ComposerFinder.php @@ -10,6 +10,7 @@ use Kcs\ClassFinder\Iterator\ComposerIterator; use Kcs\ClassFinder\Iterator\FilteredComposerIterator; use Kcs\ClassFinder\Reflection\ReflectorFactoryInterface; +use Kcs\ClassFinder\Util\BogonFilesFilter; use Reflector; use RuntimeException; use Symfony\Component\ErrorHandler\DebugClassLoader; @@ -93,6 +94,10 @@ public function getIterator(): Iterator }; } + if ($this->skipBogonClasses) { + $pathFilterCallback = BogonFilesFilter::getFileFilterFn($pathFilterCallback); + } + if ($this->namespaces || $this->dirs || $this->notNamespaces) { $iterator = new FilteredComposerIterator( $this->loader, diff --git a/lib/Finder/Psr0Finder.php b/lib/Finder/Psr0Finder.php index 8a683e9..e48ecc8 100644 --- a/lib/Finder/Psr0Finder.php +++ b/lib/Finder/Psr0Finder.php @@ -8,6 +8,7 @@ use Kcs\ClassFinder\Iterator\Psr0Iterator; use Kcs\ClassFinder\PathNormalizer; use Kcs\ClassFinder\Reflection\ReflectorFactoryInterface; +use Kcs\ClassFinder\Util\BogonFilesFilter; use Reflector; use function substr; @@ -50,11 +51,16 @@ public function setReflectorFactory(ReflectorFactoryInterface|null $reflectorFac /** @return Iterator */ public function getIterator(): Iterator { + $pathFilterCallback = $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null; + if ($this->skipBogonClasses) { + $pathFilterCallback = BogonFilesFilter::getFileFilterFn($pathFilterCallback); + } + return $this->applyFilters(new Psr0Iterator( $this->namespace, $this->path, reflectorFactory: $this->reflectorFactory, - pathCallback: $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null, + pathCallback: $pathFilterCallback, )); } } diff --git a/lib/Finder/Psr4Finder.php b/lib/Finder/Psr4Finder.php index 3c93462..5fd5f56 100644 --- a/lib/Finder/Psr4Finder.php +++ b/lib/Finder/Psr4Finder.php @@ -8,6 +8,7 @@ use Kcs\ClassFinder\Iterator\Psr4Iterator; use Kcs\ClassFinder\PathNormalizer; use Kcs\ClassFinder\Reflection\ReflectorFactoryInterface; +use Kcs\ClassFinder\Util\BogonFilesFilter; use Reflector; use function substr; @@ -50,11 +51,16 @@ public function setReflectorFactory(ReflectorFactoryInterface|null $reflectorFac /** @return Iterator */ public function getIterator(): Iterator { + $pathFilterCallback = $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null; + if ($this->skipBogonClasses) { + $pathFilterCallback = BogonFilesFilter::getFileFilterFn($pathFilterCallback); + } + return $this->applyFilters(new Psr4Iterator( $this->namespace, $this->path, $this->reflectorFactory, - pathCallback: $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null, + pathCallback: $pathFilterCallback, )); } } diff --git a/lib/Finder/RecursiveFinder.php b/lib/Finder/RecursiveFinder.php index 6e5c5fc..4c97eef 100644 --- a/lib/Finder/RecursiveFinder.php +++ b/lib/Finder/RecursiveFinder.php @@ -6,6 +6,7 @@ use Kcs\ClassFinder\Iterator\RecursiveIterator; use Kcs\ClassFinder\PathNormalizer; +use Kcs\ClassFinder\Util\BogonFilesFilter; use ReflectionClass; use Traversable; @@ -24,9 +25,14 @@ public function __construct(string $path) /** @return Traversable */ public function getIterator(): Traversable { + $pathFilterCallback = $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null; + if ($this->skipBogonClasses) { + $pathFilterCallback = BogonFilesFilter::getFileFilterFn($pathFilterCallback); + } + return $this->applyFilters(new RecursiveIterator( $this->path, - pathCallback: $this->pathFilterCallback !== null ? ($this->pathFilterCallback)(...) : null, + pathCallback: $pathFilterCallback, )); } } diff --git a/lib/Finder/ReflectionFilterTrait.php b/lib/Finder/ReflectionFilterTrait.php index 80ed50c..95792f9 100644 --- a/lib/Finder/ReflectionFilterTrait.php +++ b/lib/Finder/ReflectionFilterTrait.php @@ -15,6 +15,18 @@ trait ReflectionFilterTrait { use FinderTrait; + private bool $skipBogonClasses = false; + + /** + * Prevents the inclusion of files known to possibly cause bugs/fatal errors. + */ + public function skipBogonFiles(bool $skip = true): static + { + $this->skipBogonClasses = $skip; + + return $this; + } + /** * @param T $iterator * diff --git a/lib/Iterator/ComposerIterator.php b/lib/Iterator/ComposerIterator.php index fab9526..775961b 100644 --- a/lib/Iterator/ComposerIterator.php +++ b/lib/Iterator/ComposerIterator.php @@ -7,6 +7,7 @@ use Closure; use Composer\Autoload\ClassLoader; use Generator; +use Kcs\ClassFinder\PathNormalizer; use Kcs\ClassFinder\Reflection\NativeReflectorFactory; use Kcs\ClassFinder\Reflection\ReflectorFactoryInterface; use Kcs\ClassFinder\Util\ErrorHandler; @@ -43,7 +44,7 @@ private function searchInClassMap(): Generator { /** @var class-string $class */ foreach ($this->classLoader->getClassMap() as $class => $file) { - if ($this->pathCallback && ! ($this->pathCallback)($file)) { + if ($this->pathCallback && ! ($this->pathCallback)(PathNormalizer::resolvePath($file))) { continue; } diff --git a/lib/Iterator/PhpDocumentorIterator.php b/lib/Iterator/PhpDocumentorIterator.php index 70b6530..c998aaa 100644 --- a/lib/Iterator/PhpDocumentorIterator.php +++ b/lib/Iterator/PhpDocumentorIterator.php @@ -158,7 +158,7 @@ protected function isInstantiable(mixed $reflector): bool protected function getGenerator(): Generator { foreach ($this->scan() as $path => $info) { - if (! $this->accept($path)) { + if (! $this->accept(PathNormalizer::resolvePath($path))) { continue; } diff --git a/lib/Iterator/Psr0Iterator.php b/lib/Iterator/Psr0Iterator.php index 45dad90..417d097 100644 --- a/lib/Iterator/Psr0Iterator.php +++ b/lib/Iterator/Psr0Iterator.php @@ -72,6 +72,7 @@ class_exists($class, true); assert($include instanceof Closure); foreach ($this->search() as $path => $info) { + $path = PathNormalizer::resolvePath($path); if (! preg_match($pattern, $path, $m) || ! $info->isReadable()) { continue; } diff --git a/lib/Iterator/Psr4Iterator.php b/lib/Iterator/Psr4Iterator.php index 037f3fa..0befdef 100644 --- a/lib/Iterator/Psr4Iterator.php +++ b/lib/Iterator/Psr4Iterator.php @@ -72,6 +72,7 @@ class_exists($class, true); assert($include instanceof Closure); foreach ($this->search() as $path => $info) { + $path = PathNormalizer::resolvePath($path); if (! preg_match($pattern, $path, $m) || ! $info->isReadable()) { continue; } diff --git a/lib/Iterator/RecursiveIterator.php b/lib/Iterator/RecursiveIterator.php index a504d42..7973200 100644 --- a/lib/Iterator/RecursiveIterator.php +++ b/lib/Iterator/RecursiveIterator.php @@ -34,6 +34,7 @@ protected function getGenerator(): Generator $pattern = defined('HHVM_VERSION') ? '/\\.(php|hh)$/i' : '/\\.php$/i'; foreach ($this->search() as $path => $info) { + $path = PathNormalizer::resolvePath($path); if (! preg_match($pattern, $path, $m) || ! $info->isReadable()) { continue; } diff --git a/lib/Util/BogonFilesFilter.php b/lib/Util/BogonFilesFilter.php new file mode 100644 index 0000000..cd76dff --- /dev/null +++ b/lib/Util/BogonFilesFilter.php @@ -0,0 +1,30 @@ + true; + + return static function (string $path) use ($filter): bool { + if (preg_match(self::BOGON_FILES_REGEX, $path) === 1) { + return false; + } + + return $filter($path); + }; + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4715d42..bf2b88c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,6 +12,7 @@ tests/functional/error-handler/ tests/functional/nested-composer-projects/test.phpt tests/issue-13/test.phpt + tests/issue-13/test-bogon-files-filter.phpt tests/issue-13/test-path-callback.phpt diff --git a/tests/issue-13/test-bogon-files-filter.phpt b/tests/issue-13/test-bogon-files-filter.phpt new file mode 100644 index 0000000..80c2851 --- /dev/null +++ b/tests/issue-13/test-bogon-files-filter.phpt @@ -0,0 +1,18 @@ +--TEST-- +ComposerFinder - compatibility with symfony/cache: exclude path (#13) +--FILE-- +skipBogonFiles(); + +$count = 0; +foreach ($finder as $className => $reflector) { + ++$count; +} + +echo "OK" +?> +--EXPECT-- +OK