Completed
Push — master ( cc122d...e5911d )
by Maciej
14s
created

lib/Doctrine/ODM/MongoDB/Cursor.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB;
21
22
use Doctrine\MongoDB\Collection;
23
use Doctrine\MongoDB\Connection;
24
use Doctrine\MongoDB\CursorInterface;
25
use Doctrine\MongoDB\EagerCursor as BaseEagerCursor;
26
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
27
use Doctrine\ODM\MongoDB\Query\Query;
28
use Doctrine\ODM\MongoDB\Query\ReferencePrimer;
29
30
/**
31
 * Wrapper for the Doctrine\MongoDB\Cursor class.
32
 *
33
 * This class composes a Doctrine\MongoDB\Cursor instance and wraps its methods
34
 * in order to return results as hydrated document class instances. Hydration
35
 * behavior may be controlled with the {@link Cursor::hydrate()} method.
36
 *
37
 * For compatibility, this class also extends Doctrine\MongoDB\Cursor.
38
 *
39
 * @since  1.0
40
 */
41
class Cursor implements CursorInterface
42
{
43
    /**
44
     * The Doctrine\MongoDB\Cursor instance being wrapped.
45
     *
46
     * @var CursorInterface
47
     */
48
    private $baseCursor;
49
50
    /**
51
     * The ClassMetadata instance for the document class being queried.
52
     *
53
     * @var ClassMetadata
54
     */
55
    private $class;
56
57
    /**
58
     * Whether or not to hydrate results as document class instances.
59
     *
60
     * @var boolean
61
     */
62
    private $hydrate = true;
63
64
    /**
65
     * The UnitOfWork instance used for result hydration and preparing arguments
66
     * for {@link Cursor::sort()}.
67
     *
68
     * @var UnitOfWork
69
     */
70
    private $unitOfWork;
71
72
    /**
73
     * Hints for UnitOfWork behavior.
74
     *
75
     * @var array
76
     */
77
    private $unitOfWorkHints = array();
78
79
    /**
80
     * ReferencePrimer object for priming references
81
     *
82
     * @var ReferencePrimer
83
     */
84
    private $referencePrimer;
85
86
    /**
87
     * Primers
88
     *
89
     * @var array
90
     */
91
    private $primers = array();
92
93
    /**
94
     * Whether references have been primed
95
     *
96
     * @var bool
97
     */
98
    private $referencesPrimed = false;
99
100
    /**
101
     * Constructor.
102
     *
103
     * @param CursorInterface $baseCursor  Cursor instance being wrapped
104
     * @param UnitOfWork      $unitOfWork  UnitOfWork for result hydration and query preparation
105
     * @param ClassMetadata   $class       ClassMetadata for the document class being queried
106
     */
107 149
    public function __construct(CursorInterface $baseCursor, UnitOfWork $unitOfWork, ClassMetadata $class)
108
    {
109 149
        $this->baseCursor = $baseCursor;
110 149
        $this->unitOfWork = $unitOfWork;
111 149
        $this->class = $class;
112 149
    }
113
114
    /**
115
     * Return the wrapped Doctrine\MongoDB\Cursor instance.
116
     *
117
     * @return CursorInterface
118
     */
119
    public function getBaseCursor()
120
    {
121
        return $this->baseCursor;
122
    }
123
124
    /**
125
     * Return the database connection for this cursor.
126
     *
127
     * @see \Doctrine\MongoDB\Cursor::getConnection()
128
     * @return Connection
129
     */
130
    public function getConnection()
131
    {
132
        return $this->baseCursor->getCollection()->getDatabase()->getConnection();
133
    }
134
135
    /**
136
     * Return the collection for this cursor.
137
     *
138
     * @see CursorInterface::getCollection()
139
     * @return Collection
140
     */
141
    public function getCollection()
142
    {
143
        return $this->baseCursor->getCollection();
144
    }
145
146
    /**
147
     * Return the selected fields (projection).
148
     *
149
     * @see CursorInterface::getFields()
150
     * @return array
151
     */
152
    public function getFields()
153
    {
154
        return $this->baseCursor->getFields();
155
    }
156
157
    /**
158
     * Get hints for UnitOfWork behavior.
159
     *
160
     * @return array
161
     */
162 18
    public function getHints()
163
    {
164 18
        return $this->unitOfWorkHints;
165
    }
166
167
    /**
168
     * Set hints for UnitOfWork behavior.
169
     *
170
     * @param array $hints
171
     */
172 133
    public function setHints(array $hints)
173
    {
174 133
        $this->unitOfWorkHints = $hints;
175 133
    }
176
177
    /**
178
     * Return the query criteria.
179
     *
180
     * @see CursorInterface::getQuery()
181
     * @return array
182
     */
183
    public function getQuery()
184
    {
185
        return $this->baseCursor->getQuery();
186
    }
187
188
    /**
189
     * Wrapper method for MongoCursor::addOption().
190
     *
191
     * @see CursorInterface::addOption()
192
     * @see http://php.net/manual/en/mongocursor.addoption.php
193
     * @param string $key
194
     * @param mixed $value
195
     * @return $this
196
     */
197
    public function addOption($key, $value)
198
    {
199
        $this->baseCursor->addOption($key, $value);
200
        return $this;
201
    }
202
203
    /**
204
     * Wrapper method for MongoCursor::batchSize().
205
     *
206
     * @see CursorInterface::batchSize()
207
     * @see http://php.net/manual/en/mongocursor.batchsize.php
208
     * @param integer $num
209
     * @return $this
210
     */
211
    public function batchSize($num)
212
    {
213
        $this->baseCursor->batchSize($num);
214
        return $this;
215
    }
216
217
    /**
218
     * Wrapper method for MongoCursor::count().
219
     *
220
     * @see CursorInterface::count()
221
     * @see http://php.net/manual/en/countable.count.php
222
     * @see http://php.net/manual/en/mongocursor.count.php
223
     * @param boolean $foundOnly
224
     * @return integer
225
     */
226 25
    public function count($foundOnly = false)
227
    {
228 25
        return $this->baseCursor->count($foundOnly);
229
    }
230
231
    /**
232
     * Wrapper method for MongoCursor::current().
233
     *
234
     * If configured, the result may be a hydrated document class instance.
235
     *
236
     * @see CursorInterface::current()
237
     * @see http://php.net/manual/en/iterator.current.php
238
     * @see http://php.net/manual/en/mongocursor.current.php
239
     * @return array|object|null
240
     */
241 60
    public function current()
242
    {
243 60
        $this->primeReferences();
244
245 58
        return $this->hydrateDocument($this->baseCursor->current());
246
    }
247
248
    /**
249
     * Wrapper method for MongoCursor::dead().
250
     *
251
     * @see CursorInterface::dead()
252
     * @see http://php.net/manual/en/mongocursor.dead.php
253
     * @return boolean
254
     */
255
    public function dead()
256
    {
257
        return $this->baseCursor->dead();
258
    }
259
260
    /**
261
     * Wrapper method for MongoCursor::explain().
262
     *
263
     * @see CursorInterface::explain()
264
     * @see http://php.net/manual/en/mongocursor.explain.php
265
     * @return array
266
     */
267
    public function explain()
268
    {
269
        return $this->baseCursor->explain();
270
    }
271
272
    /**
273
     * Wrapper method for MongoCursor::fields().
274
     *
275
     * @param array $f Fields to return (or not return).
276
     *
277
     * @see CursorInterface::fields()
278
     * @see http://php.net/manual/en/mongocursor.fields.php
279
     * @return $this
280
     */
281
    public function fields(array $f)
282
    {
283
        $this->baseCursor->fields($f);
284
        return $this;
285
    }
286
287
    /**
288
     * Wrapper method for MongoCursor::getNext().
289
     *
290
     * If configured, the result may be a hydrated document class instance.
291
     *
292
     * @see CursorInterface::getNext()
293
     * @see http://php.net/manual/en/mongocursor.getnext.php
294
     * @return array|object|null
295
     */
296 3
    public function getNext()
297
    {
298 3
        $this->primeReferences();
299
300 3
        return $this->hydrateDocument($this->baseCursor->getNext());
301
    }
302
303
    /**
304
     * Wrapper method for MongoCursor::getReadPreference().
305
     *
306
     * @see CursorInterface::getReadPreference()
307
     * @see http://php.net/manual/en/mongocursor.getreadpreference.php
308
     * @return array
309
     */
310
    public function getReadPreference()
311
    {
312
        return $this->baseCursor->getReadPreference();
313
    }
314
315
    /**
316
     * Wrapper method for MongoCursor::setReadPreference().
317
     *
318
     * @see CursorInterface::setReadPreference()
319
     * @see http://php.net/manual/en/mongocursor.setreadpreference.php
320
     * @param string $readPreference
321
     * @param array  $tags
322
     * @return $this
323
     */
324
    public function setReadPreference($readPreference, array $tags = null)
325
    {
326
        $this->baseCursor->setReadPreference($readPreference, $tags);
327
        $this->unitOfWorkHints[Query::HINT_READ_PREFERENCE] = $readPreference;
328
        $this->unitOfWorkHints[Query::HINT_READ_PREFERENCE_TAGS] = $tags;
329
        return $this;
330
    }
331
332
    /**
333
     * Reset the cursor and return its first result.
334
     *
335
     * The cursor will be reset both before and after the single result is
336
     * fetched. The original cursor limit (if any) will remain in place.
337
     *
338
     * @see Iterator::getSingleResult()
339
     * @return array|object|null
340
     */
341 89
    public function getSingleResult()
342
    {
343 89
        $document = $this->hydrateDocument($this->baseCursor->getSingleResult());
344 89
        $this->primeReferencesForSingleResult($document);
345
346 89
        return $document;
347
    }
348
349
    /**
350
     * {@inheritDoc}
351
     */
352 54
    public function getUseIdentifierKeys()
353
    {
354 54
        return $this->baseCursor->getUseIdentifierKeys();
355
    }
356
357
    /**
358
     * {@inheritDoc}
359
     */
360 54
    public function setUseIdentifierKeys($useIdentifierKeys)
361
    {
362 54
        $this->baseCursor->setUseIdentifierKeys($useIdentifierKeys);
363
364 54
        return $this;
365
    }
366
367
    /**
368
     * {@inheritDoc}
369
     */
370
    public function hasNext()
371
    {
372
        return $this->baseCursor->hasNext();
373
    }
374
375
    /**
376
     * Wrapper method for MongoCursor::hint().
377
     *
378
     * This method is intended for setting MongoDB query hints, which are
379
     * unrelated to UnitOfWork hints.
380
     *
381
     * @see CursorInterface::hint()
382
     * @see http://php.net/manual/en/mongocursor.hint.php
383
     * @param array|string $keyPattern
384
     * @return $this
385
     */
386
    public function hint($keyPattern)
387
    {
388
        $this->baseCursor->hint($keyPattern);
389
        return $this;
390
    }
391
392
    /**
393
     * Set whether to hydrate results as document class instances.
394
     *
395
     * @param boolean $hydrate
396
     * @return $this
397
     */
398 133
    public function hydrate($hydrate = true)
399
    {
400 133
        $this->hydrate = (boolean) $hydrate;
401 133
        return $this;
402
    }
403
404
    /**
405
     * @param array $document
406
     * @return array|object|null
407
     */
408 133
    private function hydrateDocument($document)
409
    {
410 133
        if ($document !== null && $this->hydrate) {
411 125
            return $this->unitOfWork->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints);
412
        }
413
414 12
        return $document;
415
    }
