Skip to content

Commit 509e703

Browse files
committed
feat(repository): integrate lazy releases page loading
1 parent 3f43168 commit 509e703

File tree

5 files changed

+194
-13
lines changed

5 files changed

+194
-13
lines changed

src/Module/Common/Internal/ObjectContainer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function __construct()
3737
$this->injector = (new Injector($this))->withCacheReflections(false);
3838
$this->cache[Injector::class] = $this->injector;
3939
$this->cache[self::class] = $this;
40+
$this->cache[Container::class] = $this;
4041
$this->cache[ContainerInterface::class] = $this;
4142
}
4243

src/Module/Downloader/Downloader.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,13 @@ private function processRepository(Repository $repository, DownloadContext $cont
136136
->satisfies($context->actionConfig->version);
137137

138138
/** @var ReleaseInterface[] $releases */
139-
$releases = $releasesCollection->sortByVersion()->toArray();
139+
$releases = $releasesCollection->limit(10)->sortByVersion()->toArray();
140140

141141
$this->logger->debug('%d releases found.', \count($releases));
142142

143143
process_release:
144+
// Try without limit
145+
$releases === [] and $releases = $releasesCollection->limit(0)->toArray();
144146
$releases === [] and throw new \RuntimeException('No relevant release found.');
145147
$context->release = \array_shift($releases);
146148

src/Module/Repository/Collection/ReleasesCollection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function withAssets(): self
7474
*/
7575
public function sortByVersion(): self
7676
{
77-
$result = $this->items;
77+
$result = \iterator_to_array($this->getIterator());
7878

7979
$sort = function (ReleaseInterface $a, ReleaseInterface $b): int {
8080
return \version_compare($this->comparisonVersionString($b), $this->comparisonVersionString($a));

src/Module/Repository/Internal/Collection.php

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ abstract class Collection implements \IteratorAggregate, \Countable
3939
*/
4040
protected array $filters = [];
4141

42+
/**
43+
* Maximum number of items to return from the collection
44+
* Zero means no limit
45+
*/
46+
protected int $limitCount = 0;
47+
4248
/**
4349
* @param array<T>|CachedGenerator<T> $items Collection items
4450
*/
@@ -157,6 +163,21 @@ public function firstOr(callable $otherwise, ?callable $filter = null): object
157163
return $this->first($filter) ?? $otherwise();
158164
}
159165

166+
/**
167+
* Limits the collection to a specified number of items.
168+
* Using 0 as a count will reset any previous limit and return all items.
169+
*
170+
* @param int<0, max> $count Maximum number of items to include
171+
* @return $this New collection with limited items
172+
* @throws \InvalidArgumentException If count is negative
173+
*/
174+
public function limit(int $count): static
175+
{
176+
$clone = clone $this;
177+
$clone->limitCount = $count;
178+
return $clone;
179+
}
180+
160181
/**
161182
* Returns an iterator for traversing the collection.
162183
*
@@ -165,17 +186,15 @@ public function firstOr(callable $otherwise, ?callable $filter = null): object
165186
public function getIterator(): \Traversable
166187
{
167188
$combinedFilter = $this->getCombinedFilter();
189+
$itemCount = 0;
168190

169-
// If no filters, return the items directly
170-
if ($combinedFilter === null) {
171-
yield from $this->items;
172-
return;
173-
}
174-
175-
// Apply filters during iteration
191+
// Apply filters and limit during iteration
176192
foreach ($this->items as $item) {
177-
if ($combinedFilter($item)) {
193+
if ($combinedFilter === null || $combinedFilter($item)) {
178194
yield $item;
195+
if ($this->limitCount > 0 && ++$itemCount >= $this->limitCount) {
196+
return;
197+
}
179198
}
180199
}
181200
}
@@ -187,13 +206,13 @@ public function getIterator(): \Traversable
187206
*/
188207
public function count(): int
189208
{
190-
if ($this->filters === []) {
209+
if ($this->filters === [] && $this->limitCount === 0) {
191210
return $this->items instanceof CachedGenerator
192211
? $this->items->count()
193212
: \count($this->items);
194213
}
195214

196-
// For iterables or with filters, we need to count matching items
215+
// For iterables or with filters or limits, we need to count matching items
197216
$count = 0;
198217
foreach ($this as $item) {
199218
$count++;
@@ -227,7 +246,7 @@ public function empty(): bool
227246
: $this->items === [];
228247
}
229248

230-
// For iterables or with filters, try to get the first item
249+
// For iterables or with filters or limits, try to get the first item
231250
return $this->first() === null;
232251
}
233252

tests/Unit/Module/Repository/Internal/CollectionTest.php

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,35 @@
77
use Internal\DLoad\Module\Repository\Internal\Collection;
88
use Internal\DLoad\Module\Repository\Internal\Paginator;
99
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\Attributes\DataProvider;
1011
use PHPUnit\Framework\TestCase;
1112

1213
#[CoversClass(Collection::class)]
1314
final class CollectionTest extends TestCase
1415
{
16+
public static function provideLimitCases(): \Generator
17+
{
18+
yield 'empty collection' => [
19+
[], 5, [],
20+
];
21+
22+
yield 'limit less than collection size' => [
23+
[1, 2, 3, 4, 5], 3, [1, 2, 3],
24+
];
25+
26+
yield 'limit equal to collection size' => [
27+
[1, 2, 3], 3, [1, 2, 3],
28+
];
29+
30+
yield 'limit greater than collection size' => [
31+
[1, 2, 3], 5, [1, 2, 3],
32+
];
33+
34+
yield 'zero limit returns all items' => [
35+
[1, 2, 3, 4, 5], 0, [1, 2, 3, 4, 5],
36+
];
37+
}
38+
1539
public function testCollectionWithIterable(): void
1640
{
1741
// Create a test collection class
@@ -145,4 +169,139 @@ public function testFirst(): void
145169
$this->assertTrue($pagesLoaded[1]);
146170
$this->assertFalse($pagesLoaded[2]);
147171
}
172+
173+
#[DataProvider('provideLimitCases')]
174+
public function testLimit(array $sourceItems, int $limit, array $expectedItems): void
175+
{
176+
// Arrange
177+
$testCollection = new class([]) extends Collection {};
178+
$collection = $testCollection::create($sourceItems);
179+
180+
// Act
181+
$limited = $collection->limit($limit);
182+
183+
// Assert
184+
self::assertEquals($expectedItems, $limited->toArray());
185+
186+
// Check count matches expected
187+
self::assertCount(\count($expectedItems), $limited);
188+
}
189+
190+
public function testLimitWithFilter(): void
191+
{
192+
// Arrange
193+
$testCollection = new class([]) extends Collection {};
194+
$collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
195+
196+
// Act
197+
$filtered = $collection
198+
->filter(static fn($item) => $item > 3)
199+
->limit(2);
200+
201+
// Assert
202+
self::assertEquals([4, 5], $filtered->toArray());
203+
self::assertCount(2, $filtered);
204+
}
205+
206+
public function testLimitWithPaginator(): void
207+
{
208+
// Arrange
209+
$testCollection = new class([]) extends Collection {};
210+
211+
$pageLoader = static function (): \Generator {
212+
yield [1, 2, 3];
213+
yield [4, 5, 6];
214+
yield [7, 8, 9];
215+
};
216+
217+
$paginator = Paginator::createFromGenerator($pageLoader(), null);
218+
$collection = $testCollection::create($paginator);
219+
220+
// Act
221+
$limited = $collection->limit(4);
222+
223+
// Assert
224+
self::assertEquals([1, 2, 3, 4], $limited->toArray());
225+
self::assertCount(4, $limited);
226+
}
227+
228+
public function testLimitWithNegativeCount(): void
229+
{
230+
// Arrange
231+
$testCollection = new class([]) extends Collection {};
232+
$collection = $testCollection::create([1, 2, 3]);
233+
234+
// Assert
235+
$this->expectException(\InvalidArgumentException::class);
236+
$this->expectExceptionMessage('Limit count must be non-negative');
237+
238+
// Act
239+
$collection->limit(-1);
240+
}
241+
242+
public function testLimitWithZeroCountResetsLimit(): void
243+
{
244+
// Arrange
245+
$testCollection = new class([]) extends Collection {};
246+
$collection = $testCollection::create([1, 2, 3, 4, 5]);
247+
$limitedCollection = $collection->limit(2);
248+
249+
// Verify the limit was applied
250+
self::assertEquals([1, 2], $limitedCollection->toArray());
251+
252+
// Act - apply zero limit to reset the limit
253+
$resetCollection = $limitedCollection->limit(0);
254+
255+
// Assert - should have all items
256+
self::assertEquals([1, 2, 3, 4, 5], $resetCollection->toArray());
257+
}
258+
259+
public function testLimitResetWithFilteredCollection(): void
260+
{
261+
// Arrange
262+
$testCollection = new class([]) extends Collection {};
263+
$collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
264+
265+
// Apply filter and limit
266+
$filtered = $collection
267+
->filter(static fn($item) => $item > 3)
268+
->limit(2);
269+
270+
// Verify initial state
271+
self::assertEquals([4, 5], $filtered->toArray());
272+
273+
// Act - reset limit
274+
$resetLimited = $filtered->limit(0);
275+
276+
// Assert - filter should still be applied, but not the limit
277+
self::assertEquals([4, 5, 6, 7, 8, 9, 10], $resetLimited->toArray());
278+
}
279+
280+
public function testCountWithLimit(): void
281+
{
282+
// Arrange
283+
$testCollection = new class([]) extends Collection {};
284+
$collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
285+
286+
// Act
287+
$limited = $collection->limit(3);
288+
289+
// Assert
290+
self::assertCount(3, $limited);
291+
}
292+
293+
public function testEmptyWithLimit(): void
294+
{
295+
// Arrange
296+
$testCollection = new class([]) extends Collection {};
297+
$collection = $testCollection::create([1, 2, 3]);
298+
299+
// Act & Assert
300+
self::assertFalse($collection->limit(1)->empty());
301+
self::assertFalse($collection->limit(0)->empty());
302+
303+
// With an empty source collection
304+
$emptyCollection = $testCollection::create([]);
305+
self::assertTrue($emptyCollection->limit(5)->empty());
306+
}
148307
}

0 commit comments

Comments
 (0)