Completed
Pull Request — master (#79)
by Andreas
04:00
created

AbstractCursor::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 */
15
16
namespace Alcaeus\MongoDbAdapter;
17
18
use Alcaeus\MongoDbAdapter\Helper\ReadPreference;
19
use MongoDB\Collection;
20
use MongoDB\Driver\Cursor;
21
22
/**
23
 * @internal
24
 */
25
abstract class AbstractCursor
26
{
27
    use ReadPreference;
28
29
    /**
30
     * @var int
31
     */
32
    protected $batchSize = 0;
33
34
    /**
35
     * @var Collection
36
     */
37
    protected $collection;
38
39
    /**
40
     * @var \MongoClient
41
     */
42
    protected $connection;
43
44
    /**
45
     * @var Cursor
46
     */
47
    protected $cursor;
48
49
    /**
50
     * @var \MongoDB\Database
51
     */
52
    protected $db;
53
54
    /**
55
     * @var \Iterator
56
     */
57
    protected $iterator;
58
59
    /**
60
     * @var string
61
     */
62
    protected $ns;
63
64
    /**
65
     * @var bool
66
     */
67
    protected $startedIterating = false;
68
69
    /**
70
     * @var bool
71
     */
72
    protected $cursorNeedsAdvancing = true;
73
74
    /**
75
     * @var mixed
76
     */
77
    private $current = null;
78
79
    /**
80
     * @var mixed
81
     */
82
    private $key = null;
83
84
    /**
85
     * @var mixed
86
     */
87
    private $valid = false;
88
89
    /**
90
     * @var int
91
     */
92
    protected $position = 0;
93
94
    /**
95
     * @var array
96
     */
97
    protected $optionNames = [
98
        'batchSize',
99
        'readPreference',
100
    ];
101
102
    /**
103
     * @return Cursor
104
     */
105
    abstract protected function ensureCursor();
106
107
    /**
108
     * @return array
109
     */
110
    abstract protected function getCursorInfo();
111
112
    /**
113
     * Create a new cursor
114
     * @link http://www.php.net/manual/en/mongocursor.construct.php
115
     * @param \MongoClient $connection Database connection.
116
     * @param string $ns Full name of database and collection.
117
     */
118 51
    public function __construct(\MongoClient $connection, $ns)
119
    {
120 51
        $this->connection = $connection;
121 51
        $this->ns = $ns;
122
123 51
        $nsParts = explode('.', $ns);
124 51
        $dbName = array_shift($nsParts);
125 51
        $collectionName = implode('.', $nsParts);
126
127 51
        $this->db = $connection->selectDB($dbName)->getDb();
128
129 51
        if ($collectionName) {
130 40
            $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
131 40
        }
132 51
    }
133
134
    /**
135
     * Returns the current element
136
     * @link http://www.php.net/manual/en/mongocursor.current.php
137
     * @return array
138
     */
139 23
    public function current()
140
    {
141 23
        return $this->current;
142
    }
143
144
    /**
145
     * Returns the current result's _id
146
     * @link http://www.php.net/manual/en/mongocursor.key.php
147
     * @return string The current result's _id as a string.
148
     */
149 14
    public function key()
150
    {
151 14
        return $this->key;
152
    }
153
154
    /**
155
     * Advances the cursor to the next result, and returns that result
156
     * @link http://www.php.net/manual/en/mongocursor.next.php
157
     * @throws \MongoConnectionException
158
     * @throws \MongoCursorTimeoutException
159
     * @return array Returns the next object
160
     */
161 24
    public function next()
162
    {
163 24
        if (! $this->startedIterating) {
164 2
            $this->ensureIterator();
165 2
            $this->startedIterating = true;
166 2
        } else {
167 24
            if ($this->cursorNeedsAdvancing) {
168 23
                $this->ensureIterator()->next();
169 23
            }
170
171 24
            $this->cursorNeedsAdvancing = true;
172 24
            $this->position++;
173
        }
174
175 24
        return $this->storeIteratorState();
176
    }
177
178
    /**
179
     * Returns the cursor to the beginning of the result set
180
     * @throws \MongoConnectionException
181
     * @throws \MongoCursorTimeoutException
182
     * @return void
183
     */
184 42
    public function rewind()
185
    {
186
        // We can recreate the cursor to allow it to be rewound
187 42
        $this->reset();
188 42
        $this->startedIterating = true;
189 42
        $this->position = 0;
190 42
        $this->ensureIterator()->rewind();
191 40
        $this->storeIteratorState();
192 40
    }
193
194
    /**
195
     * Checks if the cursor is reading a valid result.
196
     * @link http://www.php.net/manual/en/mongocursor.valid.php
197
     * @return boolean If the current result is not null.
198
     */
199 40
    public function valid()
200
    {
201 40
        return $this->valid;
202
    }
203
204
    /**
205
     * Limits the number of elements returned in one batch.
206
     *
207
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
208
     * @param int $batchSize The number of results to return per batch
209
     * @return $this Returns this cursor.
210
     */
211 1
    public function batchSize($batchSize)
212
    {
213 1
        $this->batchSize = $batchSize;
214
215 1
        return $this;
216
    }
217
218
    /**
219
     * Checks if there are documents that have not been sent yet from the database for this cursor
220
     * @link http://www.php.net/manual/en/mongocursor.dead.php
221
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
222
     */
223
    public function dead()
224
    {
225
        return $this->ensureCursor()->isDead();
226
    }
227
228
    /**
229
     * @return array
230
     */
231 3
    public function info()
232
    {
233 3
        return $this->getCursorInfo() + $this->getIterationInfo();
234
    }
235
236
    /**
237
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
238
     * @param string $readPreference
239
     * @param array $tags
240
     * @return $this Returns this cursor.
241
     */
242 51
    public function setReadPreference($readPreference, $tags = null)
243
    {
244 51
        $this->setReadPreferenceFromParameters($readPreference, $tags);
245
246 51
        return $this;
247
    }
248
249
    /**
250
     * Sets a client-side timeout for this query
251
     * @link http://www.php.net/manual/en/mongocursor.timeout.php
252
     * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
253
     * @return $this Returns this cursor
254
     */
255
    public function timeout($ms)
0 ignored issues
show
Unused Code introduced by
The parameter $ms is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
256
    {
257
        trigger_error('The ' . __METHOD__ . ' method is not implemented in mongo-php-adapter', E_USER_WARNING);
258
        return $this;
259
    }
260
261
    /**
262
     * Applies all options set on the cursor, overwriting any options that have already been set
263
     *
264
     * @param array $optionNames Array of option names to be applied (will be read from properties)
265
     * @return array
266
     */
267 45
    protected function getOptions($optionNames = null)
268
    {
269 45
        $options = [];
270
271 45
        if ($optionNames === null) {
272 43
            $optionNames = $this->optionNames;
273 43
        }
274
275 45
        foreach ($optionNames as $option) {
276 45
            $converter = 'convert' . ucfirst($option);
277 45
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
278
279 45
            if ($value === null) {
280 32
                continue;
281
            }
282
283 44
            $options[$option] = $value;
284 45
        }
285
286 45
        return $options;
287
    }
288
289
    /**
290
     * @return \Iterator
291
     */
292 43
    protected function ensureIterator()
293
    {
294 43
        if ($this->iterator === null) {
295
            // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
296
            // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
297 43
            $this->iterator = $this->wrapTraversable($this->ensureCursor());
298 41
        }
299
300 41
        return $this->iterator;
301
    }
302
303
    /**
304
     * @param \Traversable $traversable
305
     * @return \Generator
306
     */
307 11
    protected function wrapTraversable(\Traversable $traversable)
308
    {
309 11
        foreach ($traversable as $key => $value) {
310 11
            yield $key => $value;
311 11
        }
312 11
    }
313
314
    /**
315
     * @throws \MongoCursorException
316
     */
317 21
    protected function errorIfOpened()
318
    {
319 21
        if ($this->cursor === null) {
320 21
            return;
321
        }
322
323
        throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
324
    }
325
326
    /**
327
     * @return array
328
     */
329 3
    protected function getIterationInfo()
330
    {
331
        $iterationInfo = [
332 3
            'started_iterating' => $this->cursor !== null,
333 3
        ];
334
335 3
        if ($this->cursor !== null) {
336 3
            switch ($this->cursor->getServer()->getType()) {
337 3
                case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
338
                    $typeString = 'ARBITER';
339
                    break;
340 3
                case \MongoDB\Driver\Server::TYPE_MONGOS:
341
                    $typeString = 'MONGOS';
342
                    break;
343 3
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
344
                    $typeString = 'PRIMARY';
345
                    break;
346 3
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
347
                    $typeString = 'SECONDARY';
348
                    break;
349 3
                default:
350 3
                    $typeString = 'STANDALONE';
351 3
            }
352
353 3
            $cursorId = (string) $this->cursor->getId();
354
            $iterationInfo += [
355 3
                'id' => (int) $cursorId,
356 3
                'at' => $this->position,
357 3
                'numReturned' => $this->position, // This can't be obtained from the new cursor
358 3
                'server' => sprintf('%s:%d;-;.;%d', $this->cursor->getServer()->getHost(), $this->cursor->getServer()->getPort(), getmypid()),
359 3
                'host' => $this->cursor->getServer()->getHost(),
360 3
                'port' => $this->cursor->getServer()->getPort(),
361 3
                'connection_type_desc' => $typeString,
362
            ];
363 3
        }
364
365 3
        return $iterationInfo;
366
    }
367
368
    /**
369
     * @throws \Exception
370
     */
371
    protected function notImplemented()
372
    {
373
        throw new \Exception('Not implemented');
374
    }
375
376
    /**
377
     * Clears the cursor
378
     *
379
     * This is generic but implemented as protected since it's only exposed in MongoCursor
380
     */
381 43
    protected function reset()
382
    {
383 43
        $this->startedIterating = false;
384 43
        $this->cursor = null;
385 43
        $this->iterator = null;
386 43
        $this->storeIteratorState();
387 43
    }
388
389
    /**
390
     * @return array
391
     */
392 3
    public function __sleep()
393
    {
394 3
        return ['batchSize', 'connection', 'iterator', 'ns', 'optionNames', 'position', 'startedIterating'];
395
    }
396
397
    /**
398
     * Stores the current cursor element.
399
     *
400
     * This is necessary because hasNext() might advance the iterator but we still
401
     * need to be able to return the current object.
402
     */
403 43
    private function storeIteratorState()
404
    {
405 43
        if (! $this->startedIterating) {
406 43
            $this->current = null;
407 43
            $this->key = null;
408 43
            $this->valid = false;
409 43
            return null;
410
        }
411
412 41
        $this->current = $this->ensureIterator()->current();
413 41
        $this->key = $this->ensureIterator()->key();
414 41
        $this->valid = $this->ensureIterator()->valid();
415
416 41
        if ($this->current !== null) {
417 24
            $this->current = TypeConverter::toLegacy($this->current);
418 24
        }
419
420 41
        return $this->current;
421
    }
422
}
423