Completed
Pull Request — master (#216)
by
unknown
08:27
created

MongoCursor::count()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 1
nop 1
dl 0
loc 26
rs 8.5806
c 0
b 0
f 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
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
    use MongoAnalytics;
34
35
    /**
36
     * @var bool
37
     */
38
    public static $slaveOkay = false;
39
40
    /**
41
     * @var int
42
     */
43
    public static $timeout = 30000;
44
45
    /**
46
     * @var array
47
     */
48
    protected $optionNames = [
49
        'allowPartialResults',
50
        'batchSize',
51
        'cursorType',
52
        'limit',
53
        'maxTimeMS',
54
        'modifiers',
55
        'noCursorTimeout',
56
        'projection',
57
        'readPreference',
58
        'skip',
59
        'sort',
60
    ];
61
62
    /**
63
     * @var array
64
     */
65
    protected $projection;
66
67
    /**
68
     * @var array
69
     */
70
    protected $query;
71
72
    protected $allowPartialResults;
73
    protected $awaitData;
74
    protected $flags = 0;
75
    protected $hint;
76
    protected $limit;
77
    protected $maxTimeMS;
78
    protected $noCursorTimeout;
79
    protected $options = [];
80
    protected $skip;
81
    protected $snapshot;
82
    protected $sort;
83
    protected $tailable;
84
85
    /**
86
     * Create a new cursor
87
     * @link http://www.php.net/manual/en/mongocursor.construct.php
88
     * @param MongoClient $connection Database connection.
89
     * @param string $ns Full name of database and collection.
90
     * @param array $query Database query.
91
     * @param array $fields Fields to return.
92
     */
93
    public function __construct(MongoClient $connection, $ns, array $query = array(), array $fields = array())
94
    {
95
        parent::__construct($connection, $ns);
96
97
        $this->query = $query;
98
        $this->projection = $fields;
99
    }
100
101
    /**
102
     * Adds a top-level key/value pair to a query
103
     * @link http://www.php.net/manual/en/mongocursor.addoption.php
104
     * @param string $key Fieldname to add.
105
     * @param mixed $value Value to add.
106
     * @throws MongoCursorException
107
     * @return MongoCursor Returns this cursor
108
     */
109
    public function addOption($key, $value)
110
    {
111
        $this->errorIfOpened();
112
        $this->options[$key] = $value;
113
114
        return $this;
115
    }
116
117
    /**
118
     * (PECL mongo &gt;= 1.2.11)<br/>
119
     * Sets whether this cursor will wait for a while for a tailable cursor to return more data
120
     * @param bool $wait [optional] <p>If the cursor should wait for more data to become available.</p>
121
     * @return MongoCursor Returns this cursor.
122
     */
123
    public function awaitData($wait = true)
124
    {
125
        $this->errorIfOpened();
126
        $this->awaitData = $wait;
127
128
        return $this;
129
    }
130
131
132
    /**
133
     * Counts the number of results for this query
134
     * @link http://www.php.net/manual/en/mongocursor.count.php
135
     * @param bool $foundOnly Send cursor limit and skip information to the count function, if applicable.
136
     * @return int The number of documents returned by this cursor's query.
137
     */
138
    public function count($foundOnly = false)
139
    {
140
        return $this->eavesdrop([
141
        'readArgs' => false,
142
        'criteria' => $this->query,
143
        'options' => array_merge(['foundOnly'=> $foundOnly], $this->options),
144
        'operation' => 'count',
145
        'name' => $this->collection->getCollectionName()
146
      ], function () use ($foundOnly) {
147
          $optionNames = ['hint', 'maxTimeMS'];
148
          if ($foundOnly) {
149
              $optionNames = array_merge($optionNames, ['limit', 'skip']);
150
          }
151
152
          $options = $this->getOptions($optionNames) + $this->options;
153
          try {
154
              $count = $this->collection->count(TypeConverter::fromLegacy($this->query), $options);
155
          } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) {
156
              throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e);
157
          } catch (\MongoDB\Driver\Exception\Exception $e) {
158
              throw ExceptionConverter::toLegacy($e);
159
          }
160
161
          return $count;
162
      });
163
    }
164
165
    /**
166
     * @return \Iterator
167
     */
168
    protected function ensureIterator()
169
    {
170
        return $this->eavesdrop([
171
            'readArgs' => false,
172
            'criteria' => $this->query,
173
            'options' => $this->options,
174
            'operation' => 'count',
175
            'name' => $this->collection->getCollectionName()
176 View Code Duplication
        ], function () {
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...
177
            // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
178
            // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
179
            if ($this->iterator === null) {
180
                $this->iterator = $this->wrapTraversable($this->ensureCursor());
181
            }
182
            return $this->iterator;
183
        });
184
    }
185
186
    /**
187
     * Execute the query
188
     * @link http://www.php.net/manual/en/mongocursor.doquery.php
189
     * @throws MongoConnectionException if it cannot reach the database.
190
     * @return void
191
     */
192
    protected function doQuery()
