diff --git a/demo/parsing-whole-directory/example1.php b/demo/parsing-whole-directory/example1.php index ab9f687ba..96cce7613 100644 --- a/demo/parsing-whole-directory/example1.php +++ b/demo/parsing-whole-directory/example1.php @@ -17,6 +17,11 @@ $reflector = new DefaultReflector($sourceLocator); -$classReflections = $reflector->reflectAllClasses(); +$generator = $reflector->reflectAllClasses(); + +$classReflections = []; +foreach ($generator as $classReflection) { + $classReflections[] = $classReflection; +} !empty($classReflections) && print 'success'; diff --git a/demo/parsing-whole-directory/example2.php b/demo/parsing-whole-directory/example2.php index b7e514816..db4a45adc 100644 --- a/demo/parsing-whole-directory/example2.php +++ b/demo/parsing-whole-directory/example2.php @@ -23,6 +23,11 @@ $reflector = new DefaultReflector($sourceLocator); -$classReflections = $reflector->reflectAllClasses(); +$generator = $reflector->reflectAllClasses(); + +$classReflections = []; +foreach ($generator as $classReflection) { + $classReflections[] = $classReflection; +} !empty($classReflections) && print 'success'; diff --git a/src/Reflector/DefaultReflector.php b/src/Reflector/DefaultReflector.php index 8fb250047..8db4b2c55 100644 --- a/src/Reflector/DefaultReflector.php +++ b/src/Reflector/DefaultReflector.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\Reflector; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\ReflectionClass; @@ -43,11 +44,11 @@ public function reflectClass(string $identifierName): ReflectionClass /** * Get all the classes available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllClasses(): iterable + public function reflectAllClasses(): Generator { - /** @var list $allClasses */ + /** @var Generator $allClasses */ $allClasses = $this->sourceLocator->locateIdentifiersByType( $this, new IdentifierType(IdentifierType::IDENTIFIER_CLASS), @@ -79,11 +80,11 @@ public function reflectFunction(string $identifierName): ReflectionFunction /** * Get all the functions available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllFunctions(): iterable + public function reflectAllFunctions(): Generator { - /** @var list $allFunctions */ + /** @var Generator $allFunctions */ $allFunctions = $this->sourceLocator->locateIdentifiersByType( $this, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), @@ -115,11 +116,11 @@ public function reflectConstant(string $identifierName): ReflectionConstant /** * Get all the constants available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllConstants(): iterable + public function reflectAllConstants(): Generator { - /** @var list $allConstants */ + /** @var Generator $allConstants */ $allConstants = $this->sourceLocator->locateIdentifiersByType( $this, new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT), diff --git a/src/Reflector/Reflector.php b/src/Reflector/Reflector.php index d9d9b1e44..df4fe4461 100644 --- a/src/Reflector/Reflector.php +++ b/src/Reflector/Reflector.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\Reflector; +use Generator; use Roave\BetterReflection\Reflection\ReflectionClass; use Roave\BetterReflection\Reflection\ReflectionConstant; use Roave\BetterReflection\Reflection\ReflectionFunction; @@ -21,9 +22,9 @@ public function reflectClass(string $identifierName): ReflectionClass; /** * Get all the classes available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllClasses(): iterable; + public function reflectAllClasses(): Generator; /** * Create a ReflectionFunction for the specified $functionName. @@ -35,9 +36,9 @@ public function reflectFunction(string $identifierName): ReflectionFunction; /** * Get all the functions available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllFunctions(): iterable; + public function reflectAllFunctions(): Generator; /** * Create a ReflectionConstant for the specified $constantName. @@ -49,7 +50,7 @@ public function reflectConstant(string $identifierName): ReflectionConstant; /** * Get all the constants available in the scope specified by the SourceLocator. * - * @return list + * @return Generator */ - public function reflectAllConstants(): iterable; + public function reflectAllConstants(): Generator; } diff --git a/src/SourceLocator/Type/AbstractSourceLocator.php b/src/SourceLocator/Type/AbstractSourceLocator.php index cfc2aadda..27fdb6091 100644 --- a/src/SourceLocator/Type/AbstractSourceLocator.php +++ b/src/SourceLocator/Type/AbstractSourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; @@ -54,15 +55,15 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): * * @throws ParseToAstFailure */ - final public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + final public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { $locatedSource = $this->createLocatedSource(new Identifier(Identifier::WILDCARD, $identifierType)); if (! $locatedSource) { - return []; + return; } - return $this->astLocator->findReflectionsOfType( + yield from $this->astLocator->findReflectionsOfType( $reflector, $locatedSource, $identifierType, diff --git a/src/SourceLocator/Type/AggregateSourceLocator.php b/src/SourceLocator/Type/AggregateSourceLocator.php index a5a760093..a457a9b4c 100644 --- a/src/SourceLocator/Type/AggregateSourceLocator.php +++ b/src/SourceLocator/Type/AggregateSourceLocator.php @@ -4,14 +4,12 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; use Roave\BetterReflection\Reflector\Reflector; -use function array_map; -use function array_merge; - class AggregateSourceLocator implements SourceLocator { /** @param list $sourceLocators */ @@ -32,14 +30,10 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): return null; } - /** - * {@inheritDoc} - */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return array_merge( - [], - ...array_map(static fn (SourceLocator $sourceLocator): array => $sourceLocator->locateIdentifiersByType($reflector, $identifierType), $this->sourceLocators), - ); + foreach ($this->sourceLocators as $sourceLocator) { + yield from $sourceLocator->locateIdentifiersByType($reflector, $identifierType); + } } } diff --git a/src/SourceLocator/Type/AnonymousClassObjectSourceLocator.php b/src/SourceLocator/Type/AnonymousClassObjectSourceLocator.php index 23734964b..0558594cd 100644 --- a/src/SourceLocator/Type/AnonymousClassObjectSourceLocator.php +++ b/src/SourceLocator/Type/AnonymousClassObjectSourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\NodeTraverser; @@ -25,7 +26,6 @@ use Roave\BetterReflection\SourceLocator\Located\AnonymousLocatedSource; use Roave\BetterReflection\Util\FileHelper; -use function array_filter; use function assert; use function file_get_contents; use function str_contains; @@ -55,9 +55,14 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): * * @throws ParseToAstFailure */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return array_filter([$this->getReflectionClass($reflector, $identifierType)]); + $reflection = $this->getReflectionClass($reflector, $identifierType); + if (! $reflection) { + return; + } + + yield $reflection; } private function getReflectionClass(Reflector $reflector, IdentifierType $identifierType): ReflectionClass|null diff --git a/src/SourceLocator/Type/ClosureSourceLocator.php b/src/SourceLocator/Type/ClosureSourceLocator.php index 74cc006f1..043549008 100644 --- a/src/SourceLocator/Type/ClosureSourceLocator.php +++ b/src/SourceLocator/Type/ClosureSourceLocator.php @@ -5,6 +5,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; use Closure; +use Generator; use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeTraverser; @@ -26,7 +27,6 @@ use Roave\BetterReflection\SourceLocator\Located\AnonymousLocatedSource; use Roave\BetterReflection\Util\FileHelper; -use function array_filter; use function assert; use function file_get_contents; use function str_contains; @@ -56,9 +56,14 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): * * @throws ParseToAstFailure */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return array_filter([$this->getReflectionFunction($reflector, $identifierType)]); + $reflection = $this->getReflectionFunction($reflector, $identifierType); + if (! $reflection) { + return; + } + + yield $reflection; } private function getReflectionFunction(Reflector $reflector, IdentifierType $identifierType): ReflectionFunction|null diff --git a/src/SourceLocator/Type/Composer/PsrAutoloaderLocator.php b/src/SourceLocator/Type/Composer/PsrAutoloaderLocator.php index ec6b97ca7..7ae0c7f47 100644 --- a/src/SourceLocator/Type/Composer/PsrAutoloaderLocator.php +++ b/src/SourceLocator/Type/Composer/PsrAutoloaderLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type\Composer; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; @@ -54,11 +55,11 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): /** * Find all identifiers of a type * - * @return list + * @return Generator */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return (new DirectoriesSourceLocator( + yield from (new DirectoriesSourceLocator( $this->mapping->directories(), $this->astLocator, ))->locateIdentifiersByType($reflector, $identifierType); diff --git a/src/SourceLocator/Type/DirectoriesSourceLocator.php b/src/SourceLocator/Type/DirectoriesSourceLocator.php index ff3256b26..dee0ddb52 100644 --- a/src/SourceLocator/Type/DirectoriesSourceLocator.php +++ b/src/SourceLocator/Type/DirectoriesSourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use Roave\BetterReflection\Identifier\Identifier; @@ -55,11 +56,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): return $this->aggregateSourceLocator->locateIdentifier($reflector, $identifier); } - /** - * {@inheritDoc} - */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return $this->aggregateSourceLocator->locateIdentifiersByType($reflector, $identifierType); + yield from $this->aggregateSourceLocator->locateIdentifiersByType($reflector, $identifierType); } } diff --git a/src/SourceLocator/Type/FileIteratorSourceLocator.php b/src/SourceLocator/Type/FileIteratorSourceLocator.php index dcd9cc0f0..c0da4ad09 100644 --- a/src/SourceLocator/Type/FileIteratorSourceLocator.php +++ b/src/SourceLocator/Type/FileIteratorSourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use Iterator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; @@ -83,8 +84,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): * * @throws InvalidFileLocation */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { - return $this->getAggregatedSourceLocator()->locateIdentifiersByType($reflector, $identifierType); + yield from $this->getAggregatedSourceLocator()->locateIdentifiersByType($reflector, $identifierType); } } diff --git a/src/SourceLocator/Type/MemoizingSourceLocator.php b/src/SourceLocator/Type/MemoizingSourceLocator.php index 8ac20e31a..10229543b 100644 --- a/src/SourceLocator/Type/MemoizingSourceLocator.php +++ b/src/SourceLocator/Type/MemoizingSourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; @@ -37,17 +38,23 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): = $this->wrappedSourceLocator->locateIdentifier($reflector, $identifier); } - /** @return list */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + /** @return Generator */ + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator { $cacheKey = sprintf('%s_%s', $this->reflectorCacheKey($reflector), $this->identifierTypeToCacheKey($identifierType)); if (array_key_exists($cacheKey, $this->cacheByIdentifierTypeKeyAndOid)) { - return $this->cacheByIdentifierTypeKeyAndOid[$cacheKey]; + yield from $this->cacheByIdentifierTypeKeyAndOid[$cacheKey]; + + return; + } + + $items = []; + foreach ($this->wrappedSourceLocator->locateIdentifiersByType($reflector, $identifierType) as $item) { + yield $items[] = $item; } - return $this->cacheByIdentifierTypeKeyAndOid[$cacheKey] - = $this->wrappedSourceLocator->locateIdentifiersByType($reflector, $identifierType); + $this->cacheByIdentifierTypeKeyAndOid[$cacheKey] = $items; } private function reflectorCacheKey(Reflector $reflector): string diff --git a/src/SourceLocator/Type/SourceLocator.php b/src/SourceLocator/Type/SourceLocator.php index 372b51cd6..61411a954 100644 --- a/src/SourceLocator/Type/SourceLocator.php +++ b/src/SourceLocator/Type/SourceLocator.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\SourceLocator\Type; +use Generator; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; @@ -26,7 +27,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): /** * Find all identifiers of a type * - * @return list + * @return Generator */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array; + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator; } diff --git a/src/Util/FindReflectionOnLine.php b/src/Util/FindReflectionOnLine.php index 24c6cac9f..f10c39d65 100644 --- a/src/Util/FindReflectionOnLine.php +++ b/src/Util/FindReflectionOnLine.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflection\Util; +use Generator; use InvalidArgumentException; use Roave\BetterReflection\Identifier\IdentifierType; use Roave\BetterReflection\Reflection\Reflection; @@ -19,8 +20,6 @@ use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator; use Roave\BetterReflection\SourceLocator\Type\SourceLocator; -use function array_merge; - final class FindReflectionOnLine { public function __construct(private SourceLocator $sourceLocator, private Locator $astLocator) @@ -40,9 +39,9 @@ public function __construct(private SourceLocator $sourceLocator, private Locato */ public function __invoke(string $filename, int $lineNumber): ReflectionMethod|ReflectionClass|ReflectionFunction|ReflectionConstant|Reflection|null { - $reflections = $this->computeReflections($filename); + $reflectionsGenerator = $this->computeReflections($filename); - foreach ($reflections as $reflection) { + foreach ($reflectionsGenerator as $reflection) { if ($reflection instanceof ReflectionClass && $this->containsLine($reflection, $lineNumber)) { foreach ($reflection->getMethods() as $method) { if ($this->containsLine($method, $lineNumber)) { @@ -70,20 +69,29 @@ public function __invoke(string $filename, int $lineNumber): ReflectionMethod|Re * * @param non-empty-string $filename * - * @return list + * @return Generator * * @throws ParseToAstFailure * @throws InvalidFileLocation */ - private function computeReflections(string $filename): array + private function computeReflections(string $filename): Generator { $singleFileSourceLocator = new SingleFileSourceLocator($filename, $this->astLocator); $reflector = new DefaultReflector(new AggregateSourceLocator([$singleFileSourceLocator, $this->sourceLocator])); - return array_merge( - $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)), - $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION)), - $singleFileSourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT)), + yield from $singleFileSourceLocator->locateIdentifiersByType( + $reflector, + new IdentifierType(IdentifierType::IDENTIFIER_CLASS), + ); + + yield from $singleFileSourceLocator->locateIdentifiersByType( + $reflector, + new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), + ); + + yield from $singleFileSourceLocator->locateIdentifiersByType( + $reflector, + new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT), ); } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index b4c928bf2..60aced7ff 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -1153,7 +1153,13 @@ public function testIsAnonymousWithAnonymousClassNoNamespace(): void $this->astLocator, )); - $allClassesInfo = $reflector->reflectAllClasses(); + $generator = $reflector->reflectAllClasses(); + + $allClassesInfo = []; + foreach ($generator as $reflectionClass) { + $allClassesInfo[] = $reflectionClass; + } + self::assertCount(1, $allClassesInfo); $classInfo = $allClassesInfo[0]; @@ -1171,14 +1177,17 @@ public function testIsAnonymousWithAnonymousClassInNamespace(): void )); $allClassesInfo = $reflector->reflectAllClasses(); - self::assertCount(2, $allClassesInfo); + $count = 0; foreach ($allClassesInfo as $classInfo) { self::assertTrue($classInfo->isAnonymous()); self::assertFalse($classInfo->inNamespace()); self::assertStringStartsWith(ReflectionClass::ANONYMOUS_CLASS_NAME_PREFIX, $classInfo->getName()); self::assertStringMatchesFormat('%sFixture/AnonymousClassInNamespace.php(%d)', $classInfo->getName()); + ++$count; } + + self::assertSame(2, $count); } public function testIsAnonymousWithNestedAnonymousClasses(): void @@ -1189,14 +1198,17 @@ public function testIsAnonymousWithNestedAnonymousClasses(): void )); $allClassesInfo = $reflector->reflectAllClasses(); - self::assertCount(3, $allClassesInfo); + $count = 0; foreach ($allClassesInfo as $classInfo) { self::assertTrue($classInfo->isAnonymous()); self::assertFalse($classInfo->inNamespace()); self::assertStringStartsWith(ReflectionClass::ANONYMOUS_CLASS_NAME_PREFIX, $classInfo->getName()); self::assertStringMatchesFormat('%sFixture/NestedAnonymousClassInstances.php(%d)', $classInfo->getName()); + ++$count; } + + self::assertSame(3, $count); } public function testAnonymousClassWithParent(): void @@ -1206,7 +1218,11 @@ public function testAnonymousClassWithParent(): void $this->astLocator, )); - $allClassesInfo = $reflector->reflectAllClasses(); + $allClassesInfo = []; + foreach ($reflector->reflectAllClasses() as $classInfo) { + $allClassesInfo[] = $classInfo; + } + self::assertCount(3, $allClassesInfo); $classInfo = $allClassesInfo[2]; @@ -1227,7 +1243,13 @@ public function testAnonymousClassWithParentDefinedLater(): void $this->astLocator, )); - $allClassesInfo = $reflector->reflectAllClasses(); + $generator = $reflector->reflectAllClasses(); + + $allClassesInfo = []; + foreach ($generator as $reflectionClass) { + $allClassesInfo[] = $reflectionClass; + } + self::assertCount(2, $allClassesInfo); $classInfo = $allClassesInfo[0]; @@ -1244,7 +1266,13 @@ public function testAnonymousClassWithInterface(): void $this->astLocator, )); - $allClassesInfo = $reflector->reflectAllClasses(); + $generator = $reflector->reflectAllClasses(); + + $allClassesInfo = []; + foreach ($generator as $reflectionClass) { + $allClassesInfo[] = $reflectionClass; + } + self::assertCount(3, $allClassesInfo); $classInfo = $allClassesInfo[2]; @@ -1266,7 +1294,13 @@ function createAnonymous() $reflector = new DefaultReflector(new StringSourceLocator($php, $this->astLocator)); - $allClassesInfo = $reflector->reflectAllClasses(); + $generator = $reflector->reflectAllClasses(); + + $allClassesInfo = []; + foreach ($generator as $reflectionClass) { + $allClassesInfo[] = $reflectionClass; + } + self::assertCount(1, $allClassesInfo); $classInfo = $allClassesInfo[0]; diff --git a/test/unit/Reflector/DefaultReflectorTest.php b/test/unit/Reflector/DefaultReflectorTest.php index a6688e60d..5601f83bf 100644 --- a/test/unit/Reflector/DefaultReflectorTest.php +++ b/test/unit/Reflector/DefaultReflectorTest.php @@ -56,10 +56,15 @@ public function testThrowsExceptionWhenClassIdentifierNotFound(): void public function testReflectAllClasses(): void { - $classes = (new DefaultReflector( + $generator = (new DefaultReflector( new SingleFileSourceLocator(__DIR__ . '/../Fixture/ExampleClass.php', $this->astLocator), ))->reflectAllClasses(); + $classes = []; + foreach ($generator as $class) { + $classes[] = $class; + } + self::assertContainsOnlyInstancesOf(ReflectionClass::class, $classes); self::assertCount(11, $classes); } @@ -88,10 +93,15 @@ public function testThrowsExceptionWhenFunctionIdentifierNotFound(): void public function testReflectAllFunction(): void { - $functions = (new DefaultReflector( + $generator = (new DefaultReflector( new SingleFileSourceLocator(__DIR__ . '/../Fixture/Functions.php', $this->astLocator), ))->reflectAllFunctions(); + $functions = []; + foreach ($generator as $reflection) { + $functions[] = $reflection; + } + self::assertContainsOnlyInstancesOf(ReflectionFunction::class, $functions); self::assertCount(2, $functions); } @@ -120,10 +130,15 @@ public function testThrowsExceptionWhenConstantIdentifierNotFound(): void public function testReflectAllConstants(): void { - $constants = (new DefaultReflector( + $generator = (new DefaultReflector( new SingleFileSourceLocator(__DIR__ . '/../Fixture/Constants.php', $this->astLocator), ))->reflectAllConstants(); + $constants = []; + foreach ($generator as $reflection) { + $constants[] = $reflection; + } + self::assertContainsOnlyInstancesOf(ReflectionConstant::class, $constants); self::assertCount(5, $constants); } diff --git a/test/unit/SourceLocator/Type/AbstractSourceLocatorTest.php b/test/unit/SourceLocator/Type/AbstractSourceLocatorTest.php index a7a913ae0..760c81a20 100644 --- a/test/unit/SourceLocator/Type/AbstractSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/AbstractSourceLocatorTest.php @@ -15,6 +15,9 @@ use Roave\BetterReflection\SourceLocator\Located\LocatedSource; use Roave\BetterReflection\SourceLocator\Type\AbstractSourceLocator; +use function iterator_count; +use function iterator_to_array; + #[CoversClass(AbstractSourceLocator::class)] class AbstractSourceLocatorTest extends TestCase { @@ -126,7 +129,9 @@ public function testLocateIdentifiersByTypeCallsFindReflectionsOfType(): void ->method('createLocatedSource') ->willReturn($locatedSource); - self::assertSame([$mockReflection], $sourceLocator->locateIdentifiersByType($mockReflector, $identifierType)); + $generator = $sourceLocator->locateIdentifiersByType($mockReflector, $identifierType); + + self::assertSame([$mockReflection], iterator_to_array($generator)); } public function testLocateIdentifiersByTypeReturnsEmptyArrayWithoutTryingToFindReflectionsWhenUnableToLocateSource(): void @@ -149,6 +154,8 @@ public function testLocateIdentifiersByTypeReturnsEmptyArrayWithoutTryingToFindR ->method('createLocatedSource') ->willReturn(null); - self::assertSame([], $sourceLocator->locateIdentifiersByType($mockReflector, $identifierType)); + $generator = $sourceLocator->locateIdentifiersByType($mockReflector, $identifierType); + + self::assertSame(0, iterator_count($generator)); } } diff --git a/test/unit/SourceLocator/Type/AggregateSourceLocatorTest.php b/test/unit/SourceLocator/Type/AggregateSourceLocatorTest.php index 267cbd55d..dc4f9a4c3 100644 --- a/test/unit/SourceLocator/Type/AggregateSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/AggregateSourceLocatorTest.php @@ -110,19 +110,26 @@ public function testLocateIdentifiersByTypeAggregatesSource(): void $source3 = $this->createMock(ReflectionClass::class); - $locator1->expects($this->once())->method('locateIdentifiersByType')->willReturn([]); - $locator2->expects($this->once())->method('locateIdentifiersByType')->willReturn([$source2]); - $locator3->expects($this->once())->method('locateIdentifiersByType')->willReturn([$source3]); - $locator4->expects($this->once())->method('locateIdentifiersByType')->willReturn([]); + $locator1->expects($this->once())->method('locateIdentifiersByType')->willReturnCallback(static fn () => yield from []); + $locator2->expects($this->once())->method('locateIdentifiersByType')->willReturnCallback(static fn () => yield $source2); + $locator3->expects($this->once())->method('locateIdentifiersByType')->willReturnCallback(static fn () => yield $source3); + $locator4->expects($this->once())->method('locateIdentifiersByType')->willReturnCallback(static fn () => yield from []); + + $generator = (new AggregateSourceLocator([ + $locator1, + $locator2, + $locator3, + $locator4, + ]))->locateIdentifiersByType($this->getMockReflector(), $identifierType); + + $result = []; + foreach ($generator as $reflectionClass) { + $result[] = $reflectionClass; + } self::assertSame( [$source2, $source3], - (new AggregateSourceLocator([ - $locator1, - $locator2, - $locator3, - $locator4, - ]))->locateIdentifiersByType($this->getMockReflector(), $identifierType), + $result, ); } } diff --git a/test/unit/SourceLocator/Type/AnonymousClassObjectSourceLocatorTest.php b/test/unit/SourceLocator/Type/AnonymousClassObjectSourceLocatorTest.php index cdc9f1a04..ba662b2b8 100644 --- a/test/unit/SourceLocator/Type/AnonymousClassObjectSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/AnonymousClassObjectSourceLocatorTest.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflectionTest\SourceLocator\Type; +use Generator; use PhpParser\Parser; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; @@ -25,6 +26,7 @@ use function assert; use function is_string; +use function iterator_count; use function realpath; use function sprintf; @@ -114,12 +116,17 @@ public function testLocateIdentifierWithFunctionIdentifier(): void #[DataProvider('anonymousClassInstancesProvider')] public function testLocateIdentifiersByType(object $class, string $file, int $startLine, int $endLine): void { - /** @var list $reflections */ - $reflections = (new AnonymousClassObjectSourceLocator($class, $this->parser))->locateIdentifiersByType( + /** @var Generator $generator */ + $generator = (new AnonymousClassObjectSourceLocator($class, $this->parser))->locateIdentifiersByType( $this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS), ); + $reflections = []; + foreach ($generator as $reflection) { + $reflections[] = $reflection; + } + self::assertCount(1, $reflections); self::assertArrayHasKey(0, $reflections); @@ -135,13 +142,13 @@ public function testLocateIdentifiersByTypeWithFunctionIdentifier(): void $anonymousClass = new class { }; - /** @var list $reflections */ + /** @var Generator $reflections */ $reflections = (new AnonymousClassObjectSourceLocator($anonymousClass, $this->parser))->locateIdentifiersByType( $this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), ); - self::assertCount(0, $reflections); + self::assertSame(0, iterator_count($reflections)); } public function testExceptionIfAnonymousClassNotFoundOnExpectedLine(): void @@ -168,9 +175,12 @@ public function testExceptionIfAnonymousClassNotFoundOnExpectedLine(): void $coreReflectionPropertyInSourceLocatatorReflection->setAccessible(true); $coreReflectionPropertyInSourceLocatatorReflection->setValue($sourceLocator, $coreReflectionPropertyMock); - $this->expectException(NoAnonymousClassOnLine::class); + $generator = $sourceLocator->locateIdentifiersByType($this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)); - $sourceLocator->locateIdentifiersByType($this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)); + $this->expectException(NoAnonymousClassOnLine::class); + foreach ($generator as $reflection) { + continue; + } } /** @return list */ diff --git a/test/unit/SourceLocator/Type/ClosureSourceLocatorTest.php b/test/unit/SourceLocator/Type/ClosureSourceLocatorTest.php index 7e2d31e54..9b0c19e7c 100644 --- a/test/unit/SourceLocator/Type/ClosureSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/ClosureSourceLocatorTest.php @@ -5,6 +5,7 @@ namespace Roave\BetterReflectionTest\SourceLocator\Type; use Closure; +use Generator; use PhpParser\Parser; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; @@ -26,6 +27,7 @@ use function assert; use function is_string; +use function iterator_count; use function realpath; use function sprintf; @@ -105,12 +107,17 @@ public function testEvaledClosureThrowsInvalidFileLocation(): void #[DataProvider('closuresProvider')] public function testLocateIdentifiersByType(Closure $closure, string|null $namespace, string $file, int $startLine, int $endLine): void { - /** @var list $reflections */ - $reflections = (new ClosureSourceLocator($closure, $this->parser))->locateIdentifiersByType( + /** @var Generator $generator */ + $generator = (new ClosureSourceLocator($closure, $this->parser))->locateIdentifiersByType( $this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), ); + $reflections = []; + foreach ($generator as $reflection) { + $reflections[] = $reflection; + } + self::assertCount(1, $reflections); self::assertArrayHasKey(0, $reflections); @@ -144,9 +151,12 @@ public function testExceptionIfClosureNotFoundOnExpectedLine(): void $coreReflectionPropertyInSourceLocatatorReflection->setAccessible(true); $coreReflectionPropertyInSourceLocatatorReflection->setValue($sourceLocator, $coreReflectionPropertyMock); - $this->expectException(NoClosureOnLine::class); + $generator = $sourceLocator->locateIdentifiersByType($this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION)); - $sourceLocator->locateIdentifiersByType($this->reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION)); + $this->expectException(NoClosureOnLine::class); + foreach ($generator as $reflection) { + continue; + } } public function testLocateIdentifiersByTypeWithClassIdentifier(): void @@ -160,7 +170,7 @@ public function testLocateIdentifiersByTypeWithClassIdentifier(): void new IdentifierType(IdentifierType::IDENTIFIER_CLASS), ); - self::assertCount(0, $reflections); + self::assertSame(0, iterator_count($reflections)); } /** @return list */ diff --git a/test/unit/SourceLocator/Type/Composer/PsrAutoloaderLocatorTest.php b/test/unit/SourceLocator/Type/Composer/PsrAutoloaderLocatorTest.php index 1885e1507..eaae94f63 100644 --- a/test/unit/SourceLocator/Type/Composer/PsrAutoloaderLocatorTest.php +++ b/test/unit/SourceLocator/Type/Composer/PsrAutoloaderLocatorTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\TestCase; use Roave\BetterReflection\Identifier\Identifier; use Roave\BetterReflection\Identifier\IdentifierType; -use Roave\BetterReflection\Reflection\Reflection; use Roave\BetterReflection\Reflector\DefaultReflector; use Roave\BetterReflection\Reflector\Reflector; use Roave\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping; @@ -21,7 +20,6 @@ use Roave\BetterReflectionTest\Assets\DirectoryScannerAssetsFoo\Foo as Foo1; use Roave\BetterReflectionTest\BetterReflectionSingleton; -use function array_map; use function sort; #[CoversClass(PsrAutoloaderLocator::class)] @@ -166,14 +164,16 @@ public function testWillLocateAllClassesInMappedPsr4Paths(): void Foo1::class, ]; - $actual = array_map( - static fn (Reflection $reflection): string => $reflection->getName(), - $locator->locateIdentifiersByType( - new DefaultReflector($locator), - new IdentifierType(IdentifierType::IDENTIFIER_CLASS), - ), + $generator = $locator->locateIdentifiersByType( + new DefaultReflector($locator), + new IdentifierType(IdentifierType::IDENTIFIER_CLASS), ); + $actual = []; + foreach ($generator as $reflection) { + $actual[] = $reflection->getName(); + } + // Sorting may depend on filesystem here sort($expected); sort($actual); diff --git a/test/unit/SourceLocator/Type/DirectoriesSourceLocatorTest.php b/test/unit/SourceLocator/Type/DirectoriesSourceLocatorTest.php index 934832c19..255f7b541 100644 --- a/test/unit/SourceLocator/Type/DirectoriesSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/DirectoriesSourceLocatorTest.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflectionTest\SourceLocator\Type; +use Generator; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -17,7 +18,6 @@ use Roave\BetterReflectionTest\Assets\DirectoryScannerAssetsFoo; use Roave\BetterReflectionTest\BetterReflectionSingleton; -use function array_map; use function sort; use function uniqid; @@ -41,18 +41,18 @@ public function setUp(): void public function testScanDirectoryClasses(): void { - /** @var list $classes */ - $classes = $this->sourceLocator->locateIdentifiersByType( + /** @var Generator $generator */ + $generator = $this->sourceLocator->locateIdentifiersByType( new DefaultReflector($this->sourceLocator), new IdentifierType(IdentifierType::IDENTIFIER_CLASS), ); - self::assertCount(4, $classes); + $classNames = []; + foreach ($generator as $reflectionClass) { + $classNames[] = $reflectionClass->getName(); + } - $classNames = array_map( - static fn (ReflectionClass $reflectionClass): string => $reflectionClass->getName(), - $classes, - ); + self::assertCount(4, $classNames); sort($classNames); diff --git a/test/unit/SourceLocator/Type/FileIteratorSourceLocatorTest.php b/test/unit/SourceLocator/Type/FileIteratorSourceLocatorTest.php index a13ab201b..597a5f017 100644 --- a/test/unit/SourceLocator/Type/FileIteratorSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/FileIteratorSourceLocatorTest.php @@ -5,6 +5,8 @@ namespace Roave\BetterReflectionTest\SourceLocator\Type; use ArrayIterator; +use FilesystemIterator; +use Generator; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use RecursiveDirectoryIterator; @@ -19,7 +21,6 @@ use Roave\BetterReflectionTest\BetterReflectionSingleton; use stdClass; -use function array_map; use function sort; #[CoversClass(FileIteratorSourceLocator::class)] @@ -34,7 +35,7 @@ public function setUp(): void $this->sourceLocator = new FileIteratorSourceLocator( new RecursiveIteratorIterator(new RecursiveDirectoryIterator( __DIR__ . '/../../Assets/DirectoryScannerAssets', - RecursiveDirectoryIterator::SKIP_DOTS, + FilesystemIterator::SKIP_DOTS, ), RecursiveIteratorIterator::SELF_FIRST), BetterReflectionSingleton::instance()->astLocator(), ); @@ -42,18 +43,18 @@ public function setUp(): void public function testScanDirectoryClasses(): void { - /** @var list $classes */ + /** @var Generator $classes */ $classes = $this->sourceLocator->locateIdentifiersByType( new DefaultReflector($this->sourceLocator), new IdentifierType(IdentifierType::IDENTIFIER_CLASS), ); - self::assertCount(2, $classes); + $classNames = []; + foreach ($classes as $reflectionClass) { + $classNames[] = $reflectionClass->getName(); + } - $classNames = array_map( - static fn (ReflectionClass $reflectionClass): string => $reflectionClass->getName(), - $classes, - ); + self::assertCount(2, $classNames); sort($classNames); diff --git a/test/unit/SourceLocator/Type/MemoizingSourceLocatorTest.php b/test/unit/SourceLocator/Type/MemoizingSourceLocatorTest.php index 6263ee2cd..31c4422b1 100644 --- a/test/unit/SourceLocator/Type/MemoizingSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/MemoizingSourceLocatorTest.php @@ -4,6 +4,7 @@ namespace Roave\BetterReflectionTest\SourceLocator\Type; +use Generator; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,6 +21,7 @@ use function array_unique; use function count; use function in_array; +use function iterator_count; use function random_int; use function range; use function spl_object_id; @@ -134,36 +136,98 @@ public function testMemoizationByTypeDistinguishesBetweenSourceLocatorsAndType() ) use ( $symbols1, $symbols2, - ): array { + ): Generator { if ($reflector === $this->reflector1) { - return $symbols1[$identifierType->getName()]; + yield from $symbols1[$identifierType->getName()]; + + return; } - return $symbols2[$identifierType->getName()]; + yield from $symbols2[$identifierType->getName()]; }); foreach ($types as $type) { + $generator = $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $type); + + $reflections1 = []; + foreach ($generator as $reflection) { + $reflections1[] = $reflection; + } + self::assertSame( $symbols1[$type->getName()], - $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $type), + $reflections1, ); + + $generator = $this->memoizingLocator->locateIdentifiersByType($this->reflector2, $type); + + $reflections2 = []; + foreach ($generator as $reflection) { + $reflections2[] = $reflection; + } + self::assertSame( $symbols2[$type->getName()], - $this->memoizingLocator->locateIdentifiersByType($this->reflector2, $type), + $reflections2, ); // second execution - ensures that memoization is in place + $generator = $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $type); + + $reflections1 = []; + foreach ($generator as $reflection) { + $reflections1[] = $reflection; + } + self::assertSame( $symbols1[$type->getName()], - $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $type), + $reflections1, ); + + $generator = $this->memoizingLocator->locateIdentifiersByType($this->reflector2, $type); + + $reflections2 = []; + foreach ($generator as $reflection) { + $reflections2[] = $reflection; + } + self::assertSame( $symbols2[$type->getName()], - $this->memoizingLocator->locateIdentifiersByType($this->reflector2, $type), + $reflections2, ); } } + public function testNotCompletedMemoization(): void + { + $this + ->wrappedLocator + ->expects($this->exactly(2)) + ->method('locateIdentifiersByType') + ->with($this->reflector1) + ->willReturnCallback(function (): Generator { + yield from [ + $this->createMock(Reflection::class), + $this->createMock(Reflection::class), + $this->createMock(Reflection::class), + ]; + }); + + $classType = new IdentifierType(IdentifierType::IDENTIFIER_CLASS); + + $generator = $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $classType); + + // Started iterating but did not complete the operation + $generator->next(); + + // The cache will not be saved until we have completed all iterations + $generator2 = $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $classType); + $this->assertSame(3, iterator_count($generator2)); + + $generator3 = $this->memoizingLocator->locateIdentifiersByType($this->reflector1, $classType); + $this->assertSame(3, iterator_count($generator3)); + } + /** * @param list $identifiers * @param list $reflectors