Skip to content

Commit 3c6b5f2

Browse files
authored
PHPLIB-598: Clean up cursor usages to account for iterator changes (#801)
* Bump required ext-mongodb version * Remove unnecessary cursor wrapping for iterators * Update tailable cursor documentation to account for cursor changes * Apply review feedback to docs * Update evergreen config to no longer test against 1.8 driver
1 parent 1521b40 commit 3c6b5f2

File tree

12 files changed

+61
-78
lines changed

12 files changed

+61
-78
lines changed

.evergreen/config.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,17 +435,13 @@ axes:
435435
display_name: Driver Version
436436
values:
437437
- id: "lowest-supported"
438-
display_name: "1.8.1"
438+
display_name: "1.9.0"
439439
variables:
440-
DRIVER_VERSION: "1.8.1"
440+
DRIVER_VERSION: "1.9.0"
441441
- id: "latest-stable"
442442
display_name: "1.9-stable"
443443
variables:
444444
DRIVER_VERSION: "stable"
445-
- id: "1.8-dev"
446-
display_name: "1.8-dev"
447-
variables:
448-
DRIVER_BRANCH: "v1.8"
449445
- id: "1.9-dev"
450446
display_name: "1.9-dev"
451447
variables:

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"php": "^7.0 || ^8.0",
1313
"ext-hash": "*",
1414
"ext-json": "*",
15-
"ext-mongodb": "^1.8.1",
15+
"ext-mongodb": "^1.9.0",
1616
"jean85/pretty-package-versions": "^1.2",
1717
"symfony/polyfill-php80": "^1.19"
1818
},

docs/tutorial/tailable-cursor.txt

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Overview
1818
When the driver executes a query or command (e.g.
1919
:manual:`aggregate </reference/command/aggregate>`), results from the operation
2020
are returned via a :php:`MongoDB\\Driver\\Cursor <class.mongodb-driver-cursor>`
21-
object. The Cursor class implements PHP's :php:`Traversable <traversable>`
21+
object. The Cursor class implements PHP's :php:`Iterator <iterator>`
2222
interface, which allows it to be iterated with ``foreach`` and interface with
2323
any PHP functions that work with :php:`iterables <types.iterable>`. Similar to
2424
result objects in other database drivers, cursors in MongoDB only support
@@ -35,20 +35,21 @@ While normal cursors can be iterated once with ``foreach``, that approach will
3535
not work with tailable cursors. When ``foreach`` is used with a tailable cursor,
3636
the loop will stop upon reaching the end of the initial result set. Attempting
3737
to continue iteration on the cursor with a second ``foreach`` would throw an
38-
exception, since PHP attempts to rewind the cursor.
38+
exception, since PHP attempts to rewind the cursor. Therefore, reading from a
39+
tailable cursor will require direct usage of the :php:`Iterator <iterator>` API.
3940

40-
In order to continuously read from a tailable cursor, we will need to wrap the
41-
Cursor object with an :php:`IteratorIterator <iteratoriterator>`. This will
42-
allow us to directly control the cursor's iteration (e.g. call ``next()``),
43-
avoid inadvertently rewinding the cursor, and decide when to wait for new
44-
results or stop iteration entirely.
41+
.. note::
42+
43+
Before version 1.9.0 of the ``ext-mongodb`` extension, the cursor class does
44+
not implement the :php:`Iterator <iterator>` interface. To manually iterate
45+
a cursor using the method below, it must first be wrapped with an
46+
:php:`IteratorIterator <iteratoriterator>`.
4547

46-
Wrapping a Normal Cursor
47-
------------------------
48+
Manually Iterating a Normal Cursor
49+
----------------------------------
4850

49-
Before looking at how a tailable cursor can be wrapped with
50-
:php:`IteratorIterator <iteratoriterator>`, we'll start by examining how the
51-
class interacts with a normal cursor.
51+
Before looking at how a tailable cursor can be iterated, we'll start by
52+
examining how the ``Iterator`` methods interact with a normal cursor.
5253

5354
The following example finds five restaurants and uses ``foreach`` to view the
5455
results:
@@ -74,7 +75,7 @@ not occurred, the iterator then advances to the next position, control jumps
7475
back to the validity check, and the loop continues.
7576

7677
With the inner workings of ``foreach`` under our belt, we can now translate the
77-
preceding example to use IteratorIterator:
78+
preceding example to use the Iterator methods directly:
7879

