Skip to content

Commit ccf7e7a

Browse files
Add IterableEntityIdPager
This can be used to page through an iterable of entity IDs without having to load it into memory all at once, as an alternative to: new InMemoryEntityIdPager( ...$iterable )
1 parent fceb49c commit ccf7e7a

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

RELEASE-NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Wikibase DataModel Services release notes
22

3+
## Version 5.3.0 (dev)
4+
* Added `IterableEntityIdPager`
5+
36
## Version 5.2.0 (2020-03-10)
47
* Allow installing with wikimedia/assert 0.5.0
58

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Wikibase\DataModel\Services\EntityId;
4+
5+
use ArrayIterator;
6+
use Iterator;
7+
use IteratorIterator;
8+
use Wikibase\DataModel\Entity\EntityId;
9+
10+
/**
11+
* An entity ID pager that wraps an iterable and traverses it once.
12+
* It is not seekable or rewindable.
13+
*
14+
* @since 5.3
15+
*/
16+
class IterableEntityIdPager implements EntityIdPager {
17+
18+
/** @var Iterator */
19+
private $iterator;
20+
21+
/**
22+
* @param iterable<EntityId> $iterable
23+
*/
24+
public function __construct( iterable $iterable ) {
25+
if ( $iterable instanceof Iterator ) {
26+
$this->iterator = $iterable;
27+
} elseif ( is_array( $iterable ) ) {
28+
$this->iterator = new ArrayIterator( $iterable );
29+
} else {
30+
$this->iterator = new IteratorIterator( $iterable );
31+
}
32+
$this->iterator->rewind();
33+
}
34+
35+
/**
36+
* @see EntityIdPager::fetchIds
37+
*
38+
* @param int $limit
39+
*
40+
* @return EntityId[]
41+
*/
42+
public function fetchIds( $limit ) {
43+
$ids = [];
44+
while ( $limit-- > 0 && $this->iterator->valid() ) {
45+
$ids[] = $this->iterator->current();
46+
$this->iterator->next();
47+
}
48+
return $ids;
49+
}
50+
51+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace Wikibase\DataModel\Services\Tests\EntityId;
4+
5+
use ArrayIterator;
6+
use IteratorAggregate;
7+
use PHPUnit\Framework\TestCase;
8+
use Wikibase\DataModel\Services\EntityId\IterableEntityIdPager;
9+
10+
/**
11+
* Note: this test has the pager iterate through numbers, not entity IDs.
12+
* It simplifies the test, and the pager doesn’t care.
13+
*
14+
* @covers \Wikibase\DataModel\Services\EntityId\IterableEntityIdPager
15+
*
16+
* @license GPL-2.0-or-later
17+
*/
18+
class IterableEntityIdPagerTest extends TestCase {
19+
20+
private const ONE_THROUGH_TEN = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
21+
22+
private function yieldOneThroughTenIndividually() {
23+
foreach ( self::ONE_THROUGH_TEN as $number ) {
24+
yield $number;
25+
}
26+
}
27+
28+
private function yieldOneThroughTenAsYieldFrom() {
29+
yield from self::ONE_THROUGH_TEN;
30+
}
31+
32+
/** Various iterables which all yield the numbers one through ten (both inclusive). */
33+
public function provideIterables() {
34+
yield 'array' => [ self::ONE_THROUGH_TEN ];
35+
yield 'ArrayIterator' => [ new ArrayIterator( self::ONE_THROUGH_TEN ) ];
36+
yield 'generator I' => [ $this->yieldOneThroughTenIndividually() ];
37+
yield 'generator II' => [ $this->yieldOneThroughTenAsYieldFrom() ];
38+
$aggregate = $this->createMock( IteratorAggregate::class );
39+
$aggregate->method( 'getIterator' )->willReturn( new ArrayIterator( self::ONE_THROUGH_TEN ) );
40+
yield 'IteratorAggregate' => [ $aggregate ];
41+
}
42+
43+
public function providePagers() {
44+
foreach ( $this->provideIterables() as $key => $iterable ) {
45+
yield $key => [ new IterableEntityIdPager( $iterable[0] ) ];
46+
}
47+
}
48+
49+
/** @dataProvider providePagers */
50+
public function testOneBatchLimit10( IterableEntityIdPager $pager ) {
51+
$this->assertSame( self::ONE_THROUGH_TEN, $pager->fetchIds( 10 ) );
52+
$this->assertSame( [], $pager->fetchIds( 1 ) );
53+
$this->assertSame( [], $pager->fetchIds( 10 ) );
54+
}
55+
56+
/** @dataProvider providePagers */
57+
public function testOneBatchLimit100( IterableEntityIdPager $pager ) {
58+
$this->assertSame( self::ONE_THROUGH_TEN, $pager->fetchIds( 100 ) );
59+
$this->assertSame( [], $pager->fetchIds( 1 ) );
60+
$this->assertSame( [], $pager->fetchIds( 10 ) );
61+
}
62+
63+
/** @dataProvider providePagers */
64+
public function testTwoBatchesLimits5And5( IterableEntityIdPager $pager ) {
65+
$this->assertSame( [ 1, 2, 3, 4, 5 ], $pager->fetchIds( 5 ) );
66+
$this->assertSame( [ 6, 7, 8, 9, 10 ], $pager->fetchIds( 5 ) );
67+
$this->assertSame( [], $pager->fetchIds( 5 ) );
68+
$this->assertSame( [], $pager->fetchIds( 1 ) );
69+
$this->assertSame( [], $pager->fetchIds( 0 ) );
70+
}
71+
72+
/** @dataProvider providePagers */
73+
public function testThreeBatchesLimits0And5And5( IterableEntityIdPager $pager ) {
74+
$this->assertSame( [], $pager->fetchIds( 0 ) );
75+
$this->assertSame( [ 1, 2, 3, 4, 5 ], $pager->fetchIds( 5 ) );
76+
$this->assertSame( [ 6, 7, 8, 9, 10 ], $pager->fetchIds( 5 ) );
77+
$this->assertSame( [], $pager->fetchIds( 5 ) );
78+
$this->assertSame( [], $pager->fetchIds( 1 ) );
79+
$this->assertSame( [], $pager->fetchIds( 0 ) );
80+
}
81+
82+
/** @dataProvider providePagers */
83+
public function testFourBatchesLimits1And2And3And4( IterableEntityIdPager $pager ) {
84+
$this->assertSame( [ 1 ], $pager->fetchIds( 1 ) );
85+
$this->assertSame( [ 2, 3 ], $pager->fetchIds( 2 ) );
86+
$this->assertSame( [ 4, 5, 6 ], $pager->fetchIds( 3 ) );
87+
$this->assertSame( [ 7, 8, 9, 10 ], $pager->fetchIds( 4 ) );
88+
$this->assertSame( [], $pager->fetchIds( 5 ) );
89+
}
90+
91+
/** @dataProvider providePagers */
92+
public function testTenBatchesEachLimit1( IterableEntityIdPager $pager ) {
93+
foreach ( self::ONE_THROUGH_TEN as $i ) {
94+
$this->assertSame( [ $i ], $pager->fetchIds( 1 ) );
95+
}
96+
$this->assertSame( [], $pager->fetchIds( 1 ) );
97+
}
98+
99+
}

0 commit comments

Comments
 (0)