416
417
    /**
418
     * Wrapper method for MongoCursor::immortal().
419
     *
420
     * @see CursorInterface::immortal()
421
     * @see http://php.net/manual/en/mongocursor.immortal.php
422
     * @param boolean $liveForever
423
     * @return $this
424
     */
425
    public function immortal($liveForever = true)
426
    {
427
        $this->baseCursor->immortal($liveForever);
428
        return $this;
429
    }
430
431
    /**
432
     * Wrapper method for MongoCursor::info().
433
     *
434
     * @see CursorInterface::info()
435
     * @see http://php.net/manual/en/mongocursor.info.php
436
     * @return array
437
     */
438
    public function info()
439
    {
440
        return $this->baseCursor->info();
441
    }
442
443
    /**
444
     * Wrapper method for MongoCursor::key().
445
     *
446
     * @see CursorInterface::key()
447
     * @see http://php.net/manual/en/iterator.key.php
448
     * @see http://php.net/manual/en/mongocursor.key.php
449
     * @return string
450
     */
451 5
    public function key()
452
    {
453 5
        return $this->baseCursor->key();
454
    }
455
456
    /**
457
     * Wrapper method for MongoCursor::limit().
458
     *
459
     * @see CursorInterface::limit()
460
     * @see http://php.net/manual/en/mongocursor.limit.php
461
     * @param integer $num
462
     * @return $this
463
     */