7980
.. code-block:: php
8081

@@ -84,28 +85,26 @@ preceding example to use IteratorIterator:
8485

8586
$cursor = $collection->find([], ['limit' => 5]);
8687

87-
$iterator = new IteratorIterator($cursor);
88+
$cursor->rewind();
8889

89-
$iterator->rewind();
90-
91-
while ($iterator->valid()) {
92-
$document = $iterator->current();
90+
while ($cursor->valid()) {
91+
$document = $cursor->current();
9392
var_dump($document);
94-
$iterator->next();
93+
$cursor->next();
9594
}
9695

9796
.. note::
9897

99-
Calling ``$iterator->next()`` after the ``while`` loop naturally ends would
98+
Calling ``$cursor->next()`` after the ``while`` loop naturally ends would
10099
throw an exception, since all results on the cursor have been exhausted.
101100

102101
The purpose of this example is simply to demonstrate the functional equivalence
103102
between ``foreach`` and manual iteration with PHP's :php:`Iterator <iterator>`
104-
API. For normal cursors, there is little reason to use IteratorIterator instead
105-
of a concise ``foreach`` loop.
103+
API. For normal cursors, there is little reason to manually iterate results
104+
instead of a concise ``foreach`` loop.
106105

107-
Wrapping a Tailable Cursor
108-
--------------------------
106+
Iterating a Tailable Cursor
107+
---------------------------
109108

110109
In order to demonstrate a tailable cursor in action, we'll need two scripts: a
111110
"producer" and a "consumer". The producer script will create a new capped
@@ -154,7 +153,7 @@ If you execute this consumer script, you'll notice that it quickly exhausts all
154153
results in the capped collection and then terminates. We cannot add a second
155154
``foreach``, as that would throw an exception when attempting to rewind the
156155
cursor. This is a ripe use case for directly controlling the iteration process
157-
using :php:`IteratorIterator <iteratoriterator>`.
156+
using the :php:`Iterator <iterator>` interface.
158157

159158
.. code-block:: php
160159

@@ -167,17 +166,15 @@ using :php:`IteratorIterator <iteratoriterator>`.
167166
'maxAwaitTimeMS' => 100,
168167
]);
169168

170-
$iterator = new IteratorIterator($cursor);
171-
172-
$iterator->rewind();
169+
$cursor->rewind();
173170

174171
while (true) {
175-
if ($iterator->valid()) {
176-
$document = $iterator->current();
172+
if ($cursor->valid()) {
173+
$document = $cursor->current();
177174
printf("Consumed document created at: %s\n", $document->createdAt);
178175
}
179176

180-
$iterator->next();
177+
$cursor->next();
181178
}
182179

183180
Much like the ``foreach`` example, this version on the consumer script will

src/GridFS/ReadableStream.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
namespace MongoDB\GridFS;
1919

20-
use IteratorIterator;
20+
use MongoDB\Driver\CursorInterface;
2121
use MongoDB\Exception\InvalidArgumentException;
2222
use MongoDB\GridFS\Exception\CorruptFileException;
2323
use stdClass;
@@ -48,7 +48,7 @@ class ReadableStream
4848
/** @var integer */
4949
private $chunkOffset = 0;
5050

51-
/** @var IteratorIterator|null */
51+
/** @var CursorInterface|null */
5252
private $chunksIterator;
5353

5454
/** @var CollectionWrapper */
@@ -315,9 +315,7 @@ private function initBufferFromNextChunk()
315315
*/
316316
private function initChunksIterator()
317317
{
318-
$cursor = $this->collectionWrapper->findChunksByFileId($this->file->_id, $this->chunkOffset);
319-
320-
$this->chunksIterator = new IteratorIterator($cursor);
318+
$this->chunksIterator = $this->collectionWrapper->findChunksByFileId($this->file->_id, $this->chunkOffset);
321319
$this->chunksIterator->rewind();
322320
}
323321
}

src/Model/CachingIterator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class CachingIterator implements Countable, Iterator
4141
/** @var array */
4242
private $items = [];
4343

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