193
    {
194
        $options = $this->getOptions() + $this->options;
195
        try {
196
            $this->cursor = $this->collection->find(TypeConverter::fromLegacy($this->query), $options);
197
        } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) {
198
            throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e);
199
        } catch (\MongoDB\Driver\Exception\Exception $e) {
200
            throw ExceptionConverter::toLegacy($e);
201
        }
202
    }
203
204
    /**
205
     * Return an explanation of the query, often useful for optimization and debugging
206
     * @link http://www.php.net/manual/en/mongocursor.explain.php
207
     * @return array Returns an explanation of the query.
208
     */
209
    public function explain()
210
    {
211
        $optionNames = [
212
            'allowPartialResults',
213
            'batchSize',
214
            'cursorType',
215
            'limit',
216
            'maxTimeMS',
217
            'noCursorTimeout',
218
            'projection',
219
            'skip',
220
            'sort',
221
        ];
222
223
        $options = $this->getOptions($optionNames);
224
225
        $command = [
226
            'explain' => [
227
                'find' => $this->collection->getCollectionName(),
228
                'filter' => TypeConverter::fromLegacy($this->query),
229
            ] + $options,
230
        ];
231
232
        $explained = TypeConverter::toLegacy(iterator_to_array($this->db->command($command))[0]);
233
        unset($explained['ok']);
234
235
        return $explained;
236
    }
237
238
    /**
239
     * Sets the fields for a query
240
     * @link http://www.php.net/manual/en/mongocursor.fields.php
241
     * @param array $f Fields to return (or not return).
242
     * @throws MongoCursorException
243
     * @return MongoCursor
244
     */
245
    public function fields(array $f)
246
    {
247
        $this->errorIfOpened();
248
        $this->projection = $f;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Advances the cursor to the next result, and returns that result
255
     * @link http://www.php.net/manual/en/mongocursor.getnext.php
256
     * @throws MongoConnectionException
257
     * @throws MongoCursorTimeoutException
258
     * @return array Returns the next object
259
     */
260
    public function getNext()
261
    {
262
        return $this->next();
263
    }
264
265
    /**
266
     * Checks if there are any more elements in this cursor
267
     * @link http://www.php.net/manual/en/mongocursor.hasnext.php
268
     * @throws MongoConnectionException
269
     * @throws MongoCursorTimeoutException
270
     * @return bool Returns true if there is another element
271
     */
272
    public function hasNext()
273
    {
274 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...
275
            $this->ensureIterator();
276
            $this->startedIterating = true;
277
            $this->storeIteratorState();
278
            $this->cursorNeedsAdvancing = false;
279
        } elseif ($this->cursorNeedsAdvancing) {
280
            $this->ensureIterator()->next();
281
            $this->cursorNeedsAdvancing = false;
282
        }
283
284
285
        return $this->ensureIterator()->valid();
286
    }
287
288
    /**
289
     * Gives the database a hint about the query
290
     * @link http://www.php.net/manual/en/mongocursor.hint.php
291
     * @param array|string $keyPattern Indexes to use for the query.
292
     * @throws MongoCursorException
293
     * @return MongoCursor Returns this cursor
294
     */
295
    public function hint($keyPattern)
296
    {
297
        $this->errorIfOpened();
298
        $this->hint = $keyPattern;
299
300
        return $this;
301
    }
302
303
    /**
304
     * Sets whether this cursor will timeout
305
     * @link http://www.php.net/manual/en/mongocursor.immortal.php
306
     * @param bool $liveForever If the cursor should be immortal.
307
     * @throws MongoCursorException
308
     * @return MongoCursor Returns this cursor
309
     */
310
    public function immortal($liveForever = true)
311
    {
312
        $this->errorIfOpened();
313
        $this->noCursorTimeout = $liveForever;
314
315
        return $this;
316
    }
317
318
    /**
319
     * Limits the number of results returned
320
     * @link http://www.php.net/manual/en/mongocursor.limit.php
321
     * @param int $num The number of results to return.
322
     * @throws MongoCursorException
323
     * @return MongoCursor Returns this cursor
324
     */
325
    public function limit($num)
326
    {
327
        $this->errorIfOpened();
328
        $this->limit = $num;
329
330
        return $this;
331
    }
332
333
    /**
334
     * @param int $ms
335
     * @return $this
336
     * @throws MongoCursorException
337
     */
338
    public function maxTimeMS($ms)
339
    {
340
        $this->errorIfOpened();
341
        $this->maxTimeMS = $ms;
342
343
        return $this;
344
    }
345
346
    /**
347
     * @link http://www.php.net/manual/en/mongocursor.partial.php
348
     * @param bool $okay [optional] <p>If receiving partial results is okay.</p>
349
     * @return MongoCursor Returns this cursor.
350
     */
351
    public function partial($okay = true)
352
    {
353
        $this->allowPartialResults = $okay;
354
355
        return $this;
356
    }
357
358
    /**
359
     * Clears the cursor
360
     * @link http://www.php.net/manual/en/mongocursor.reset.php
361
     * @return void
362
     */
363
    public function reset()
364
    {
365
        parent::reset();
366
    }