464 13
    public function limit($num)
465
    {
466 13
        $this->baseCursor->limit($num);
467 13
        return $this;
468
    }
469
470
    /**
471
     * Wrapper method for MongoCursor::next().
472
     *
473
     * @see CursorInterface::next()
474
     * @see http://php.net/manual/en/iterator.next.php
475
     * @see http://php.net/manual/en/mongocursor.next.php
476
     */
477 58
    public function next()
478
    {
479 58
        $this->baseCursor->next();
480 58
    }
481
482
    /**
483
     * Recreates the internal MongoCursor.
484
     *
485
     * @see CursorInterface::recreate()
486
     */
487 2
    public function recreate()
488
    {
489 2
        $this->baseCursor->recreate();
490 2
    }
491
492
    /**
493
     * Set whether to refresh hydrated documents that are already in the
494
     * identity map.
495
     *
496
     * This option has no effect if hydration is disabled.
497
     *
498
     * @param boolean $refresh
499
     * @return $this
500
     */
501
    public function refresh($refresh = true)
502
    {
503
        $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh;
504
        return $this;
505
    }
506
507
    /**
508
     * Wrapper method for MongoCursor::reset().
509
     *
510
     * @see CursorInterface::reset()
511
     * @see http://php.net/manual/en/iterator.reset.php
512
     * @see http://php.net/manual/en/mongocursor.reset.php
513
     */
