Skip to content

Commit dd04c0e

Browse files
committed
PHPLIB-501: Use IteratorIterator to prevent memory leak with generators
1 parent 32cfb9b commit dd04c0e

File tree

1 file changed

+31
-20
lines changed

1 file changed

+31
-20
lines changed

src/Model/CachingIterator.php

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
namespace MongoDB\Model;
1919

2020
use Countable;
21-
use Generator;
2221
use Iterator;
22+
use IteratorIterator;
2323
use Traversable;
2424
use function count;
2525
use function current;
@@ -41,7 +41,7 @@ class CachingIterator implements Countable, Iterator
4141
/** @var array */
4242
private $items = [];
4343

44-
/** @var Generator */
44+
/** @var IteratorIterator */
4545
private $iterator;
4646

4747
/** @var boolean */
@@ -50,6 +50,9 @@ class CachingIterator implements Countable, Iterator
5050
/** @var boolean */
5151
private $iteratorExhausted = false;
5252

53+
/** @var boolean */
54+
private $iteratorPrepared = false;
55+
5356
/**
5457
* Initialize the iterator and stores the first item in the cache. This
5558
* effectively rewinds the Traversable and the wrapping Generator, which
@@ -61,8 +64,7 @@ class CachingIterator implements Countable, Iterator
6164
*/
6265
public function __construct(Traversable $traversable)
6366
{
64-
$this->iterator = $this->wrapTraversable($traversable);
65-
$this->storeCurrentItem();
67+
$this->iterator = new IteratorIterator($traversable);
6668
}
6769

6870
/**
@@ -82,6 +84,8 @@ public function count()
8284
*/
8385
public function current()
8486
{
87+
$this->prepareIterator();
88+
8589
return current($this->items);
8690
}
8791

@@ -91,6 +95,8 @@ public function current()
9195
*/
9296
public function key()
9397
{
98+
$this->prepareIterator();
99+
94100
return key($this->items);
95101
}
96102

@@ -100,9 +106,15 @@ public function key()
100106
*/
101107
public function next()
102108
{
109+
$this->prepareIterator();
110+
103111
if (! $this->iteratorExhausted) {
112+
$this->iteratorAdvanced = true;
104113
$this->iterator->next();
114+
105115
$this->storeCurrentItem();
116+
117+
$this->iteratorExhausted = ! $this->iterator->valid();
106118
}
107119

108120
next($this->items);
@@ -114,6 +126,8 @@ public function next()
114126
*/
115127
public function rewind()
116128
{
129+
$this->prepareIterator();
130+
117131
/* If the iterator has advanced, exhaust it now so that future iteration
118132
* can rely on the cache.
119133
*/
@@ -138,11 +152,24 @@ public function valid()
138152
*/
139153
private function exhaustIterator()
140154
{
155+
$this->prepareIterator();
156+
141157
while (! $this->iteratorExhausted) {
142158
$this->next();
143159
}
144160
}
145161

162+
private function prepareIterator()
163+
{
164+
if ($this->iteratorPrepared) {
165+
return;
166+
}
167+
168+
$this->iterator->rewind();
169+
$this->iteratorPrepared = true;
170+
$this->storeCurrentItem();
171+
}
172+
146173
/**
147174
* Stores the current item in the cache.
148175
*/
@@ -156,20 +183,4 @@ private function storeCurrentItem()
156183

157184
$this->items[$key] = $this->iterator->current();
158185
}
159-
160-
/**
161-
* Wraps the Traversable with a Generator.
162-
*
163-
* @param Traversable $traversable
164-
* @return Generator
165-
*/
166-
private function wrapTraversable(Traversable $traversable)
167-
{
168-
foreach ($traversable as $key => $value) {
169-
yield $key => $value;
170-
$this->iteratorAdvanced = true;
171-
}
172-
173-
$this->iteratorExhausted = true;
174-
}
175186
}

0 commit comments

Comments
 (0)