Skip to content

Commit 84f3d2e

Browse files
committed
Add caching iterator
1 parent 41857f9 commit 84f3d2e

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

src/CachingIterator.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace MongoDB;
4+
5+
class CachingIterator implements \Iterator, \Countable
6+
{
7+
/**
8+
* @var \Traversable
9+
*/
10+
private $iterator;
11+
12+
/**
13+
* @var array
14+
*/
15+
private $items = [];
16+
17+
/**
18+
* @var bool
19+
*/
20+
private $iteratorExhausted = false;
21+
22+
/**
23+
* @param \Traversable $iterator
24+
*/
25+
public function __construct(\Traversable $iterator)
26+
{
27+
$this->iterator = $this->wrapTraversable($iterator);
28+
$this->storeCurrentItem();
29+
}
30+
31+
/**
32+
* @return int
33+
*/
34+
public function count()
35+
{
36+
$this->exhaustIterator();
37+
return count($this->items);
38+
}
39+
40+
/**
41+
* @return mixed
42+
*/
43+
public function current()
44+
{
45+
return current($this->items);
46+
}
47+
48+
/**
49+
* @return mixed
50+
*/
51+
public function key()
52+
{
53+
return key($this->items);
54+
}
55+
56+
/**
57+
* @return void
58+
*/
59+
public function next()
60+
{
61+
if (! $this->iteratorExhausted) {
62+
$this->iterator->next();
63+
$this->storeCurrentItem();
64+
}
65+
66+
next($this->items);
67+
}
68+
69+
/**
70+
* @return void
71+
*/
72+
public function rewind()
73+
{
74+
$this->exhaustIterator();
75+
reset($this->items);
76+
}
77+
78+
/**
79+
* @return bool
80+
*/
81+
public function valid()
82+
{
83+
return $this->key() !== null;
84+
}
85+
86+
/**
87+
* Ensures the original iterator is fully consumed and all items cached
88+
*/
89+
private function exhaustIterator()
90+
{
91+
while (!$this->iteratorExhausted) {
92+
$this->next();
93+
}
94+
}
95+
96+
/**
97+
* Stores the current item
98+
*/
99+
private function storeCurrentItem()
100+
{
101+
if (null === $key = $this->iterator->key()) {
102+
return;
103+
}
104+
105+
$this->items[$key] = $this->iterator->current();
106+
}
107+
108+
/**
109+
* @param \Traversable $traversable
110+
* @return \Generator
111+
*/
112+
private function wrapTraversable(\Traversable $traversable)
113+
{
114+
foreach ($traversable as $key => $value) {
115+
yield $key => $value;
116+
}
117+
$this->iteratorExhausted = true;
118+
}
119+
}

tests/CachingIteratorTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace MongoDB\Tests;
4+
5+
use MongoDB\CachingIterator;
6+
7+
class CachingIteratorTest extends \PHPUnit_Framework_TestCase
8+
{
9+
/**
10+
* Sanity check for all following tests
11+
* @expectedException \Exception
12+
* @expectedExceptionMessage Cannot traverse an already closed generator
13+
*/
14+
public function testTraverseGeneratorConsumesIt()
15+
{
16+
$iterator = $this->getTraversable([1, 2, 3]);
17+
$this->assertSame([1, 2, 3], iterator_to_array($iterator));
18+
$this->assertSame([1, 2, 3], iterator_to_array($iterator));
19+
}
20+
21+
public function testIterateOverItems()
22+
{
23+
$iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
24+
25+
$expectedKey = 0;
26+
$expectedItem = 1;
27+
foreach ($iterator as $key => $item) {
28+
$this->assertSame($expectedKey++, $key);
29+
$this->assertSame($expectedItem++, $item);
30+
}
31+
$this->assertFalse($iterator->valid());
32+
}
33+
34+
public function testIteratePartiallyThenRewind()
35+
{
36+
$iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
37+
38+
$this->assertSame(1, $iterator->current());
39+
$iterator->next();
40+
41+
$this->assertSame([1, 2, 3], iterator_to_array($iterator));
42+
}
43+
44+
public function testCount()
45+
{
46+
$iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
47+
$this->assertCount(3, $iterator);
48+
}
49+
50+
public function testCountAfterPartiallyIterating()
51+
{
52+
$iterator = new CachingIterator($this->getTraversable([1, 2, 3]));
53+
$iterator->next();
54+
$this->assertCount(3, $iterator);
55+
}
56+
57+
private function getTraversable($items)
58+
{
59+
foreach ($items as $item) {
60+
yield $item;
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)