367
368
    /**
369
     * @link http://www.php.net/manual/en/mongocursor.setflag.php
370
     * @param int $flag
371
     * @param bool $set
372
     * @return MongoCursor
373
     */
374
    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...
375
    {
376
        $this->notImplemented();
377
    }
378
379
    /**
380
     * Skips a number of results
381
     * @link http://www.php.net/manual/en/mongocursor.skip.php
382
     * @param int $num The number of results to skip.
383
     * @throws MongoCursorException
384
     * @return MongoCursor Returns this cursor
385
     */
386
    public function skip($num)
387
    {
388
        $this->errorIfOpened();
389
        $this->skip = $num;
390
391
        return $this;
392
    }
393
394
    /**
395
     * Sets whether this query can be done on a slave
396
     * This method will override the static class variable slaveOkay.
397
     * @link http://www.php.net/manual/en/mongocursor.slaveOkay.php
398
     * @param boolean $okay If it is okay to query the slave.
399
     * @throws MongoCursorException
400
     * @return MongoCursor Returns this cursor
401
     */
402
    public function slaveOkay($okay = true)
403
    {
404
        $this->errorIfOpened();
405
406
        $this->setReadPreferenceFromSlaveOkay($okay);
407
408
        return $this;
409
    }
410
411
    /**
412
     * Use snapshot mode for the query
413
     * @link http://www.php.net/manual/en/mongocursor.snapshot.php
414
     * @throws MongoCursorException
415
     * @return MongoCursor Returns this cursor
416
     */
417
    public function snapshot()
418
    {
419
        $this->errorIfOpened();
420
        $this->snapshot = true;
421
422
        return $this;
423
    }
424
425
    /**
426
     * Sorts the results by given fields
427
     * @link http://www.php.net/manual/en/mongocursor.sort.php
428
     * @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
429
     * @throws MongoCursorException
430
     * @return MongoCursor Returns the same cursor that this method was called on
431
     */
432
    public function sort(array $fields)
433
    {
434
        $this->errorIfOpened();
435
        $this->sort = $fields;
436
437
        return $this;
438
    }
439
440
    /**
441
     * Sets whether this cursor will be left open after fetching the last results
442
     * @link http://www.php.net/manual/en/mongocursor.tailable.php
443
     * @param bool $tail If the cursor should be tailable.
444
     * @return MongoCursor Returns this cursor
445
     */
446
    public function tailable($tail = true)
447
    {
448
        $this->errorIfOpened();
449
        $this->tailable = $tail;
450
451
        return $this;
452
    }
453
454
    /**
455
     * @return int|null
456
     */
457
    protected function convertCursorType()
458
    {
459
        if (! $this->tailable) {
460
            return null;
461
        }
462
463
        return $this->awaitData ? Find::TAILABLE_AWAIT : Find::TAILABLE;
464
    }
465
466
    /**
467
     * @return array
468
     */
469
    protected function convertModifiers()
470
    {
471
        $modifiers = array_key_exists('modifiers', $this->options) ? $this->options['modifiers'] : [];
472
473
        foreach (['hint', 'snapshot'] as $modifier) {
474
            if ($this->$modifier === null) {
475
                continue;
476
            }
477
478
            $modifiers['$' . $modifier] = $this->$modifier;
479
        }
480
481
        return $modifiers;
482
    }
483
484
    /**
485
     * @return array
486
     */
487
    protected function convertProjection()
488
    {
489
        return TypeConverter::convertProjection($this->projection);
490
    }
491
492
    /**
493
     * @return Cursor
494
     */
495
    protected function ensureCursor()
496
    {
497
        if ($this->cursor === null) {
498
            $this->doQuery();
499
        }
500
501
        return $this->cursor;
502
    }
503
504
505
506
    /**
507
     * @param \Traversable $traversable
508
     * @return \Generator
509
     */
510
    protected function wrapTraversable(\Traversable $traversable)
511
    {
512
        foreach ($traversable as $key => $value) {
513
            if (isset($value->_id) && ($value->_id instanceof \MongoDB\BSON\ObjectID || !is_object($value->_id))) {
514
                $key = (string) $value->_id;
515
            }
516
            yield $key => $value;
517
        }
518
    }
519
520
    /**
521
     * @return array
522
     */
523
    protected function getCursorInfo()
524
    {
525
        return [
526
            'ns' => $this->ns,
527
            'limit' => $this->limit,
528
            'batchSize' => (int) $this->batchSize,
529
            'skip' => $this->skip,
530
            'flags' => $this->flags,
531
            'query' => $this->query,
532
            'fields' => $this->projection,
533
        ];
534
    }
535
536
    /**
537
     * @return array
538
     */
539
    public function __sleep()
540
    {
541
        return [
542
            'allowPartialResults',
543
            'awaitData',
544
            'flags',
545
            'hint',
546
            'limit',
547
            'maxTimeMS',
548
            'noCursorTimeout',
549
            'optionNames',
550
            'options',
551
            'projection',
552
            'query',
553
            'skip',
554
            'snapshot',
555
            'sort',
556
            'tailable',
557
        ] + parent::__sleep();
558
    }
559
}
560