514 1
    public function reset()
515
    {
516 1
        $this->baseCursor->reset();
517 1
    }
518
519
    /**
520
     * Wrapper method for MongoCursor::rewind().
521
     *
522
     * @see CursorInterface::rewind()
523
     * @see http://php.net/manual/en/iterator.rewind.php
524
     * @see http://php.net/manual/en/mongocursor.rewind.php
525
     */
526 61
    public function rewind()
527
    {
528 61
        $this->baseCursor->rewind();
529 61
    }
530
531
    /**
532
     * Wrapper method for MongoCursor::skip().
533
     *
534
     * @see CursorInterface::skip()
535
     * @see http://php.net/manual/en/mongocursor.skip.php
536
     * @param integer $num
537
     * @return $this
538
     */
539 2
    public function skip($num)
540
    {
541 2
        $this->baseCursor->skip($num);
542 2
        return $this;
543
    }
544
545
    /**
546
     * Wrapper method for MongoCursor::slaveOkay().
547
     *
548
     * @see CursorInterface::slaveOkay()
549
     * @see http://php.net/manual/en/mongocursor.slaveokay.php
550
     * @param boolean $ok
551
     * @return $this
552
     *
553
     * @deprecated in version 1.2 - use setReadPreference on the query instead.
554
     */
555
    public function slaveOkay($ok = true)
556
    {
557
        $ok = (boolean) $ok;
558
        if ($ok) {
559
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
560
                sprintf('%s was deprecated in version 1.2 - use setReadPreference on the query instead.', __METHOD__),
561
                E_USER_DEPRECATED
562
            );
563
        }
564
565
        $this->baseCursor->slaveOkay($ok);
566
        $this->unitOfWorkHints[Query::HINT_SLAVE_OKAY] = $ok;
567
        return $this;
568
    }
569
570
    /**
571
     * Wrapper method for MongoCursor::snapshot().
572
     *
573
     * @see CursorInterface::snapshot()
574
     * @see http://php.net/manual/en/mongocursor.snapshot.php
575
     * @return $this
576
     */
577
    public function snapshot()
578
    {
579
        $this->baseCursor->snapshot();
580
        return $this;
581
    }
582
583
    /**
584
     * Wrapper method for MongoCursor::sort().
585
     *
586
     * Field names will be prepared according to the document mapping.
587
     *
588
     * @see CursorInterface::sort()
589
     * @see http://php.net/manual/en/mongocursor.sort.php
590
     * @param array $fields
591
     * @return $this
592
     */
593 14
    public function sort($fields)
594
    {
595 14
        $fields = $this->unitOfWork
596 14
            ->getDocumentPersister($this->class->name)
597 14
            ->prepareSortOrProjection($fields);
598
599 14
        $this->baseCursor->sort($fields);
600 14
        return $this;
601
    }