4747
/** @var boolean */
@@ -53,14 +53,14 @@ class CachingIterator implements Countable, Iterator
5353
/**
5454
* Initialize the iterator and stores the first item in the cache. This
5555
* effectively rewinds the Traversable and the wrapping IteratorIterator.
56-
* Additionally, this mimics behavior of the SPL iterators and allows users
57-
* to omit an explicit call * to rewind() before using the other methods.
56+
* Additionally, this mimics behavior of the SPL iterators and allows users
57+
* to omit an explicit call to rewind() before using the other methods.
5858
*
5959
* @param Traversable $traversable
6060
*/
6161
public function __construct(Traversable $traversable)
6262
{
63-
$this->iterator = new IteratorIterator($traversable);
63+
$this->iterator = $traversable instanceof Iterator ? $traversable : new IteratorIterator($traversable);
6464

6565
$this->iterator->rewind();
6666
$this->storeCurrentItem();

src/Model/CallbackIterator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ class CallbackIterator implements Iterator
3232
/** @var Closure */
3333
private $callback;
3434

35-
/** @var IteratorIterator */
35+
/** @var Iterator */
3636
private $iterator;
3737

3838
public function __construct(Traversable $traversable, Closure $callback)
3939
{
40-
$this->iterator = new IteratorIterator($traversable);
40+
$this->iterator = $traversable instanceof Iterator ? $traversable : new IteratorIterator($traversable);
4141
$this->callback = $callback;
4242
}
4343

tests/Collection/CrudSpecFunctionalTest.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace MongoDB\Tests\Collection;
44

5-
use IteratorIterator;
65
use LogicException;
76
use MongoDB\BulkWriteResult;
87
use MongoDB\Collection;
@@ -105,8 +104,8 @@ public function provideSpecificationTests()
105104
private function assertEquivalentCollections($expectedCollection, $actualCollection)
106105
{
107106
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
108-
$mi->attachIterator(new IteratorIterator($expectedCollection->find()));
109-
$mi->attachIterator(new IteratorIterator($actualCollection->find()));
107+
$mi->attachIterator($expectedCollection->find());
108+
$mi->attachIterator($actualCollection->find());
110109

111110
foreach ($mi as $documents) {
112111
list($expectedDocument, $actualDocument) = $documents;

tests/GridFS/SpecFunctionalTest.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace MongoDB\Tests\GridFS;
44

55
use DateTime;
6-
use IteratorIterator;
76
use LogicException;
87
use MongoDB\BSON\Binary;
98
use MongoDB\BSON\ObjectId;
@@ -121,8 +120,8 @@ public function provideSpecificationTests()
121120
private function assertEquivalentCollections($expectedCollection, $actualCollection, $actualResult)
122121
{
123122
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
124-
$mi->attachIterator(new IteratorIterator($expectedCollection->find()));
125-
$mi->attachIterator(new IteratorIterator($actualCollection->find()));
123+
$mi->attachIterator($expectedCollection->find());
124+
$mi->attachIterator($actualCollection->find());
126125

127126
foreach ($mi as $documents) {
128127
list($expectedDocument, $actualDocument) = $documents;

tests/Operation/FindFunctionalTest.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace MongoDB\Tests\Operation;
44

5-
use IteratorIterator;
65
use MongoDB\Driver\BulkWrite;
76
use MongoDB\Driver\ReadPreference;
87
use MongoDB\Operation\CreateCollection;
@@ -183,40 +182,39 @@ public function testMaxAwaitTimeMS()
183182

184183
$operation = new Find($databaseName, $cappedCollectionName, [], ['cursorType' => Find::TAILABLE_AWAIT, 'maxAwaitTimeMS' => $maxAwaitTimeMS]);
185184
$cursor = $operation->execute($this->getPrimaryServer());
186-
$it = new IteratorIterator($cursor);
187185

188186
/* The initial query includes the one and only document in its result
189187
* batch, so we should not expect a delay. */
190188
$startTime = microtime(true);
191-
$it->rewind();
189+
$cursor->rewind();
192190
$duration = microtime(true) - $startTime;
193191
$this->assertLessThan($pivot, $duration);
194192

195-
$this->assertTrue($it->valid());
196-
$this->assertSameDocument(['_id' => 1], $it->current());
193+
$this->assertTrue($cursor->valid());
194+
$this->assertSameDocument(['_id' => 1], $cursor->current());
197195

198196
/* Advancing again takes us to the last document of the result batch,
199197
* but still should not issue a getMore */
200198
$startTime = microtime(true);
201-
$it->next();
199+
$cursor->next();
202200
$duration = microtime(true) - $startTime;
203201
$this->assertLessThan($pivot, $duration);
204202

205-
$this->assertTrue($it->valid());
206-
$this->assertSameDocument(['_id' => 2], $it->current());
203+
$this->assertTrue($cursor->valid());
204+
$this->assertSameDocument(['_id' => 2], $cursor->current());
207205

208206
/* Now that we've reached the end of the initial result batch, advancing
209207
* again will issue a getMore. Expect to wait at least maxAwaitTimeMS,
210208
* since no new documents should be inserted to wake up the server's
211209
* query thread. Also ensure we don't wait too long (server default is
212210
* one second). */
213211
$startTime = microtime(true);
214-
$it->next();
212+
$cursor->next();
215213
$duration = microtime(true) - $startTime;
216214
$this->assertGreaterThan($pivot, $duration);
217215
$this->assertLessThan(0.5, $duration);
218216

219-
$this->assertFalse($it->valid());
217+
$this->assertFalse($cursor->valid());
220218
}
221219

222220
public function testReadPreferenceWithinTransaction()

tests/SpecTests/FunctionalTestCase.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace MongoDB\Tests\SpecTests;
44

55
use ArrayIterator;
6-
use IteratorIterator;
76
use LogicException;
87
use MongoDB\Collection;
98
use MongoDB\Driver\Server;
@@ -106,7 +105,7 @@ protected function assertOutcomeCollectionData(array $expectedDocuments, $result
106105

107106
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
108107
$mi->attachIterator(new ArrayIterator($expectedDocuments));
109-
$mi->attachIterator(new IteratorIterator($outcomeCollection->find([], ['sort' => ['_id' => 1]])));
108+
$mi->attachIterator($outcomeCollection->find([], ['sort' => ['_id' => 1]]));
110109

111110
foreach ($mi as $documents) {
112111
list($expectedDocument, $actualDocument) = $documents;

tests/SpecTests/PrimaryStepDownSpecTest.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace MongoDB\Tests\SpecTests;
44

5-
use IteratorIterator;
65
use MongoDB\Client;
76
use MongoDB\Collection;
87
use MongoDB\Driver\Command;
@@ -205,12 +204,11 @@ public function testGetMoreIteration()
205204
// Start a find operation on the collection with a batch size of 2, and retrieve the first batch of results.
206205
$cursor = $this->collection->find([], ['batchSize' => 2]);
207206

208-
$iterator = new IteratorIterator($cursor);
209-
$iterator->rewind();
210-
$this->assertTrue($iterator->valid());
207+
$cursor->rewind();
208+
$this->assertTrue($cursor->valid());
211209

212-
$iterator->next();
213-
$this->assertTrue($iterator->valid());
210+
$cursor->next();
211+
$this->assertTrue($cursor->valid());
214212

215213
$totalConnectionsCreated = $this->getTotalConnectionsCreated();
216214

@@ -235,14 +233,14 @@ public function testGetMoreIteration()
235233
$events = [];
236234
$observer = new CommandObserver();
237235
$observer->observe(
238-
function () use ($iterator) {
239-
$iterator->next();
236+
function () use ($cursor) {
237+
$cursor->next();
240238
},
241239
function ($event) use (&$events) {
242240
$events[] = $event;
243241
}
244242
);
245-
$this->assertTrue($iterator->valid());
243+
$this->assertTrue($cursor->valid());
246244
$this->assertCount(1, $events);
247245
$this->assertSame('getMore', $events[0]['started']->getCommandName());
248246

tests/UnifiedSpecTests/CollectionData.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace MongoDB\Tests\UnifiedSpecTests;
44

55
use ArrayIterator;
6-
use IteratorIterator;
76
use MongoDB\Client;
87
use MongoDB\Driver\ReadConcern;
98
use MongoDB\Driver\ReadPreference;
@@ -75,7 +74,7 @@ public function assertOutcome(Client $client)
7574

7675
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
7776
$mi->attachIterator(new ArrayIterator($this->documents));
78-
$mi->attachIterator(new IteratorIterator($cursor));
77+
$mi->attachIterator($cursor);
7978

8079
foreach ($mi as $i => $documents) {
8180
list($expectedDocument, $actualDocument) = $documents;

0 commit comments

Comments
 (0)