Completed
Pull Request — master (#168)
by Andreas
02:51
created

MongoCursor::getNext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 31 and the first side effect is on line 17.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
if (class_exists('MongoCursor', false)) {
17
    return;
18
}
19
20
use Alcaeus\MongoDbAdapter\AbstractCursor;
21
use Alcaeus\MongoDbAdapter\TypeConverter;
22
use Alcaeus\MongoDbAdapter\ExceptionConverter;
23
use MongoDB\Driver\Cursor;
24
use MongoDB\Driver\ReadPreference;
25
use MongoDB\Operation\Find;
26
27
/**
28
 * Result object for database query.
29
 * @link http://www.php.net/manual/en/class.mongocursor.php
30
 */
31
class MongoCursor extends AbstractCursor implements Iterator
32
{
33
    /**
34
     * @var bool
35
     */
36
    public static $slaveOkay = false;
37
38
    /**
39
     * @var int
40
     */
41
    public static $timeout = 30000;
42
43
    /**
44
     * @var array
45
     */
46
    protected $optionNames = [
47
        'allowPartialResults',
48
        'batchSize',
49
        'cursorType',
50
        'limit',
51
        'maxTimeMS',
52
        'modifiers',
53
        'noCursorTimeout',
54
        'projection',
55
        'readPreference',
56
        'skip',
57
        'sort',
58
    ];
59
60
    /**
61
     * @var array
62
     */
63
    protected $projection;
64
65
    /**
66
     * @var array
67
     */
68
    protected $query;
69
70
    protected $allowPartialResults;
71
    protected $awaitData;
72
    protected $flags = 0;
73
    protected $hint;
74
    protected $limit;
75
    protected $maxTimeMS;
76
    protected $noCursorTimeout;
77
    protected $options = [];
78
    protected $skip;
79
    protected $snapshot;
80
    protected $sort;
81
    protected $tailable;
82
83
    /**
84
     * Create a new cursor
85
     * @link http://www.php.net/manual/en/mongocursor.construct.php
86
     * @param MongoClient $connection Database connection.
87
     * @param string $ns Full name of database and collection.
88
     * @param array $query Database query.
89
     * @param array $fields Fields to return.
90
     */
91
    public function __construct(MongoClient $connection, $ns, array $query = array(), array $fields = array())
92
    {
93
        parent::__construct($connection, $ns);
94
95
        $this->query = $query;
96
        $this->projection = $fields;
97
    }
98
99
    /**
100
     * Adds a top-level key/value pair to a query
101
     * @link http://www.php.net/manual/en/mongocursor.addoption.php
102
     * @param string $key Fieldname to add.
103
     * @param mixed $value Value to add.
104
     * @throws MongoCursorException
105
     * @return MongoCursor Returns this cursor
106
     */
107
    public function addOption($key, $value)
108
    {
109
        $this->errorIfOpened();
110
        $this->options[$key] = $value;
111
112
        return $this;
113
    }
114
115
    /**
116
     * (PECL mongo &gt;= 1.2.11)<br/>
117
     * Sets whether this cursor will wait for a while for a tailable cursor to return more data
118
     * @param bool $wait [optional] <p>If the cursor should wait for more data to become available.</p>
119
     * @return MongoCursor Returns this cursor.
120
     */
121
    public function awaitData($wait = true)
122
    {
123
        $this->errorIfOpened();
124
        $this->awaitData = $wait;
125
126
        return $this;
127
    }
128
129
130
    /**
131
     * Counts the number of results for this query
132
     * @link http://www.php.net/manual/en/mongocursor.count.php
133
     * @param bool $foundOnly Send cursor limit and skip information to the count function, if applicable.
134
     * @return int The number of documents returned by this cursor's query.
135
     */
136
    public function count($foundOnly = false)
137
    {
138
        $optionNames = ['hint', 'maxTimeMS'];
139
        if ($foundOnly) {
140
            $optionNames = array_merge($optionNames, ['limit', 'skip']);
141
        }
142
143
        $options = $this->getOptions($optionNames) + $this->options;
144
        try {
145
            $count = $this->collection->count(TypeConverter::fromLegacy($this->query), $options);
146
        } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) {
147
            throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e);
148
        } catch (\MongoDB\Driver\Exception\Exception $e) {
149
            throw ExceptionConverter::toLegacy($e);
150
        }
151
152
        return $count;
153
    }
154
155
    /**
156
     * Execute the query
157
     * @link http://www.php.net/manual/en/mongocursor.doquery.php
158
     * @throws MongoConnectionException if it cannot reach the database.
159
     * @return void
160
     */
161
    protected function doQuery()
162
    {
163
        $options = $this->getOptions() + $this->options;
164
165
        try {
166
            $this->cursor = $this->collection->find(TypeConverter::fromLegacy($this->query), $options);
167
        } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) {
168
            throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e);
169
        } catch (\MongoDB\Driver\Exception\Exception $e) {
170
            throw ExceptionConverter::toLegacy($e);
171
        }
172
    }
