Skip to content

Commit 4c6f7ce

Browse files
committed
PHPLIB-411: postBatchResumeToken, getResumeToken(), and resume improvements
Added ChangeStream::getResumeToken() method (PHPLIB-435), which returns the cached resume token. postBatchResumeToken and startAfter (introduced in PHPLIB-407) can now be used for resuming. Replaced TailableCursorIterator with ChangeStreamIterator. In addition to avoiding getMore commands when calling rewind(), the new class also tracks the size of each cursor batch so it can capture the postBatchResumeToken from getMore commands. Refactored the resume process such that Watch now exclusively constructs the inner iterator for ChangeStream. UnexpectedValueException is now thrown if firstBatch and nextBatch fields are not found in aggregate and getMore responses, respectively.
1 parent bfcda32 commit 4c6f7ce

File tree

7 files changed

+655
-274
lines changed

7 files changed

+655
-274
lines changed

src/ChangeStream.php

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717

1818
namespace MongoDB;
1919

20-
use MongoDB\BSON\Serializable;
21-
use MongoDB\Driver\Cursor;
20+
use MongoDB\Driver\CursorId;
2221
use MongoDB\Driver\Exception\ConnectionException;
2322
use MongoDB\Driver\Exception\RuntimeException;
2423
use MongoDB\Driver\Exception\ServerException;
25-
use MongoDB\Exception\InvalidArgumentException;
2624
use MongoDB\Exception\ResumeTokenException;
27-
use MongoDB\Model\TailableCursorIterator;
25+
use MongoDB\Model\ChangeStreamIterator;
2826
use Iterator;
2927

3028
/**
@@ -46,9 +44,8 @@ class ChangeStream implements Iterator
4644
private static $errorCodeInterrupted = 11601;
4745
private static $errorCodeCursorKilled = 237;
4846

49-
private $resumeToken;
5047
private $resumeCallable;
51-
private $csIt;
48+
private $iterator;
5249
private $key = 0;
5350

5451
/**
@@ -61,14 +58,13 @@ class ChangeStream implements Iterator
6158
* Constructor.
6259
*
6360
* @internal
64-
* @param Cursor $cursor
65-
* @param callable $resumeCallable
66-
* @param boolean $isFirstBatchEmpty
61+
* @param ChangeStreamIterator $iterator
62+
* @param callable $resumeCallable
6763
*/
68-
public function __construct(Cursor $cursor, callable $resumeCallable, $isFirstBatchEmpty)
64+
public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable)
6965
{
66+
$this->iterator = $iterator;
7067
$this->resumeCallable = $resumeCallable;
71-
$this->csIt = new TailableCursorIterator($cursor, $isFirstBatchEmpty);
7268
}
7369

7470
/**
@@ -77,15 +73,29 @@ public function __construct(Cursor $cursor, callable $resumeCallable, $isFirstBa
7773
*/
7874
public function current()
7975
{
80-
return $this->csIt->current();
76+
return $this->iterator->current();
8177
}
8278

8379
/**
84-
* @return \MongoDB\Driver\CursorId
80+
* @return CursorId
8581
*/
8682
public function getCursorId()
8783
{
88-
return $this->csIt->getInnerIterator()->getId();
84+
return $this->iterator->getInnerIterator()->getId();
85+
}
86+
87+
/**
88+
* Returns the resume token for the iterator's current position.
89+
*
90+
* Null may be returned if no change documents have been iterated and the
91+
* server did not include a postBatchResumeToken in its aggregate or getMore
92+
* command response.
93+
*
94+
* @return array|object|null
95+
*/
96+
public function getResumeToken()
97+
{
98+
return $this->iterator->getResumeToken();
8999
}
90100

91101
/**
@@ -108,7 +118,7 @@ public function key()
108118
public function next()
109119
{
110120
try {
111-
$this->csIt->next();
121+
$this->iterator->next();
112122
$this->onIteration($this->hasAdvanced);
113123
} catch (RuntimeException $e) {
114124
$this->resumeOrThrow($e);
@@ -123,7 +133,7 @@ public function next()
123133
public function rewind()
124134
{
125135
try {
126-
$this->csIt->rewind();
136+
$this->iterator->rewind();
127137
/* Unlike next() and resume(), the decision to increment the key
128138
* does not depend on whether the change stream has advanced. This
129139
* ensures that multiple calls to rewind() do not alter state. */
@@ -139,40 +149,7 @@ public function rewind()
139149
*/
140150
public function valid()
141151
{
142-
return $this->csIt->valid();
143-
}
144-
145-
/**
146-
* Extracts the resume token (i.e. "_id" field) from the change document.
147-
*
148-
* @param array|object $document Change document
149-
* @return mixed
150-
* @throws InvalidArgumentException
151-
* @throws ResumeTokenException if the resume token is not found or invalid
152-
*/
153-
private function extractResumeToken($document)
154-
{
155-
if ( ! is_array($document) && ! is_object($document)) {
156-
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
157-
}
158-
159-
if ($document instanceof Serializable) {
160-
return $this->extractResumeToken($document->bsonSerialize());
161-
}
162-
163-
$resumeToken = is_array($document)
164-
? (isset($document['_id']) ? $document['_id'] : null)
165-
: (isset($document->_id) ? $document->_id : null);
166-
167-
if ( ! isset($resumeToken)) {
168-
throw ResumeTokenException::notFound();
169-
}
170-
171-
if ( ! is_array($resumeToken) && ! is_object($resumeToken)) {
172-
throw ResumeTokenException::invalidType($resumeToken);
173-
}
174-
175-
return $resumeToken;
152+
return $this->iterator->valid();
176153
}
177154

178155
/**
@@ -222,13 +199,11 @@ private function onIteration($incrementKey)
222199
}
223200

224201
/* Return early if there is not a current result. Avoid any attempt to
225-
* increment the iterator's key or extract a resume token */
202+
* increment the iterator's key. */
226203
if (!$this->valid()) {
227204
return;
228205
}
229206

230-
$this->resumeToken = $this->extractResumeToken($this->csIt->current());
231-
232207
if ($incrementKey) {
233208
$this->key++;
234209
}
@@ -237,16 +212,14 @@ private function onIteration($incrementKey)
237212
}
238213

239214
/**
240-
* Creates a new changeStream after a resumable server error.
215+
* Recreates the ChangeStreamIterator after a resumable server error.
241216
*
242217
* @return void
243218
*/
244219
private function resume()
245220
{
246-
list($cursor, $isFirstBatchEmpty) = call_user_func($this->resumeCallable, $this->resumeToken);
247-
248-
$this->csIt = new TailableCursorIterator($cursor, $isFirstBatchEmpty);
249-
$this->csIt->rewind();
221+
$this->iterator = call_user_func($this->resumeCallable, $this->getResumeToken());
222+
$this->iterator->rewind();
250223

251224
$this->onIteration($this->hasAdvanced);
252225
}

0 commit comments

Comments
 (0)