602
603
    /**
604
     * Wrapper method for MongoCursor::tailable().
605
     *
606
     * @see CursorInterface::tailable()
607
     * @see http://php.net/manual/en/mongocursor.tailable.php
608
     * @param boolean $tail
609
     * @return $this
610
     */
611
    public function tailable($tail = true)
612
    {
613
        $this->baseCursor->tailable($tail);
614
        return $this;
615
    }
616
617
    /**
618
     * Wrapper method for MongoCursor::timeout().
619
     *
620
     * @see CursorInterface::timeout()
621
     * @see http://php.net/manual/en/mongocursor.timeout.php
622
     * @param integer $ms
623
     * @return $this
624
     */
625
    public function timeout($ms)
626
    {
627
        $this->baseCursor->timeout($ms);
628
        return $this;
629
    }
630
631
    /**
632
     * Return the cursor's results as an array.
633
     *
634
     * If documents in the result set use BSON objects for their "_id", the
635
     * $useKeys parameter may be set to false to avoid errors attempting to cast
636
     * arrays (i.e. BSON objects) to string keys.
637
     *
638
     * @see Iterator::toArray()
639
     * @param boolean $useIdentifierKeys
640
     * @return array
641
     */
642 54
    public function toArray($useIdentifierKeys = null)
643
    {
644 54
        $originalUseIdentifierKeys = $this->getUseIdentifierKeys();
645 54
        $useIdentifierKeys = isset($useIdentifierKeys) ? (boolean) $useIdentifierKeys : $this->baseCursor->getUseIdentifierKeys();
646
647
        /* Let iterator_to_array() decide to use keys or not. This will avoid
648
         * superfluous MongoCursor::info() from the key() method until the
649
         * cursor position is tracked internally.
650
         */
651 54
        $this->setUseIdentifierKeys(true);
652
653 54
        $results = iterator_to_array($this, $useIdentifierKeys);
654
655 52
        $this->setUseIdentifierKeys($originalUseIdentifierKeys);
656
657 52
        return $results;
658
    }
659
660
    /**
661
     * Wrapper method for MongoCursor::valid().
662
     *
663
     * @see CursorInterface::valid()
664
     * @see http://php.net/manual/en/iterator.valid.php
665
     * @see http://php.net/manual/en/mongocursor.valid.php
666
     * @return boolean
667
     */
668 65
    public function valid()
669
    {
670 65
        return $this->baseCursor->valid();
671
    }
672
673
    /**
674
     * @param array $primers
675
     * @param ReferencePrimer $referencePrimer
676
     * @return $this
677
     */
678 23
    public function enableReferencePriming(array $primers, ReferencePrimer $referencePrimer)
679
    {
680 23
        if ( ! $this->baseCursor instanceof BaseEagerCursor) {
681 1
            throw new \BadMethodCallException("Can't enable reference priming when not using eager cursors.");
682
        }
683
684 22
        $this->referencePrimer = $referencePrimer;
685 22
        $this->primers = $primers;
686 22
        return $this;
687
    }
688
689
    /**
690
     * Prime references
691
     */
692 62
    protected function primeReferences()
693
    {
694 62
        if ($this->referencesPrimed || ! $this->hydrate || empty($this->primers)) {
695 60
            return;
696
        }
697
698 16
        $this->referencesPrimed = true;
699
700 16
        foreach ($this->primers as $fieldName => $primer) {
701 16
            $primer = is_callable($primer) ? $primer : null;
702 16
            $this->referencePrimer->primeReferences($this->class, $this, $fieldName, $this->unitOfWorkHints, $primer);
703
        }
704
705 14
        $this->rewind();
706 14
    }
707
708
    /**
709
     * Primes all references for a single document only. This avoids iterating
710
     * over the entire cursor when getSingleResult() is called.
711
     *
712
     * @param object $document
713
     */
714 89
    protected function primeReferencesForSingleResult($document)
715
    {
716 89
        if ($this->referencesPrimed || ! $this->hydrate || empty($this->primers) || null === $document) {
717 85
            return;
718
        }
719
720 4 View Code Duplication
        foreach ($this->primers as $fieldName => $primer) {
721 4
            $primer = is_callable($primer) ? $primer : null;
722 4
            $this->referencePrimer->primeReferences($this->class, array($document), $fieldName, $this->unitOfWorkHints, $primer);
723
        }
724 4
    }
725
}
726