173
174
    /**
175
     * Return an explanation of the query, often useful for optimization and debugging
176
     * @link http://www.php.net/manual/en/mongocursor.explain.php
177
     * @return array Returns an explanation of the query.
178
     */
179
    public function explain()
180
    {
181
        $optionNames = [
182
            'allowPartialResults',
183
            'batchSize',
184
            'cursorType',
185
            'limit',
186
            'maxTimeMS',
187
            'noCursorTimeout',
188
            'projection',
189
            'skip',
190
            'sort',
191
        ];
192
193
        $options = $this->getOptions($optionNames);
194
195
        $command = [
196
            'explain' => [
197
                'find' => $this->collection->getCollectionName(),
198
                'filter' => $this->query,
199
            ] + $options,
200
        ];
201
202
        $explained = TypeConverter::toLegacy(iterator_to_array($this->db->command($command))[0]);
203
        unset($explained['ok']);
204
205
        return $explained;
206
    }
207
208
    /**
209
     * Sets the fields for a query
210
     * @link http://www.php.net/manual/en/mongocursor.fields.php
211
     * @param array $f Fields to return (or not return).
212
     * @throws MongoCursorException
213
     * @return MongoCursor
214
     */
215
    public function fields(array $f)
216
    {
217
        $this->errorIfOpened();
218
        $this->projection = $f;
219
220
        return $this;
221
    }
222
223
    /**
224
     * Advances the cursor to the next result, and returns that result
225
     * @link http://www.php.net/manual/en/mongocursor.getnext.php
226
     * @throws MongoConnectionException
227
     * @throws MongoCursorTimeoutException
228
     * @return array Returns the next object
229
     */
230
    public function getNext()
231
    {
232
        return $this->next();
233
    }
234
235
    /**
236
     * Checks if there are any more elements in this cursor
237
     * @link http://www.php.net/manual/en/mongocursor.hasnext.php
238
     * @throws MongoConnectionException
239
     * @throws MongoCursorTimeoutException
240
     * @return bool Returns true if there is another element
241
     */
242
    public function hasNext()
243
    {
244 View Code Duplication
        if (! $this->startedIterating) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
            $this->ensureIterator();
246
            $this->startedIterating = true;
247
            $this->storeIteratorState();
248
            $this->cursorNeedsAdvancing = false;
249
        } elseif ($this->cursorNeedsAdvancing) {
250
            $this->ensureIterator()->next();
251
            $this->cursorNeedsAdvancing = false;
252
        }
253
254
        return $this->ensureIterator()->valid();
255
    }
256
257
    /**
258
     * Gives the database a hint about the query
259
     * @link http://www.php.net/manual/en/mongocursor.hint.php
260
     * @param array|string $keyPattern Indexes to use for the query.
261
     * @throws MongoCursorException
262
     * @return MongoCursor Returns this cursor
263
     */
264
    public function hint($keyPattern)
265
    {
266
        $this->errorIfOpened();
267
        $this->hint = $keyPattern;
268
269
        return $this;
270
    }
271
272
    /**
273
     * Sets whether this cursor will timeout
274
     * @link http://www.php.net/manual/en/mongocursor.immortal.php
275
     * @param bool $liveForever If the cursor should be immortal.
276
     * @throws MongoCursorException
277
     * @return MongoCursor Returns this cursor
278
     */
279
    public function immortal($liveForever = true)
280
    {
281
        $this->errorIfOpened();
282
        $this->noCursorTimeout = $liveForever;
283
284
        return $this;
285
    }
286
287
    /**
288
     * Limits the number of results returned
289
     * @link http://www.php.net/manual/en/mongocursor.limit.php
290
     * @param int $num The number of results to return.
291
     * @throws MongoCursorException
292
     * @return MongoCursor Returns this cursor
293
     */
294
    public function limit($num)
295
    {
296
        $this->errorIfOpened();
297
        $this->limit = $num;
298
299
        return $this;
300
    }
301
302
    /**
303
     * @param int $ms
304
     * @return $this
305
     * @throws MongoCursorException
306
     */
307
    public function maxTimeMS($ms)
308
    {
309
        $this->errorIfOpened();
310
        $this->maxTimeMS = $ms;
311
312
        return $this;
313
    }
314
315
    /**
316
     * @link http://www.php.net/manual/en/mongocursor.partial.php
317
     * @param bool $okay [optional] <p>If receiving partial results is okay.</p>
318
     * @return MongoCursor Returns this cursor.
319
     */
320
    public function partial($okay = true)
