diff --git a/Finder.php b/Finder.php index 4438df9b..27c0d44c 100644 --- a/Finder.php +++ b/Finder.php @@ -57,8 +57,10 @@ class Finder implements \IteratorAggregate, \Countable private bool $reverseSorting = false; private \Closure|int|false $sort = false; private int $ignore = 0; + /** @var list */ private array $dirs = []; private array $dates = []; + /** @var list> */ private array $iterators = []; private array $contains = []; private array $notContains = []; @@ -663,27 +665,33 @@ public function in(string|array $dirs): static */ public function getIterator(): \Iterator { - if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + if (!$this->dirs && !$this->iterators) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); } - if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + if (1 === \count($this->dirs) && !$this->iterators) { $iterator = $this->searchInDirectory($this->dirs[0]); - - if ($this->sort || $this->reverseSorting) { - $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } else { + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); } - return $iterator; - } - - $iterator = new \AppendIterator(); - foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); - } - - foreach ($this->iterators as $it) { - $iterator->append($it); + foreach ($this->iterators as $it) { + $iterator->append((static function () use ($it) { + foreach ($it as $file) { + if (!$file instanceof \SplFileInfo) { + $file = new \SplFileInfo($file); + } + $key = $file->getPathname(); + if (!$file instanceof SplFileInfo) { + $file = new SplFileInfo($key, $file->getPath(), $key); + } + + yield $key => $file; + } + })()); + } } if ($this->sort || $this->reverseSorting) { @@ -698,22 +706,13 @@ public function getIterator(): \Iterator * * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * + * @param iterable $iterator + * * @return $this */ public function append(iterable $iterator): static { - if ($iterator instanceof \IteratorAggregate) { - $this->iterators[] = $iterator->getIterator(); - } elseif ($iterator instanceof \Iterator) { - $this->iterators[] = $iterator; - } else { - $it = new \ArrayIterator(); - foreach ($iterator as $file) { - $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); - $it[$file->getPathname()] = $file; - } - $this->iterators[] = $it; - } + $this->iterators[] = $iterator; return $this; } diff --git a/Tests/FinderTest.php b/Tests/FinderTest.php index b336e849..aae0c84b 100644 --- a/Tests/FinderTest.php +++ b/Tests/FinderTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\ExpectationFailedException; use Symfony\Component\Finder\Exception\DirectoryNotFoundException; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; class FinderTest extends Iterator\RealIteratorTestCase { @@ -1296,6 +1297,121 @@ public function testAppendWithAnArray() $this->assertIterator($this->toAbsolute(['foo', 'foo/bar.tmp', 'toto']), $finder->getIterator()); } + public function testAppendStandardizesItemsToBeSymfonySplFileInfo() + { + $finder1 = $this->buildFinder(); + $finder1->files()->in(self::$tmpDir.\DIRECTORY_SEPARATOR.'foo'); + + $finder2 = $this->buildFinder(); + $finder2->directories()->in(self::$tmpDir); + + $finder1->append($finder2); + $finder1->append($this->toAbsolute(['foo'])); + $finder1->append(array_map(static fn ($item) => new \SplFileInfo($item), $this->toAbsolute(['toto']))); + + foreach ($finder1 as $item) { + $this->assertInstanceOf(SplFileInfo::class, $item); + } + } + + public function testRelativePathOfAppendedItems() + { + $this->setupVfsProvider([ + 'a' => [ + 'a1' => '', + 'a2' => '', + 'b' => [ + 'b1' => '', + 'b2' => '', + 'c' => [ + 'c1' => '', + 'c2' => '', + ], + ], + ], + ]); + + $formatForAssert = static function (Finder $finder) { + $data = []; + foreach ($finder as $key => $value) { + $data[] = ['key' => $key, 'relativePathname' => $value->getRelativePathname()]; + } + + return $data; + }; + + $dir = $this->vfsScheme.'://'; + + // no append + $finder = Finder::create()->sortByName()->in($dir.'a/b'); + $this->assertSame( + [ + ['key' => $dir.'a/b/b1', 'relativePathname' => 'b1'], + ['key' => $dir.'a/b/b2', 'relativePathname' => 'b2'], + ['key' => $dir.'a/b/c', 'relativePathname' => 'c'], + ['key' => $dir.'a/b/c/c1', 'relativePathname' => 'c/c1'], + ['key' => $dir.'a/b/c/c2', 'relativePathname' => 'c/c2'], + ], + $formatForAssert($finder), + ); + + // appending another Finder with parent directory + $finder = Finder::create()->sortByName()->in($dir.'a/b'); + $finder->append(Finder::create()->sortByName()->in($dir.'a')); + $this->assertSame( + [ + ['key' => $dir.'a/a1', 'relativePathname' => 'a1'], + ['key' => $dir.'a/a2', 'relativePathname' => 'a2'], + ['key' => $dir.'a/b', 'relativePathname' => 'b'], + ['key' => $dir.'a/b/b1', 'relativePathname' => 'b/b1'], + ['key' => $dir.'a/b/b2', 'relativePathname' => 'b/b2'], + ['key' => $dir.'a/b/c', 'relativePathname' => 'b/c'], + ['key' => $dir.'a/b/c/c1', 'relativePathname' => 'b/c/c1'], + ['key' => $dir.'a/b/c/c2', 'relativePathname' => 'b/c/c2'], + ], + $formatForAssert($finder), + ); + + // appending another Finder with child directory + $finder = Finder::create()->sortByName()->in($dir.'a/b'); + $finder->append(Finder::create()->sortByName()->in($dir.'a/b/c')); + $this->assertSame( + [ + ['key' => $dir.'a/b/b1', 'relativePathname' => 'b1'], + ['key' => $dir.'a/b/b2', 'relativePathname' => 'b2'], + ['key' => $dir.'a/b/c', 'relativePathname' => 'c'], + ['key' => $dir.'a/b/c/c1', 'relativePathname' => 'c1'], + ['key' => $dir.'a/b/c/c2', 'relativePathname' => 'c2'], + ], + $formatForAssert($finder), + ); + + // appending file paths + $finder = Finder::create()->sortByName()->in($dir.'a/b'); + $finder->append([$dir.'a/a1', $dir.'a/b/c/c1']); + $this->assertSame( + [ + ['key' => $dir.'a/a1', 'relativePathname' => $dir.'a/a1'], + ['key' => $dir.'a/b/b1', 'relativePathname' => 'b1'], + ['key' => $dir.'a/b/b2', 'relativePathname' => 'b2'], + ['key' => $dir.'a/b/c', 'relativePathname' => 'c'], + ['key' => $dir.'a/b/c/c1', 'relativePathname' => $dir.'a/b/c/c1'], + ['key' => $dir.'a/b/c/c2', 'relativePathname' => 'c/c2'], + ], + $formatForAssert($finder), + ); + + // appending with to empty Finder + $finder = Finder::create()->sortByName(); + $finder->append([$dir.'a/a1']); + $this->assertSame( + [ + ['key' => $dir.'a/a1', 'relativePathname' => $dir.'a/a1'], + ], + $formatForAssert($finder), + ); + } + public function testAppendReturnsAFinder() { $this->assertInstanceOf(Finder::class, Finder::create()->append([])); diff --git a/Tests/Iterator/RealIteratorTestCase.php b/Tests/Iterator/RealIteratorTestCase.php index b7cde583..dc9414a7 100644 --- a/Tests/Iterator/RealIteratorTestCase.php +++ b/Tests/Iterator/RealIteratorTestCase.php @@ -72,8 +72,9 @@ public static function setUpBeforeClass(): void file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800)); file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000)); - touch(self::toAbsolute('foo/bar.tmp'), strtotime('-19 years')); - touch(self::toAbsolute('test.php'), strtotime('-19 years')); + $oneYearAgo = strtotime('-1 year'); + touch(self::toAbsolute('foo/bar.tmp'), $oneYearAgo); + touch(self::toAbsolute('test.php'), $oneYearAgo); if (FinderTest::class === static::class) { $fs = new Filesystem();