Completed
Push — master ( bc2142...415cad )
by Andreas
04:35
created

MongoCursor::info()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 44
Code Lines 35

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 44
rs 8.439
cc 6
eloc 35
nc 6
nop 0
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
use Alcaeus\MongoDbAdapter\TypeConverter;
17
use MongoDB\Collection;
18
use MongoDB\Driver\Cursor;
19
use MongoDB\Driver\ReadPreference;
20
use MongoDB\Operation\Find;
21
22
/**
23
 * Result object for database query.
24
 * @link http://www.php.net/manual/en/class.mongocursor.php
25
 */
26
class MongoCursor implements Iterator
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
27
{
28
    /**
29
     * @var bool
30
     */
31
    public static $slaveOkay = false;
32
33
    /**
34
     * @var int
35
     */
36
    static $timeout = 30000;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $timeout.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

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

Loading history...
37
38
    /**
39
     * @var MongoClient
40
     */
41
    private $connection;
42
43
    /**
44
     * @var string
45
     */
46
    private $ns;
47
48
    /**
49
     * @var array
50
     */
51
    private $query;
52
53
    /**
54
     * @var
55
     */
56
    private $filter;
0 ignored issues
show
Unused Code introduced by
The property $filter is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
57
58
    /**
59
     * @var Collection
60
     */
61
    private $collection;
62
63
    /**
64
     * @var Cursor
65
     */
66
    private $cursor;
67
68
    /**
69
     * @var IteratorIterator
70
     */
71
    private $iterator;
72
73
    private $allowPartialResults;
74
    private $awaitData;
75
    private $batchSize;
76
    private $flags;
77
    private $hint;
78
    private $limit;
79
    private $maxTimeMS;
80
    private $noCursorTimeout;
81
    private $oplogReplay;
0 ignored issues
show
Unused Code introduced by
The property $oplogReplay is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
82
    private $options = [];
83
    private $projection;
84
    private $readPreference = [];
85
    private $skip;
86
    private $snapshot;
87
    private $sort;
88
    private $tailable;
89
90
    /**
91
     * Create a new cursor
92
     * @link http://www.php.net/manual/en/mongocursor.construct.php
93
     * @param MongoClient $connection Database connection.
94
     * @param string $ns Full name of database and collection.
95
     * @param array $query Database query.
96
     * @param array $fields Fields to return.
97
     * @return MongoCursor Returns the new cursor
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
98
     */
99
    public function __construct(MongoClient $connection, $ns, array $query = array(), array $fields = array())
100
    {
101
        $this->connection = $connection;
102
        $this->ns = $ns;
103
        $this->query = $query;
104
        $this->projection = $fields;
105
106
        $nsParts = explode('.', $ns);
107
        $db = array_shift($nsParts);
108
109
        $this->collection = $connection->selectCollection($db, implode('.', $nsParts))->getCollection();
110
    }
111
112
    /**
113
     * Adds a top-level key/value pair to a query
114
     * @link http://www.php.net/manual/en/mongocursor.addoption.php
115
     * @param string $key Fieldname to add.
116
     * @param mixed $value Value to add.
117
     * @throws MongoCursorException
118
     * @return MongoCursor Returns this cursor
119
     */
120
    public function addOption($key, $value)
121
    {
122
        $this->errorIfOpened();
123
        $this->options[$key] = $value;
124
125
        return $this;
126
    }
127
128
    /**
129
     * (PECL mongo &gt;= 1.2.11)<br/>
130
     * Sets whether this cursor will wait for a while for a tailable cursor to return more data
131
     * @param bool $wait [optional] <p>If the cursor should wait for more data to become available.</p>
132
     * @return MongoCursor Returns this cursor.
133
     */
134
    public function awaitData($wait = true)
135
    {
136
        $this->errorIfOpened();
137
        $this->awaitData = $wait;
138
139
        return $this;
140
    }
141
142
    /**
143
     * Limits the number of elements returned in one batch.
144
     *
145
     * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
146
     * @param int $batchSize The number of results to return per batch
147
     * @return MongoCursor Returns this cursor.
148
     */
149
    public function batchSize($batchSize)
150
    {
151
        $this->errorIfOpened();
152
        $this->batchSize = $batchSize;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Counts the number of results for this query
159
     * @link http://www.php.net/manual/en/mongocursor.count.php
160
     * @param bool $foundOnly Send cursor limit and skip information to the count function, if applicable.
161
     * @return int The number of documents returned by this cursor's query.
162
     */
163
    public function count($foundOnly = false)
164
    {
165
        if ($foundOnly && $this->cursor !== null) {
166
            return iterator_count($this->ensureIterator());
167
        }
168
169
        $optionNames = ['hint', 'limit', 'maxTimeMS', 'skip'];
170
        $options = $foundOnly ? $this->applyOptions($this->options, $optionNames) : $this->options;
171
172
        return $this->collection->count($this->query, $options);
173
    }
174
175
    /**
176
     * Returns the current element
177
     * @link http://www.php.net/manual/en/mongocursor.current.php
178
     * @return array
179
     */
180
    public function current()
181
    {
182
        $document = $this->ensureIterator()->current();
183
        if ($document !== null) {
184
            $document = TypeConverter::convertObjectToLegacyArray($document);
185
        }
186
187
        return $document;
188
    }
189
190
    /**
191
     * Checks if there are documents that have not been sent yet from the database for this cursor
192
     * @link http://www.php.net/manual/en/mongocursor.dead.php
193
     * @return boolean Returns if there are more results that have not been sent to the client, yet.
194
     */
195
    public function dead()
196
    {
197
        return $this->ensureCursor()->isDead();
198
    }
199
200
    /**
201
     * Execute the query
202
     * @link http://www.php.net/manual/en/mongocursor.doquery.php
203
     * @throws MongoConnectionException if it cannot reach the database.
204
     * @return void
205
     */
206
    protected function doQuery()
207
    {
208
        $options = $this->applyOptions($this->options);
209
210
        $this->cursor = $this->collection->find($this->query, $options);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->collection->find($this->query, $options) of type object<MongoDB\Operation\Cursor> is incompatible with the declared type object<MongoDB\Driver\Cursor> of property $cursor.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
211
    }
212
213
    /**
214
     * Return an explanation of the query, often useful for optimization and debugging
215
     * @link http://www.php.net/manual/en/mongocursor.explain.php
216
     * @return array Returns an explanation of the query.
217
     */
218
    public function explain()
219
    {
220
        $this->notImplemented();
221
    }
222
223
    /**
224
     * Sets the fields for a query
225
     * @link http://www.php.net/manual/en/mongocursor.fields.php
226
     * @param array $f Fields to return (or not return).
227
     * @throws MongoCursorException
228
     * @return MongoCursor
229
     */
230
    public function fields(array $f)
231
    {
232
        $this->errorIfOpened();
233
        $this->projection = $f;
234
235
        return $this;
236
    }
237
238
    /**
239
     * Return the next object to which this cursor points, and advance the cursor
240
     * @link http://www.php.net/manual/en/mongocursor.getnext.php
241
     * @throws MongoConnectionException
242
     * @throws MongoCursorTimeoutException
243
     * @return array Returns the next object
244
     */
245
    public function getNext()
246
    {
247
        $this->next();
248
249
        return $this->current();
250
    }
251
252
    /**
253
     * Get the read preference for this query
254
     * @link http://www.php.net/manual/en/mongocursor.getreadpreference.php
255
     * @return array
256
     */
257
    public function getReadPreference()
258
    {
259
        return $this->readPreference;
260
    }
261
262
    /**
263
     * Checks if there are any more elements in this cursor
264
     * @link http://www.php.net/manual/en/mongocursor.hasnext.php
265
     * @throws MongoConnectionException
266
     * @throws MongoCursorTimeoutException
267
     * @return bool Returns true if there is another element
268
     */
269
    public function hasNext()
270
    {
271
        $this->errorIfOpened();
272
        $this->notImplemented();
273
    }
274
275
    /**
276
     * Gives the database a hint about the query
277
     * @link http://www.php.net/manual/en/mongocursor.hint.php
278
     * @param array|string $keyPattern Indexes to use for the query.
279
     * @throws MongoCursorException
280
     * @return MongoCursor Returns this cursor
281
     */
282
    public function hint($keyPattern)
283
    {
284
        $this->errorIfOpened();
285
        $this->hint = $keyPattern;
286
287
        return $this;
288
    }
289
290
    /**
291
     * Sets whether this cursor will timeout
292
     * @link http://www.php.net/manual/en/mongocursor.immortal.php
293
     * @param bool $liveForever If the cursor should be immortal.
294
     * @throws MongoCursorException
295
     * @return MongoCursor Returns this cursor
296
     */
297
    public function immortal($liveForever = true)
298
    {
299
        $this->errorIfOpened();
300
        $this->noCursorTimeout = $liveForever;
301
302
        return $this;
303
    }
304
305
    /**
306
     * Gets the query, fields, limit, and skip for this cursor
307
     * @link http://www.php.net/manual/en/mongocursor.info.php
308
     * @return array The query, fields, limit, and skip for this cursor as an associative array.
309
     */
310
    public function info()
311
    {
312
        $info = [
313
            'ns' => $this->ns,
314
            'limit' => $this->limit,
315
            'batchSize' => $this->batchSize,
316
            'skip' => $this->skip,
317
            'flags' => $this->flags,
318
            'query' => $this->query,
319
            'fields' => $this->projection,
320
            'started_iterating' => $this->cursor !== null,
321
        ];
322
323
        if ($info['started_iterating']) {
324
            switch ($this->cursor->getServer()->getType()) {
325
                case \MongoDB\Driver\Server::TYPE_ARBITER:
326
                    $typeString = 'ARBITER';
327
                    break;
328
                case \MongoDB\Driver\Server::TYPE_MONGOS:
329
                    $typeString = 'MONGOS';
330
                    break;
331
                case \MongoDB\Driver\Server::TYPE_PRIMARY:
332
                    $typeString = 'PRIMARY';
333
                    break;
334
                case \MongoDB\Driver\Server::TYPE_SECONDARY:
335
                    $typeString = 'SECONDARY';
336
                    break;
337
                default:
338
                    $typeString = 'STANDALONE';
339
            }
340
341
            $info = array_merge($info, [
342
                'id' => (string) $this->cursor->getId(),
343
                'at' => null, // @todo Complete info for cursor that is iterating
344
                'numReturned' => null, // @todo Complete info for cursor that is iterating
345
                'server' => null, // @todo Complete info for cursor that is iterating
346
                'host' => $this->cursor->getServer()->getHost(),
347
                'port' => $this->cursor->getServer()->getPort(),
348
                'connection_type_desc' => $typeString,
349
            ]);
350
        }
351
352
        return $info;
353
    }
354
355
    /**
356
     * Returns the current result's _id
357
     * @link http://www.php.net/manual/en/mongocursor.key.php
358
     * @return string The current result's _id as a string.
359
     */
360
    public function key()
361
    {
362
        return $this->ensureIterator()->key();
363
    }
364
365
    /**
366
     * Limits the number of results returned
367
     * @link http://www.php.net/manual/en/mongocursor.limit.php
368
     * @param int $num The number of results to return.
369
     * @throws MongoCursorException
370
     * @return MongoCursor Returns this cursor
371
     */
372
    public function limit($num)
373
    {
374
        $this->errorIfOpened();
375
        $this->limit = $num;
376
377
        return $this;
378
    }
379
380
    /**
381
     * @param int $ms
382
     * @return $this
383
     * @throws MongoCursorException
384
     */
385
    public function maxTimeMS($ms)
386
    {
387
        $this->errorIfOpened();
388
        $this->maxTimeMS = $ms;
389
390
        return $this;
391
    }
392
393
    /**
394
     * Advances the cursor to the next result
395
     * @link http://www.php.net/manual/en/mongocursor.next.php
396
     * @throws MongoConnectionException
397
     * @throws MongoCursorTimeoutException
398
     * @return void
399
     */
400
    public function next()
401
    {
402
        $this->ensureIterator()->next();
403
    }
404
405
    /**
406
     * @link http://www.php.net/manual/en/mongocursor.partial.php
407
     * @param bool $okay [optional] <p>If receiving partial results is okay.</p>
408
     * @return MongoCursor Returns this cursor.
409
     */
410
    public function partial($okay = true)
411
    {
412
        $this->allowPartialResults = $okay;
413
414
        return $this;
415
    }
416
417
    /**
418
     * Clears the cursor
419
     * @link http://www.php.net/manual/en/mongocursor.reset.php
420
     * @return void
421
     */
422
    public function reset()
423
    {
424
        $this->cursor = null;
425
        $this->iterator = null;
426
    }
427
428
    /**
429
     * Returns the cursor to the beginning of the result set
430
     * @throws MongoConnectionException
431
     * @throws MongoCursorTimeoutException
432
     * @return void
433
     */
434
    public function rewind()
435
    {
436
        // Note: rewinding the cursor means recreating it internally
437
        $this->reset();
438
        $this->ensureIterator()->rewind();
439
    }
440
441
    /**
442
     * @link http://www.php.net/manual/en/mongocursor.setflag.php
443
     * @param int $flag
444
     * @param bool $set
445
     * @return MongoCursor
446
     */
447
    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...
448
    {
449
        $this->notImplemented();
450
    }
451
452
    /**
453
     * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
454
     * @param string $readPreference
455
     * @param array $tags
456
     * @return MongoCursor Returns this cursor.
457
     */
458
    public function setReadPreference($readPreference, array $tags = [])
459
    {
460
        $availableReadPreferences = [
461
            MongoClient::RP_PRIMARY,
462
            MongoClient::RP_PRIMARY_PREFERRED,
463
            MongoClient::RP_SECONDARY,
464
            MongoClient::RP_SECONDARY_PREFERRED,
465
            MongoClient::RP_NEAREST
466
        ];
467
        if (! in_array($readPreference, $availableReadPreferences)) {
468
            trigger_error("The value '$readPreference' is not valid as read preference type", E_WARNING);
469
            return $this;
470
        }
471
472
        if ($readPreference == MongoClient::RP_PRIMARY && count($tags)) {
473
            trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_WARNING);
474
            return $this;
475
        }
476
477
        $this->readPreference = [
478
            'type' => $readPreference,
479
            'tagsets' => $tags
480
        ];
481
482
        return $this;
483
    }
484
485
    /**
486
     * Skips a number of results
487
     * @link http://www.php.net/manual/en/mongocursor.skip.php
488
     * @param int $num The number of results to skip.
489
     * @throws MongoCursorException
490
     * @return MongoCursor Returns this cursor
491
     */
492
    public function skip($num)
493
    {
494
        $this->errorIfOpened();
495
        $this->skip = $num;
496
497
        return $this;
498
    }
499
500
    /**
501
     * Sets whether this query can be done on a slave
502
     * This method will override the static class variable slaveOkay.
503
     * @link http://www.php.net/manual/en/mongocursor.slaveOkay.php
504
     * @param boolean $okay If it is okay to query the slave.
505
     * @throws MongoCursorException
506
     * @return MongoCursor Returns this cursor
507
     */
508
    public function slaveOkay($okay = true)
509
    {
510
        $this->errorIfOpened();
511
        static::$slaveOkay = $okay;
512
    }
513
514
    /**
515
     * Use snapshot mode for the query
516
     * @link http://www.php.net/manual/en/mongocursor.snapshot.php
517
     * @throws MongoCursorException
518
     * @return MongoCursor Returns this cursor
519
     */
520
    public function snapshot()
521
    {
522
        $this->errorIfOpened();
523
        $this->snapshot = true;
524
525
        return $this;
526
    }
527
528
    /**
529
     * Sorts the results by given fields
530
     * @link http://www.php.net/manual/en/mongocursor.sort.php
531
     * @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
532
     * @throws MongoCursorException
533
     * @return MongoCursor Returns the same cursor that this method was called on
534
     */
535
    public function sort(array $fields)
536
    {
537
        $this->errorIfOpened();
538
        $this->sort = $fields;
539
540
        return $this;
541
    }
542
543
    /**
544
     * Sets whether this cursor will be left open after fetching the last results
545
     * @link http://www.php.net/manual/en/mongocursor.tailable.php
546
     * @param bool $tail If the cursor should be tailable.
547
     * @return MongoCursor Returns this cursor
548
     */
549
    public function tailable($tail = true)
550
    {
551
        $this->errorIfOpened();
552
        $this->tailable = $tail;
553
554
        return $this;
555
    }
556
557
    /**
558
     * Sets a client-side timeout for this query
559
     * @link http://www.php.net/manual/en/mongocursor.timeout.php
560
     * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
561
     * @throws MongoCursorTimeoutException
562
     * @return MongoCursor Returns this cursor
563
     */
564
    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...
565
    {
566
        $this->notImplemented();
567
    }
568
569
    /**
570
     * Checks if the cursor is reading a valid result.
571
     * @link http://www.php.net/manual/en/mongocursor.valid.php
572
     * @return boolean If the current result is not null.
573
     */
574
    public function valid()
575
    {
576
        return $this->ensureIterator()->valid();
577
    }
578
579
    /**
580
     * Applies all options set on the cursor, overwriting any options that have already been set
581
     *
582
     * @param array $options Existing options array
583
     * @param array $optionNames Array of option names to be applied (will be read from properties)
584
     * @return array
585
     */
586
    private function applyOptions($options, $optionNames = null)
587
    {
588
        if ($optionNames === null) {
589
            $optionNames = [
590
                'allowPartialResults',
591
                'batchSize',
592
                'cursorType',
593
                'limit',
594
                'maxTimeMS',
595
                'modifiers',
596
                'noCursorTimeout',
597
                'oplogReplay',
598
                'projection',
599
                'readPreference',
600
                'skip',
601
                'sort',
602
            ];
603
        }
604
605
        foreach ($optionNames as $option) {
606
            $converter = 'convert' . ucfirst($option);
607
            $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
608
609
            if ($value === null) {
610
                continue;
611
            }
612
613
            $options[$option] = $value;
614
        }
615
616
        return $options;
617
    }
618
619
    /**
620
     * @return int|null
621
     */
622
    private function convertCursorType()
623
    {
624
        if (! $this->tailable) {
625
            return null;
626
        }
627
628
        return $this->awaitData ? Find::TAILABLE_AWAIT : Find::TAILABLE;
629
    }
630
631
    private function convertModifiers()
632
    {
633
        $modifiers = array_key_exists('modifiers', $this->options) ? $this->options['modifiers'] : [];
634
635
        foreach (['hint', 'snapshot'] as $modifier) {
636
            if ($this->$modifier === null) {
637
                continue;
638
            }
639
640
            $modifiers['$' . $modifier] = $this->$modifier;
641
        }
642
643
        return $modifiers;
644
    }
645
646
    /**
647
     * @return ReadPreference|null
648
     */
649
    private function convertReadPreference()
650
    {
651
        $type = array_key_exists('type', $this->readPreference) ? $this->readPreference['type'] : null;
652
        if ($type === null) {
653
            return static::$slaveOkay ? new ReadPreference(ReadPreference::RP_SECONDARY_PREFERRED) : null;
654
        }
655
656
        switch ($type) {
657
            case MongoClient::RP_PRIMARY_PREFERRED:
658
                $mode = ReadPreference::RP_PRIMARY_PREFERRED;
659
                break;
660
            case MongoClient::RP_SECONDARY:
661
                $mode = ReadPreference::RP_SECONDARY;
662
                break;
663
            case MongoClient::RP_SECONDARY_PREFERRED:
664
                $mode = ReadPreference::RP_SECONDARY_PREFERRED;
665
                break;
666
            case MongoClient::RP_NEAREST:
667
                $mode = ReadPreference::RP_NEAREST;
668
                break;
669
            default:
670
                $mode = ReadPreference::RP_PRIMARY;
671
        }
672
673
        $tagSets = array_key_exists('tagsets', $this->readPreference) ? $this->readPreference['tagsets'] : [];
674
675
        return new ReadPreference($mode, $tagSets);
676
    }
677
678
    /**
679
     * @return Cursor
680
     */
681
    private function ensureCursor()
682
    {
683
        if ($this->cursor === null) {
684
            $this->doQuery();
685
        }
686
687
        return $this->cursor;
688
    }
689
690
    private function errorIfOpened()
691
    {
692
        if ($this->cursor === null) {
693
            return;
694
        }
695
696
        throw new MongoCursorException('cannot modify cursor after beginning iteration.');
697
    }
698
699
    /**
700
     * @return IteratorIterator
701
     */
702
    private function ensureIterator()
703
    {
704
        if ($this->iterator === null) {
705
            $this->iterator = new IteratorIterator($this->ensureCursor());
706
        }
707
708
        return $this->iterator;
709
    }
710
711
    protected function notImplemented()
712
    {
713
        throw new \Exception('Not implemented');
714
    }
715
}
716