321
    {
322
        $this->allowPartialResults = $okay;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Clears the cursor
329
     * @link http://www.php.net/manual/en/mongocursor.reset.php
330
     * @return void
331
     */
332
    public function reset()
333
    {
334
        parent::reset();
335
    }
336
337
    /**
338
     * @link http://www.php.net/manual/en/mongocursor.setflag.php
339
     * @param int $flag
340
     * @param bool $set
341
     * @return MongoCursor
342
     */
343
    public function setFlag($flag, $set = true)
0 ignored issues
show
Unused Code introduced by
The parameter $flag 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...
Unused Code introduced by
The parameter $set 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...
344
    {
345
        $this->notImplemented();
346
    }
347
348
    /**
349
     * Skips a number of results
350
     * @link http://www.php.net/manual/en/mongocursor.skip.php
351
     * @param int $num The number of results to skip.
352
     * @throws MongoCursorException
353
     * @return MongoCursor Returns this cursor
354
     */
355
    public function skip($num)
356
    {
357
        $this->errorIfOpened();
358
        $this->skip = $num;
359
360
        return $this;
361
    }
362
363
    /**
364
     * Sets whether this query can be done on a slave
365
     * This method will override the static class variable slaveOkay.
366
     * @link http://www.php.net/manual/en/mongocursor.slaveOkay.php
367
     * @param boolean $okay If it is okay to query the slave.
368
     * @throws MongoCursorException
369
     * @return MongoCursor Returns this cursor
370
     */
371
    public function slaveOkay($okay = true)
372
    {
373
        $this->errorIfOpened();
374
375
        $this->setReadPreferenceFromSlaveOkay($okay);
376
377
        return $this;
378
    }
379
380
    /**
381
     * Use snapshot mode for the query
382
     * @link http://www.php.net/manual/en/mongocursor.snapshot.php
383
     * @throws MongoCursorException
384
     * @return MongoCursor Returns this cursor
385
     */
386
    public function snapshot()
387
    {
388
        $this->errorIfOpened();
389
        $this->snapshot = true;
390
391
        return $this;
392
    }
393
394
    /**
395
     * Sorts the results by given fields
396
     * @link http://www.php.net/manual/en/mongocursor.sort.php
397
     * @param array $fields An array of fields by which to sort. Each element in the array has as key the field name, and as value either 1 for ascending sort, or -1 for descending sort
398
     * @throws MongoCursorException
399
     * @return MongoCursor Returns the same cursor that this method was called on
400
     */
401
    public function sort(array $fields)
402
    {
403
        $this->errorIfOpened();
404
        $this->sort = $fields;
405
406
        return $this;
407
    }
408
409
    /**
410
     * Sets whether this cursor will be left open after fetching the last results
411
     * @link http://www.php.net/manual/en/mongocursor.tailable.php
412
     * @param bool $tail If the cursor should be tailable.
413
     * @return MongoCursor Returns this cursor
414
     */
415
    public function tailable($tail = true)
416
    {
417
        $this->errorIfOpened();
418
        $this->tailable = $tail;
419
420
        return $this;
421
    }
422
423
    /**
424
     * @return int|null
425
     */
426
    protected function convertCursorType()
427
    {
428
        if (! $this->tailable) {
429
            return null;
430
        }
431
432
        return $this->awaitData ? Find::TAILABLE_AWAIT : Find::TAILABLE;
433
    }
434
435
    /**
436
     * @return array
437
     */
438
    protected function convertModifiers()
439
    {
440
        $modifiers = array_key_exists('modifiers', $this->options) ? $this->options['modifiers'] : [];
441
442
        foreach (['hint', 'snapshot'] as $modifier) {
443
            if ($this->$modifier === null) {
444
                continue;
445
            }
446
447
            $modifiers['$' . $modifier] = $this->$modifier;
448
        }
449
450
        return $modifiers;
451
    }
452
453
    /**
454
     * @return array
455
     */
456
    protected function convertProjection()
457
    {
458
        return TypeConverter::convertProjection($this->projection);
459
    }
460
461
    /**
462
     * @return Cursor
463
     */
464
    protected function ensureCursor()
465
    {
466
        if ($this->cursor === null) {
467
            $this->doQuery();
468
        }
469
470
        return $this->cursor;
471
    }
472
473
    /**
474
     * @param \Traversable $traversable
475
     * @return \Generator
476
     */
477
    protected function wrapTraversable(\Traversable $traversable)
478
    {
479
        foreach ($traversable as $key => $value) {
480
            if (isset($value->_id) && ($value->_id instanceof \MongoDB\BSON\ObjectID || !is_object($value->_id))) {
481
                $key = (string) $value->_id;
482
            }
483
            yield $key => $value;
484
        }
485
    }
486
487
    /**
488
     * @return array
489
     */
490
    protected function getCursorInfo()
491
    {
492
        return [
493
            'ns' => $this->ns,
494
            'limit' => $this->limit,
495
            'batchSize' => $this->batchSize,
496
            'skip' => $this->skip,
497
            'flags' => $this->flags,
498
            'query' => $this->query,
499
            'fields' => $this->projection,
500
        ];
501
    }
502
503
    /**
504
     * @return array
505
     */
506
    public function __sleep()
507
    {
508
        return [
509
            'allowPartialResults',
510
            'awaitData',
511
            'flags',
512
            'hint',
513
            'limit',
514
            'maxTimeMS',
515
            'noCursorTimeout',
516
            'optionNames',
517
            'options',
518
            'projection',
519
            'query',
520
            'skip',
521
            'snapshot',
522
            'sort',
523
            'tailable',
524
        ] + parent::__sleep();
525
    }
526
}
527