Completed
Push — master ( 7b9f4b...1cd743 )
by Andreas
13s queued 10s
created

lib/Doctrine/ODM/MongoDB/Query/Builder.php (4 issues)

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
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Query;
6
7
use BadMethodCallException;
8
use Doctrine\ODM\MongoDB\DocumentManager;
9
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
10
use GeoJson\Geometry\Geometry;
11
use GeoJson\Geometry\Point;
12
use InvalidArgumentException;
13
use MongoDB\BSON\Binary;
14
use MongoDB\BSON\Javascript;
15
use MongoDB\Collection;
16
use MongoDB\Driver\ReadPreference;
17
use function array_filter;
18
use function array_key_exists;
19
use function count;
20
use function func_get_args;
21
use function in_array;
22
use function is_array;
23
use function is_bool;
24
use function is_callable;
25
use function is_string;
26
use function strtolower;
27
28
/**
29
 * Query builder for ODM.
30
 */
31
class Builder
32
{
33
    /**
34
     * The DocumentManager instance for this query
35
     *
36
     * @var DocumentManager
37
     */
38
    private $dm;
39
40
    /**
41
     * The ClassMetadata instance.
42
     *
43
     * @var ClassMetadata
44
     */
45
    private $class;
46
47
    /**
48
     * The current field we are operating on.
49
     *
50
     * @todo Change this to private once ODM requires doctrine/mongodb 1.1+
51
     * @var string
52
     */
53
    protected $currentField;
54
55
    /**
56
     * Whether or not to hydrate the data to documents.
57
     *
58
     * @var bool
59
     */
60
    private $hydrate = true;
61
62
    /**
63
     * Whether or not to refresh the data for documents that are already in the identity map.
64
     *
65
     * @var bool
66
     */
67
    private $refresh = false;
68
69
    /**
70
     * Array of primer Closure instances.
71
     *
72
     * @var array
73
     */
74
    private $primers = [];
75
76
    /**
77
     * Whether or not to register documents in UnitOfWork.
78
     *
79
     * @var bool
80
     */
81
    private $readOnly = false;
82
83
    /**
84
     * The Collection instance.
85
     *
86
     * @var Collection
87
     */
88
    private $collection;
89
90
    /**
91
     * Array containing the query data.
92
     *
93
     * @var array
94
     */
95
    private $query = ['type' => Query::TYPE_FIND];
96
97
    /**
98
     * The Expr instance used for building this query.
99
     *
100
     * This object includes the query criteria and the "new object" used for
101
     * insert and update queries.
102
     *
103
     * @var Expr $expr
104
     */
105
    private $expr;
106
107
    /**
108
     * Construct a Builder
109
     *
110
     * @param string[]|string|null $documentName (optional) an array of document names, the document name, or none
111
     */
112 286
    public function __construct(DocumentManager $dm, $documentName = null)
113
    {
114 286
        $this->dm   = $dm;
115 286
        $this->expr = new Expr($dm);
116 286
        if ($documentName === null) {
117 9
            return;
118
        }
119
120 278
        $this->setDocumentName($documentName);
121 277
    }
122
123 1
    public function __clone()
124
    {
125 1
        $this->expr = clone $this->expr;
126 1
    }
127
128
    /**
129
     * Add one or more $and clauses to the current query.
130
     *
131
     * You can create a new expression using the {@link Builder::expr()} method.
132
     *
133
     * @see Expr::addAnd()
134
     * @see http://docs.mongodb.org/manual/reference/operator/and/
135
     *
136
     * @param array|Expr $expression
137
     * @param array|Expr ...$expressions
138
     */
139 4
    public function addAnd($expression, ...$expressions) : self
140
    {
141 4
        $this->expr->addAnd(...func_get_args());
142 4
        return $this;
143
    }
144
145
    /**
146
     * Add one or more $nor clauses to the current query.
147
     *
148
     * You can create a new expression using the {@link Builder::expr()} method.
149
     *
150
     * @see Expr::addNor()
151
     * @see http://docs.mongodb.org/manual/reference/operator/nor/
152
     *
153
     * @param array|Expr $expression
154
     * @param array|Expr ...$expressions
155
     */
156 3
    public function addNor($expression, ...$expressions) : self
157
    {
158 3
        $this->expr->addNor(...func_get_args());
159 3
        return $this;
160
    }
161
162
    /**
163
     * Add one or more $or clauses to the current query.
164
     *
165
     * You can create a new expression using the {@link Builder::expr()} method.
166
     *
167
     * @see Expr::addOr()
168
     * @see http://docs.mongodb.org/manual/reference/operator/or/
169
     *
170
     * @param array|Expr $expression
171
     * @param array|Expr ...$expressions
172
     */
173 6
    public function addOr($expression, ...$expressions) : self
174
    {
175 6
        $this->expr->addOr(...func_get_args());
176 6
        return $this;
177
    }
178
179
    /**
180
     * Append one or more values to the current array field only if they do not
181
     * already exist in the array.
182
     *
183
     * If the field does not exist, it will be set to an array containing the
184
     * unique value(s) in the argument. If the field is not an array, the query
185
     * will yield an error.
186
     *
187
     * Multiple values may be specified by provided an Expr object and using
188
     * {@link Expr::each()}.
189
     *
190
     * @see Expr::addToSet()
191
     * @see http://docs.mongodb.org/manual/reference/operator/addToSet/
192
     * @see http://docs.mongodb.org/manual/reference/operator/each/
193
     *
194
     * @param mixed|Expr $valueOrExpression
195
     */
196 5
    public function addToSet($valueOrExpression) : self
197
    {
198 5
        $this->expr->addToSet($valueOrExpression);
199 5
        return $this;
200
    }
201
202
    /**
203
     * Specify $all criteria for the current field.
204
     *
205
     * @see Expr::all()
206
     * @see http://docs.mongodb.org/manual/reference/operator/all/
207
     */
208 3
    public function all(array $values) : self
209
    {
210 3
        $this->expr->all($values);
211 3
        return $this;
212
    }
213
214
    /**
215
     * Apply a bitwise and operation on the current field.
216
     *
217
     * @see Expr::bitAnd()
218
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
219
     *
220
     * @return $this
221
     */
222 1
    public function bitAnd(int $value) : self
223
    {
224 1
        $this->expr->bitAnd($value);
225 1
        return $this;
226
    }
227
228
    /**
229
     * Apply a bitwise or operation on the current field.
230
     *
231
     * @see Expr::bitOr()
232
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
233
     */
234 1
    public function bitOr(int $value) : self
235
    {
236 1
        $this->expr->bitOr($value);
237 1
        return $this;
238
    }
239
240
    /**
241
     * Matches documents where all of the bit positions given by the query are
242
     * clear.
243
     *
244
     * @see Expr::bitsAllClear()
245
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllClear/
246
     *
247
     * @param int|array|Binary $value
248
     */
249 1
    public function bitsAllClear($value) : self
250
    {
251 1
        $this->expr->bitsAllClear($value);
252 1
        return $this;
253
    }
254
255
    /**
256
     * Matches documents where all of the bit positions given by the query are
257
     * set.
258
     *
259
     * @see Expr::bitsAllSet()
260
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllSet/
261
     *
262
     * @param int|array|Binary $value
263
     */
264 1
    public function bitsAllSet($value) : self
265
    {
266 1
        $this->expr->bitsAllSet($value);
267 1
        return $this;
268
    }
269
270
    /**
271
     * Matches documents where any of the bit positions given by the query are
272
     * clear.
273
     *
274
     * @see Expr::bitsAnyClear()
275
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnyClear/
276
     *
277
     * @param int|array|Binary $value
278
     */
279 1
    public function bitsAnyClear($value) : self
280
    {
281 1
        $this->expr->bitsAnyClear($value);
282 1
        return $this;
283
    }
284
285
    /**
286
     * Matches documents where any of the bit positions given by the query are
287
     * set.
288
     *
289
     * @see Expr::bitsAnySet()
290
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnySet/
291
     *
292
     * @param int|array|Binary $value
293
     */
294 1
    public function bitsAnySet($value) : self
295
    {
296 1
        $this->expr->bitsAnySet($value);
297 1
        return $this;
298
    }
299
300
    /**
301
     * Apply a bitwise xor operation on the current field.
302
     *
303
     * @see Expr::bitXor()
304
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
305
     */
306 1
    public function bitXor(int $value) : self
307
    {
308 1
        $this->expr->bitXor($value);
309 1
        return $this;
310
    }
311
312
    /**
313
     * A boolean flag to enable or disable case sensitive search for $text
314
     * criteria.
315
     *
316
     * This method must be called after text().
317
     *
318
     * @see Expr::caseSensitive()
319
     * @see http://docs.mongodb.org/manual/reference/operator/text/
320
     *
321
     * @throws BadMethodCallException If the query does not already have $text criteria.
322
     */
323 1
    public function caseSensitive(bool $caseSensitive) : self
324
    {
325 1
        $this->expr->caseSensitive($caseSensitive);
326 1
        return $this;
327
    }
328
329
    /**
330
     * Associates a comment to any expression taking a query predicate.
331
     *
332
     * @see Expr::comment()
333
     * @see http://docs.mongodb.org/manual/reference/operator/query/comment/
334
     */
335 1
    public function comment(string $comment) : self
336
    {
337 1
        $this->expr->comment($comment);
338 1
        return $this;
339
    }
340
341
    /**
342
     * Change the query type to count.
343
     */
344
    public function count() : self
345
    {
346
        $this->query['type'] = Query::TYPE_COUNT;
347
        return $this;
348
    }
349
350
    /**
351
     * Sets the value of the current field to the current date, either as a date or a timestamp.
352
     *
353
     * @see Expr::currentDate()
354
     * @see http://docs.mongodb.org/manual/reference/operator/currentDate/
355
     */
356 3
    public function currentDate(string $type = 'date') : self
357
    {
358 3
        $this->expr->currentDate($type);
359 2
        return $this;
360
    }
361
362
    /**
363
     * Return an array of information about the Builder state for debugging.
364
     *
365
     * The $name parameter may be used to return a specific key from the
366
     * internal $query array property. If omitted, the entire array will be
367
     * returned.
368
     *
369
     * @return mixed
370
     */
371 28
    public function debug(?string $name = null)
372
    {
373 28
        return $name !== null ? $this->query[$name] : $this->query;
374
    }
375
376
    /**
377
     * A boolean flag to enable or disable diacritic sensitive search for $text
378
     * criteria.
379
     *
380
     * This method must be called after text().
381
     *
382
     * @see Builder::diacriticSensitive()
383
     * @see http://docs.mongodb.org/manual/reference/operator/text/
384
     *
385
     * @throws BadMethodCallException If the query does not already have $text criteria.
386
     */
387 1
    public function diacriticSensitive(bool $diacriticSensitive) : self
388
    {
389 1
        $this->expr->diacriticSensitive($diacriticSensitive);
390 1
        return $this;
391
    }
392
393
    /**
394
     * Change the query type to a distinct command.
395
     *
396
     * @see http://docs.mongodb.org/manual/reference/command/distinct/
397
     */
398 2
    public function distinct(string $field) : self
399
    {
400 2
        $this->query['type']     = Query::TYPE_DISTINCT;
401 2
        $this->query['distinct'] = $field;
402 2
        return $this;
403
    }
404
405
    /**
406
     * Specify $elemMatch criteria for the current field.
407
     *
408
     * You can create a new expression using the {@link Builder::expr()} method.
409
     *
410
     * @see Expr::elemMatch()
411
     * @see http://docs.mongodb.org/manual/reference/operator/elemMatch/
412
     *
413
     * @param array|Expr $expression
414
     */
415 6
    public function elemMatch($expression) : self
416
    {
417 6
        $this->expr->elemMatch($expression);
418 6
        return $this;
419
    }
420
421
    /**
422
     * Specify an equality match for the current field.
423
     *
424
     * @see Expr::equals()
425
     *
426
     * @param mixed $value
427
     */
428 79
    public function equals($value) : self
429
    {
430 79
        $this->expr->equals($value);
431 79
        return $this;
432
    }
433
434
    /**
435
     * Set one or more fields to be excluded from the query projection.
436
     *
437
     * If fields have been selected for inclusion, only the "_id" field may be
438
     * excluded.
439
     *
440
     * @param array|string $fieldName,...
441
     */
442 6
    public function exclude($fieldName = null) : self
443
    {
444 6
        if (! isset($this->query['select'])) {
445 6
            $this->query['select'] = [];
446
        }
447
448 6
        $fieldNames = is_array($fieldName) ? $fieldName : func_get_args();
449
450 6
        foreach ($fieldNames as $fieldName) {
451 4
            $this->query['select'][$fieldName] = 0;
452
        }
453
454 6
        return $this;
455
    }
456
457
    /**
458
     * Specify $exists criteria for the current field.
459
     *
460
     * @see Expr::exists()
461
     * @see http://docs.mongodb.org/manual/reference/operator/exists/
462
     */
463 5
    public function exists(bool $bool) : self
464
    {
465 5
        $this->expr->exists($bool);
466 5
        return $this;
467
    }
468
469
    /**
470
     * Create a new Expr instance that can be used as an expression with the Builder
471
     */
472 26
    public function expr() : Expr
473
    {
474 26
        $expr = new Expr($this->dm);
475 26
        $expr->setClassMetadata($this->class);
476
477 26
        return $expr;
478
    }
479
480
    /**
481
     * Set the current field to operate on.
482
     */
483 145
    public function field(string $field) : self
484
    {
485 145
        $this->currentField = $field;
486 145
        $this->expr->field($field);
487
488 145
        return $this;
489
    }
490
491
    /**
492
     * Set the "finalize" option for a mapReduce or group command.
493
     *
494
     * @param string|Javascript $finalize
495
     *
496
     * @throws BadMethodCallException If the query is not a mapReduce or group command.
497
     */
498 2
    public function finalize($finalize) : self
499
    {
500 2
        switch ($this->query['type']) {
501
            case Query::TYPE_MAP_REDUCE:
502 1
                $this->query['mapReduce']['options']['finalize'] = $finalize;
503 1
                break;
504
505
            case Query::TYPE_GROUP:
506
                $this->query['group']['options']['finalize'] = $finalize;
507
                break;
508
509
            default:
510 1
                throw new BadMethodCallException('mapReduce(), map() or group() must be called before finalize()');
511
        }
512
513 1
        return $this;
514
    }
515
516
    /**
517
     * Change the query type to find and optionally set and change the class being queried.
518
     */
519 13
    public function find(?string $documentName = null) : self
520
    {
521 13
        $this->setDocumentName($documentName);
522 13
        $this->query['type'] = Query::TYPE_FIND;
523
524 13
        return $this;
525
    }
526
527 1
    public function findAndRemove(?string $documentName = null) : self
528
    {
529 1
        $this->setDocumentName($documentName);
530 1
        $this->query['type'] = Query::TYPE_FIND_AND_REMOVE;
531
532 1
        return $this;
533
    }
534
535 13
    public function findAndUpdate(?string $documentName = null) : self
536
    {
537 13
        $this->setDocumentName($documentName);
538 13
        $this->query['type'] = Query::TYPE_FIND_AND_UPDATE;
539
540 13
        return $this;
541
    }
542
543
    /**
544
     * Add $geoIntersects criteria with a GeoJSON geometry to the query.
545
     *
546
     * The geometry parameter GeoJSON object or an array corresponding to the
547
     * geometry's JSON representation.
548
     *
549
     * @see Expr::geoIntersects()
550
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
551
     *
552
     * @param array|Geometry $geometry
553
     */
554 1
    public function geoIntersects($geometry) : self
555
    {
556 1
        $this->expr->geoIntersects($geometry);
557 1
        return $this;
558
    }
559
560
    /**
561
     * Add $geoWithin criteria with a GeoJSON geometry to the query.
562
     *
563
     * The geometry parameter GeoJSON object or an array corresponding to the
564
     * geometry's JSON representation.
565
     *
566
     * @see Expr::geoWithin()
567
     * @see http://docs.mongodb.org/manual/reference/operator/geoWithin/
568
     *
569
     * @param array|Geometry $geometry
570
     */
571 1
    public function geoWithin($geometry) : self
572
    {
573 1
        $this->expr->geoWithin($geometry);
574 1
        return $this;
575
    }
576
577
    /**
578
     * Add $geoWithin criteria with a $box shape to the query.
579
     *
580
     * A rectangular polygon will be constructed from a pair of coordinates
581
     * corresponding to the bottom left and top right corners.
582
     *
583
     * Note: the $box operator only supports legacy coordinate pairs and 2d
584
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
585
     *
586
     * @see Expr::geoWithinBox()
587
     * @see http://docs.mongodb.org/manual/reference/operator/box/
588
     */
589 1
    public function geoWithinBox(float $x1, float $y1, float $x2, float $y2) : self
590
    {
591 1
        $this->expr->geoWithinBox($x1, $y1, $x2, $y2);
592 1
        return $this;
593
    }
594
595
    /**
596
     * Add $geoWithin criteria with a $center shape to the query.
597
     *
598
     * Note: the $center operator only supports legacy coordinate pairs and 2d
599
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
600
     *
601
     * @see Expr::geoWithinCenter()
602
     * @see http://docs.mongodb.org/manual/reference/operator/center/
603
     */
604 1
    public function geoWithinCenter(float $x, float $y, float $radius) : self
605
    {
606 1
        $this->expr->geoWithinCenter($x, $y, $radius);
607 1
        return $this;
608
    }
609
610
    /**
611
     * Add $geoWithin criteria with a $centerSphere shape to the query.
612
     *
613
     * Note: the $centerSphere operator supports both 2d and 2dsphere indexes.
614
     *
615
     * @see Expr::geoWithinCenterSphere()
616
     * @see http://docs.mongodb.org/manual/reference/operator/centerSphere/
617
     */
618 1
    public function geoWithinCenterSphere(float $x, float $y, float $radius) : self
619
    {
620 1
        $this->expr->geoWithinCenterSphere($x, $y, $radius);
621 1
        return $this;
622
    }
623
624
    /**
625
     * Add $geoWithin criteria with a $polygon shape to the query.
626
     *
627
     * Point coordinates are in x, y order (easting, northing for projected
628
     * coordinates, longitude, latitude for geographic coordinates).
629
     *
630
     * The last point coordinate is implicitly connected with the first.
631
     *
632
     * Note: the $polygon operator only supports legacy coordinate pairs and 2d
633
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
634
     *
635
     * @see Expr::geoWithinPolygon()
636
     * @see http://docs.mongodb.org/manual/reference/operator/polygon/
637
     *
638
     * @param array $point1    First point of the polygon
639
     * @param array $point2    Second point of the polygon
640
     * @param array $point3    Third point of the polygon
641
     * @param array ...$points Additional points of the polygon
642
     */
643 1
    public function geoWithinPolygon($point1, $point2, $point3, ...$points) : self
644
    {
645 1
        $this->expr->geoWithinPolygon(...func_get_args());
646 1
        return $this;
647
    }
648
649
    /**
650
     * Return the expression's "new object".
651
     *
652
     * @see Expr::getNewObj()
653
     */
654 13
    public function getNewObj() : array
655
    {
656 13
        return $this->expr->getNewObj();
657
    }
658
659
    /**
660
     * Gets the Query executable.
661
     */
662 153
    public function getQuery(array $options = []) : Query
663
    {
664 153
        if ($this->query['type'] === Query::TYPE_MAP_REDUCE) {
665
            $this->hydrate = false;
666
        }
667
668 153
        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
669
670 153
        $query = $this->query;
671
672 153
        $query['query'] = $this->expr->getQuery();
673 153
        $query['query'] = $documentPersister->addDiscriminatorToPreparedQuery($query['query']);
674 153
        $query['query'] = $documentPersister->addFilterToPreparedQuery($query['query']);
675
676 153
        $query['newObj'] = $this->expr->getNewObj();
677
678 153
        if (isset($query['distinct'])) {
679 2
            $query['distinct'] = $documentPersister->prepareFieldName($query['distinct']);
680
        }
681
682 153
        if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_SINGLE_COLLECTION && ! empty($query['upsert']) &&
683 153
            (empty($query['query'][$this->class->discriminatorField]) || is_array($query['query'][$this->class->discriminatorField]))) {
684 1
            throw new InvalidArgumentException('Upsert query that is to be performed on discriminated document does not have single ' .
685 1
                'discriminator. Either not use base class or set \'' . $this->class->discriminatorField . '\' field manually.');
686
        }
687
688 152
        if (! empty($query['select'])) {
689 14
            $query['select'] = $documentPersister->prepareProjection($query['select']);
690 14
            if ($this->hydrate && $this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_SINGLE_COLLECTION
691 14
                && ! isset($query['select'][$this->class->discriminatorField])) {
692
                $includeMode = 0 < count(array_filter($query['select'], static function ($mode) {
693 2
                    return $mode === 1;
694 2
                }));
695 2
                if ($includeMode && ! isset($query['select'][$this->class->discriminatorField])) {
696 1
                    $query['select'][$this->class->discriminatorField] = 1;
697
                }
698
            }
699
        }
700
701 152
        if (isset($query['sort'])) {
702 19
            $query['sort'] = $documentPersister->prepareSort($query['sort']);
703
        }
704
705 152
        if ($this->class->readPreference && ! array_key_exists('readPreference', $query)) {
706 1
            $query['readPreference'] = new ReadPreference($this->class->readPreference, $this->class->readPreferenceTags);
707
        }
708
709 152
        return new Query(
710 152
            $this->dm,
711 152
            $this->class,
712 152
            $this->collection,
713 152
            $query,
714 152
            $options,
715 152
            $this->hydrate,
716 152
            $this->refresh,
717 152
            $this->primers,
718 152
            $this->readOnly
719
        );
720
    }
721
722
    /**
723
     * Return the expression's query criteria.
724
     *
725
     * @see Expr::getQuery()
726
     */
727 33
    public function getQueryArray() : array
728
    {
729 33
        return $this->expr->getQuery();
730
    }
731
732
    /**
733
     * Get the type of this query.
734
     */
735 2
    public function getType() : int
736
    {
737 2
        return $this->query['type'];
738
    }
739
740
    /**
741
     * Specify $gt criteria for the current field.
742
     *
743
     * @see Expr::gt()
744
     * @see http://docs.mongodb.org/manual/reference/operator/gt/
745
     *
746
     * @param mixed $value
747
     */
748 2
    public function gt($value) : self
749
    {
750 2
        $this->expr->gt($value);
751 2
        return $this;
752
    }
753
754
    /**
755
     * Specify $gte criteria for the current field.
756
     *
757
     * @see Expr::gte()
758
     * @see http://docs.mongodb.org/manual/reference/operator/gte/
759
     *
760
     * @param mixed $value
761
     */
762 2
    public function gte($value) : self
763
    {
764 2
        $this->expr->gte($value);
765 2
        return $this;
766
    }
767
768
    /**
769
     * Set the index hint for the query.
770
     *
771
     * @param array|string $index
772
     */
773
    public function hint($index) : self
774
    {
775
        $this->query['hint'] = $index;
776
        return $this;
777
    }
778
779 17
    public function hydrate(bool $bool = true) : self
780
    {
781 17
        $this->hydrate = $bool;
782 17
        return $this;
783
    }
784
785
    /**
786
     * Set the immortal cursor flag.
787
     */
788
    public function immortal(bool $bool = true) : self
789
    {
790
        $this->query['immortal'] = $bool;
791
        return $this;
792
    }
793
794
    /**
795
     * Specify $in criteria for the current field.
796
     *
797
     * @see Expr::in()
798
     * @see http://docs.mongodb.org/manual/reference/operator/in/
799
     */
800 24
    public function in(array $values) : self
801
    {
802 24
        $this->expr->in($values);
803 24
        return $this;
804
    }
805
806
    /**
807
     * Increment the current field.
808
     *
809
     * If the field does not exist, it will be set to this value.
810
     *
811
     * @see Expr::inc()
812
     * @see http://docs.mongodb.org/manual/reference/operator/inc/
813
     *
814
     * @param float|int $value
815
     */
816 6
    public function inc($value) : self
817
    {
818 6
        $this->expr->inc($value);
819 6
        return $this;
820
    }
821
822 6
    public function includesReferenceTo(object $document) : self
823
    {
824 6
        $this->expr->includesReferenceTo($document);
825 4
        return $this;
826
    }
827
828 1
    public function insert(?string $documentName = null) : self
829
    {
830 1
        $this->setDocumentName($documentName);
831 1
        $this->query['type'] = Query::TYPE_INSERT;
832
833 1
        return $this;
834
    }
835
836
    /**
837
     * Set the $language option for $text criteria.
838
     *
839
     * This method must be called after text().
840
     *
841
     * @see Expr::language()
842
     * @see http://docs.mongodb.org/manual/reference/operator/text/
843
     */
844 1
    public function language(string $language) : self
845
    {
846 1
        $this->expr->language($language);
847 1
        return $this;
848
    }
849
850
    /**
851
     * Set the limit for the query.
852
     *
853
     * This is only relevant for find queries and geoNear and mapReduce
854
     * commands.
855
     *
856
     * @see Query::prepareCursor()
857
     */
858 2
    public function limit(int $limit) : self
859
    {
860 2
        $this->query['limit'] = $limit;
861 2
        return $this;
862
    }
863
864
    /**
865
     * Specify $lt criteria for the current field.
866
     *
867
     * @see Expr::lte()
868
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
869
     *
870
     * @param mixed $value
871
     */
872
    public function lt($value) : self
873
    {
874
        $this->expr->lt($value);
875
        return $this;
876
    }
877
878
    /**
879
     * Specify $lte criteria for the current field.
880
     *
881
     * @see Expr::lte()
882
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
883
     *
884
     * @param mixed $value
885
     */
886
    public function lte($value) : self
887
    {
888
        $this->expr->lte($value);
889
        return $this;
890
    }
891
892
    /**
893
     * Change the query type to a mapReduce command.
894
     *
895
     * The "reduce" option is not specified when calling this method; it must
896
     * be set with the {@link Builder::reduce()} method.
897
     *
898
     * The "out" option defaults to inline, like {@link Builder::mapReduce()}.
899
     *
900
     * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
901
     *
902
     * @param string|Javascript $map
903
     */
904 1
    public function map($map) : self
905
    {
906 1
        $this->query['type']      = Query::TYPE_MAP_REDUCE;
907 1
        $this->query['mapReduce'] = [
908 1
            'map' => $map,
909
            'reduce' => null,
910
            'out' => ['inline' => true],
911
            'options' => [],
912
        ];
913 1
        return $this;
914
    }
915
916
    /**
917
     * Change the query type to a mapReduce command.
918
     *
919
     * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
920
     *
921
     * @param string|Javascript $map
922
     * @param string|Javascript $reduce
923
     * @param array|string      $out
924
     * @param array             $options
925
     *
926
     * @return $this
927
     */
928 1
    public function mapReduce($map, $reduce, $out = ['inline' => true], array $options = []) : self
929
    {
930 1
        $this->query['type']      = Query::TYPE_MAP_REDUCE;
931 1
        $this->query['mapReduce'] = [
932 1
            'map' => $map,
933 1
            'reduce' => $reduce,
934 1
            'out' => $out,
935 1
            'options' => $options,
936
        ];
937 1
        return $this;
938
    }
939
940
    /**
941
     * Set additional options for a mapReduce command.
942
     *
943
     * @throws BadMethodCallException If the query is not a mapReduce command.
944
     */
945 1
    public function mapReduceOptions(array $options) : self
946
    {
947 1
        if ($this->query['type'] !== Query::TYPE_MAP_REDUCE) {
948 1
            throw new BadMethodCallException('This method requires a mapReduce command (call map() or mapReduce() first)');
949
        }
950
951
        $this->query['mapReduce']['options'] = $options;
952
        return $this;
953
    }
954
955
    /**
956
     * Updates the value of the field to a specified value if the specified value is greater than the current value of the field.
957
     *
958
     * @see Expr::max()
959
     * @see http://docs.mongodb.org/manual/reference/operator/update/max/
960
     *
961
     * @param mixed $value
962
     */
963 1
    public function max($value) : self
964
    {
965 1
        $this->expr->max($value);
966 1
        return $this;
967
    }
968
969
    /**
970
     * Specifies a cumulative time limit in milliseconds for processing operations on a cursor.
971
     */
972
    public function maxTimeMS(int $ms) : self
973
    {
974
        $this->query['maxTimeMS'] = $ms;
975
        return $this;
976
    }
977
978
    /**
979
     * Updates the value of the field to a specified value if the specified value is less than the current value of the field.
980
     *
981
     * @see Expr::min()
982
     * @see http://docs.mongodb.org/manual/reference/operator/update/min/
983
     *
984
     * @param mixed $value
985
     */
986 1
    public function min($value) : self
987
    {
988 1
        $this->expr->min($value);
989 1
        return $this;
990
    }
991
992
    /**
993
     * Specify $mod criteria for the current field.
994
     *
995
     * @see Expr::mod()
996
     * @see http://docs.mongodb.org/manual/reference/operator/mod/
997
     *
998
     * @param float|int $divisor
999
     * @param float|int $remainder
1000
     */
1001 1
    public function mod($divisor, $remainder = 0) : self
1002
    {
1003 1
        $this->expr->mod($divisor, $remainder);
1004 1
        return $this;
1005
    }
1006
1007
    /**
1008
     * Multiply the current field.
1009
     *
1010
     * If the field does not exist, it will be set to 0.
1011
     *
1012
     * @see Expr::mul()
1013
     * @see http://docs.mongodb.org/manual/reference/operator/mul/
1014
     *
1015
     * @param float|int $value
1016
     */
1017 1
    public function mul($value) : self
1018
    {
1019 1
        $this->expr->mul($value);
1020 1
        return $this;
1021
    }
1022
1023
    /**
1024
     * Add $near criteria to the query.
1025
     *
1026
     * A GeoJSON point may be provided as the first and only argument for
1027
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
1028
     * an array corresponding to the point's JSON representation.
1029
     *
1030
     * @see Expr::near()
1031
     * @see http://docs.mongodb.org/manual/reference/operator/near/
1032
     *
1033
     * @param float|array|Point $x
1034
     * @param float             $y
1035
     */
1036 1
    public function near($x, $y = null) : self
1037
    {
1038 1
        $this->expr->near($x, $y);
1039 1
        return $this;
1040
    }
1041
1042
    /**
1043
     * Add $nearSphere criteria to the query.
1044
     *
1045
     * A GeoJSON point may be provided as the first and only argument for
1046
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
1047
     * an array corresponding to the point's JSON representation.
1048
     *
1049
     * @see Expr::nearSphere()
1050
     * @see http://docs.mongodb.org/manual/reference/operator/nearSphere/
1051
     *
1052
     * @param float|array|Point $x
1053
     * @param float             $y
1054
     */
1055 1
    public function nearSphere($x, $y = null) : self
1056
    {
1057 1
        $this->expr->nearSphere($x, $y);
1058 1
        return $this;
1059
    }
1060
1061
    /**
1062
     * Negates an expression for the current field.
1063
     *
1064
     * You can create a new expression using the {@link Builder::expr()} method.
1065
     *
1066
     * @see Expr::not()
1067
     * @see http://docs.mongodb.org/manual/reference/operator/not/
1068
     *
1069
     * @param array|Expr $expression
1070
     */
1071 3
    public function not($expression) : self
1072
    {
1073 3
        $this->expr->not($expression);
1074 3
        return $this;
1075
    }
1076
1077
    /**
1078
     * Specify $ne criteria for the current field.
1079
     *
1080
     * @see Expr::notEqual()
1081
     * @see http://docs.mongodb.org/manual/reference/operator/ne/
1082
     *
1083
     * @param mixed $value
1084
     */
1085 4
    public function notEqual($value) : self
1086
    {
1087 4
        $this->expr->notEqual($value);
1088 4
        return $this;
1089
    }
1090
1091
    /**
1092
     * Specify $nin criteria for the current field.
1093
     *
1094
     * @see Expr::notIn()
1095
     * @see http://docs.mongodb.org/manual/reference/operator/nin/
1096
     *
1097
     * @param array $values
1098
     */
1099 4
    public function notIn(array $values) : self
1100
    {
1101 4
        $this->expr->notIn($values);
1102 4
        return $this;
1103
    }
1104
1105
    /**
1106
     * Set the "out" option for a mapReduce command.
1107
     *
1108
     * @param array|string $out
1109
     *
1110
     * @throws BadMethodCallException If the query is not a mapReduce command.
1111
     */
1112 1
    public function out($out) : self
1113
    {
1114 1
        if ($this->query['type'] !== Query::TYPE_MAP_REDUCE) {
1115 1
            throw new BadMethodCallException('This method requires a mapReduce command (call map() or mapReduce() first)');
1116
        }
1117
1118
        $this->query['mapReduce']['out'] = $out;
1119
        return $this;
1120
    }
1121
1122
    /**
1123
     * Remove the first element from the current array field.
1124
     *
1125
     * @see Expr::popFirst()
1126
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
1127
     */
1128 3
    public function popFirst() : self
1129
    {
1130 3
        $this->expr->popFirst();
1131 3
        return $this;
1132
    }
1133
1134
    /**
1135
     * Remove the last element from the current array field.
1136
     *
1137
     * @see Expr::popLast()
1138
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
1139
     */
1140 2
    public function popLast() : self
1141
    {
1142 2
        $this->expr->popLast();
1143 2
        return $this;
1144
    }
1145
1146
    /**
1147
     * Use a primer to eagerly load all references in the current field.
1148
     *
1149
     * If $primer is true or a callable is provided, referenced documents for
1150
     * this field will loaded into UnitOfWork immediately after the query is
1151
     * executed. This will avoid multiple queries due to lazy initialization of
1152
     * Proxy objects.
1153
     *
1154
     * If $primer is false, no priming will take place. That is also the default
1155
     * behavior.
1156
     *
1157
     * If a custom callable is used, its signature should conform to the default
1158
     * Closure defined in {@link ReferencePrimer::__construct()}.
1159
     *
1160
     * @param bool|callable $primer
1161
     *
1162
     * @throws InvalidArgumentException If $primer is not boolean or callable.
1163
     */
1164 22
    public function prime($primer = true) : self
1165
    {
1166 22
        if (! is_bool($primer) && ! is_callable($primer)) {
1167 1
            throw new InvalidArgumentException('$primer is not a boolean or callable');
1168
        }
1169
1170 21
        if ($primer === false) {
1171
            unset($this->primers[$this->currentField]);
1172
1173
            return $this;
1174
        }
1175
1176 21
        $this->primers[$this->currentField] = $primer;
1177 21
        return $this;
1178
    }
1179
1180
    /**
1181
     * Remove all elements matching the given value or expression from the
1182
     * current array field.
1183
     *
1184
     * @see Expr::pull()
1185
     * @see http://docs.mongodb.org/manual/reference/operator/pull/
1186
     *
1187
     * @param mixed|Expr $valueOrExpression
1188
     */
1189 1
    public function pull($valueOrExpression) : self
1190
    {
1191 1
        $this->expr->pull($valueOrExpression);
1192 1
        return $this;
1193
    }
1194
1195
    /**
1196
     * Remove all elements matching any of the given values from the current
1197
     * array field.
1198
     *
1199
     * @see Expr::pullAll()
1200
     * @see http://docs.mongodb.org/manual/reference/operator/pullAll/
1201
     */
1202 1
    public function pullAll(array $values) : self
1203
    {
1204 1
        $this->expr->pullAll($values);
1205 1
        return $this;
1206
    }
1207
1208
    /**
1209
     * Append one or more values to the current array field.
1210
     *
1211
     * If the field does not exist, it will be set to an array containing the
1212
     * value(s) in the argument. If the field is not an array, the query
1213
     * will yield an error.
1214
     *
1215
     * Multiple values may be specified by providing an Expr object and using
1216
     * {@link Expr::each()}. {@link Expr::slice()} and {@link Expr::sort()} may
1217
     * also be used to limit and order array elements, respectively.
1218
     *
1219
     * @see Expr::push()
1220
     * @see http://docs.mongodb.org/manual/reference/operator/push/
1221
     * @see http://docs.mongodb.org/manual/reference/operator/each/
1222
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1223
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1224
     *
1225
     * @param mixed|Expr $valueOrExpression
1226
     */
1227 6
    public function push($valueOrExpression) : self
1228
    {
1229 6
        $this->expr->push($valueOrExpression);
1230 6
        return $this;
1231
    }
1232
1233
    /**
1234
     * Specify $gte and $lt criteria for the current field.
1235
     *
1236
     * This method is shorthand for specifying $gte criteria on the lower bound
1237
     * and $lt criteria on the upper bound. The upper bound is not inclusive.
1238
     *
1239
     * @see Expr::range()
1240
     *
1241
     * @param mixed $start
1242
     * @param mixed $end
1243
     */
1244 3
    public function range($start, $end) : self
1245
    {
1246 3
        $this->expr->range($start, $end);
1247 3
        return $this;
1248
    }
1249
1250 2
    public function readOnly(bool $bool = true) : self
1251
    {
1252 2
        $this->readOnly = $bool;
1253 2
        return $this;
1254
    }
1255
1256
    /**
1257
     * Set the "reduce" option for a mapReduce or group command.
1258
     *
1259
     * @param string|Javascript $reduce
1260
     *
1261
     * @throws BadMethodCallException If the query is not a mapReduce or group command.
1262
     */
1263 2
    public function reduce($reduce) : self
1264
    {
1265 2
        switch ($this->query['type']) {
1266
            case Query::TYPE_MAP_REDUCE:
1267 1
                $this->query['mapReduce']['reduce'] = $reduce;
1268 1
                break;
1269
1270
            case Query::TYPE_GROUP:
1271
                $this->query['group']['reduce'] = $reduce;
1272
                break;
1273
1274
            default:
1275 1
                throw new BadMethodCallException('mapReduce(), map() or group() must be called before reduce()');
1276
        }
1277
1278 1
        return $this;
1279
    }
1280
1281 10
    public function references(object $document) : self
1282
    {
1283 10
        $this->expr->references($document);
1284 8
        return $this;
1285
    }
1286
1287 5
    public function refresh(bool $bool = true) : self
1288
    {
1289 5
        $this->refresh = $bool;
1290 5
        return $this;
1291
    }
1292
1293 1
    public function remove(?string $documentName = null) : self
1294
    {
1295 1
        $this->setDocumentName($documentName);
1296 1
        $this->query['type'] = Query::TYPE_REMOVE;
1297
1298 1
        return $this;
1299
    }
1300
1301
    /**
1302
     * Rename the current field.
1303
     *
1304
     * @see Expr::rename()
1305
     * @see http://docs.mongodb.org/manual/reference/operator/rename/
1306
     */
1307
    public function rename(string $name) : self
1308
    {
1309
        $this->expr->rename($name);
1310
        return $this;
1311
    }
1312
1313 4
    public function returnNew(bool $bool = true) : self
1314
    {
1315 4
        $this->refresh(true);
1316 4
        $this->query['new'] = $bool;
1317
1318 4
        return $this;
1319
    }
1320
1321
    /**
1322
     * Set one or more fields to be included in the query projection.
1323
     *
1324
     * @param array|string $fieldName,...
1325
     */
1326 19
    public function select($fieldName = null) : self
1327
    {
1328 19
        if (! isset($this->query['select'])) {
1329 18
            $this->query['select'] = [];
1330
        }
1331
1332 19
        $fieldNames = is_array($fieldName) ? $fieldName : func_get_args();
1333
1334 19
        foreach ($fieldNames as $fieldName) {
1335 16
            $this->query['select'][$fieldName] = 1;
1336
        }
1337
1338 19
        return $this;
1339
    }
1340
1341
    /**
1342
     * Select only matching embedded documents in an array field for the query
1343
     * projection.
1344
     *
1345
     * @see http://docs.mongodb.org/manual/reference/projection/elemMatch/
1346
     *
1347
     * @param array|Expr $expression
1348
     */
1349 2
    public function selectElemMatch(string $fieldName, $expression) : self
1350
    {
1351 2
        if ($expression instanceof Expr) {
1352 1
            $expression = $expression->getQuery();
1353
        }
1354 2
        $this->query['select'][$fieldName] = ['$elemMatch' => $expression];
1355 2
        return $this;
1356
    }
1357
1358
    /**
1359
     * Select a metadata field for the query projection.
1360
     *
1361
     * @see http://docs.mongodb.org/master/reference/operator/projection/meta/
1362
     */
1363 2
    public function selectMeta(string $fieldName, string $metaDataKeyword) : self
1364
    {
1365 2
        $this->query['select'][$fieldName] = ['$meta' => $metaDataKeyword];
1366 2
        return $this;
1367
    }
1368
1369
    /**
1370
     * Select a slice of an array field for the query projection.
1371
     *
1372
     * The $countOrSkip parameter has two very different meanings, depending on
1373
     * whether or not $limit is provided. See the MongoDB documentation for more
1374
     * information.
1375
     *
1376
     * @see http://docs.mongodb.org/manual/reference/projection/slice/
1377
     */
1378 3
    public function selectSlice(string $fieldName, int $countOrSkip, ?int $limit = null) : self
1379
    {
1380 3
        $slice = $countOrSkip;
1381 3
        if ($limit !== null) {
1382 2
            $slice = [$slice, $limit];
1383
        }
1384 3
        $this->query['select'][$fieldName] = ['$slice' => $slice];
1385 3
        return $this;
1386
    }
1387
1388
    /**
1389
     * Set the current field to a value.
1390
     *
1391
     * This is only relevant for insert, update, or findAndUpdate queries. For
1392
     * update and findAndUpdate queries, the $atomic parameter will determine
1393
     * whether or not a $set operator is used.
1394
     *
1395
     * @see Expr::set()
1396
     * @see http://docs.mongodb.org/manual/reference/operator/set/
1397
     *
1398
     * @param mixed $value
1399
     */
1400 16
    public function set($value, bool $atomic = true) : self
1401
    {
1402 16
        $this->expr->set($value, $atomic && $this->query['type'] !== Query::TYPE_INSERT);
1403 16
        return $this;
1404
    }
1405
1406
    /**
1407
     * Set the expression's "new object".
1408
     *
1409
     * @see Expr::setNewObj()
1410
     */
1411
    public function setNewObj(array $newObj) : self
1412
    {
1413
        $this->expr->setNewObj($newObj);
1414
        return $this;
1415
    }
1416
1417
    /**
1418
     * Set the current field to the value if the document is inserted in an
1419
     * upsert operation.
1420
     *
1421
     * If an update operation with upsert: true results in an insert of a
1422
     * document, then $setOnInsert assigns the specified values to the fields in
1423
     * the document. If the update operation does not result in an insert,
1424
     * $setOnInsert does nothing.
1425
     *
1426
     * @see Expr::setOnInsert()
1427
     * @see https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/
1428
     *
1429
     * @param mixed $value
1430
     */
1431 2
    public function setOnInsert($value) : self
1432
    {
1433 2
        $this->expr->setOnInsert($value);
1434 2
        return $this;
1435
    }
1436
1437
    /**
1438
     * Set the read preference for the query.
1439
     *
1440
     * This is only relevant for read-only queries and commands.
1441
     *
1442
     * @see http://docs.mongodb.org/manual/core/read-preference/
1443
     */
1444 6
    public function setReadPreference(ReadPreference $readPreference) : self
1445
    {
1446 6
        $this->query['readPreference'] = $readPreference;
1447 6
        return $this;
1448
    }
1449
1450
    /**
1451
     * Set the expression's query criteria.
1452
     *
1453
     * @see Expr::setQuery()
1454
     */
1455 18
    public function setQueryArray(array $query) : self
1456
    {
1457 18
        $this->expr->setQuery($query);
1458 18
        return $this;
1459
    }
1460
1461
    /**
1462
     * Specify $size criteria for the current field.
1463
     *
1464
     * @see Expr::size()
1465
     * @see http://docs.mongodb.org/manual/reference/operator/size/
1466
     */
1467 1
    public function size(int $size) : self
1468
    {
1469 1
        $this->expr->size($size);
1470 1
        return $this;
1471
    }
1472
1473
    /**
1474
     * Set the skip for the query cursor.
1475
     *
1476
     * This is only relevant for find queries, or mapReduce queries that store
1477
     * results in an output collection and return a cursor.
1478
     *
1479
     * @see Query::prepareCursor()
1480
     */
1481
    public function skip(int $skip) : self
1482
    {
1483
        $this->query['skip'] = $skip;
1484
        return $this;
1485
    }
1486
1487
    /**
1488
     * Set the snapshot cursor flag.
1489
     */
1490
    public function snapshot(bool $bool = true) : self
1491
    {
1492
        $this->query['snapshot'] = $bool;
1493
        return $this;
1494
    }
1495
1496
    /**
1497
     * Set one or more field/order pairs on which to sort the query.
1498
     *
1499
     * If sorting by multiple fields, the first argument should be an array of
1500
     * field name (key) and order (value) pairs.
1501
     *
1502
     * @param array|string $fieldName Field name or array of field/order pairs
1503
     * @param int|string   $order     Field order (if one field is specified)
1504
     */
1505 27
    public function sort($fieldName, $order = 1) : self
1506
    {
1507 27
        if (! isset($this->query['sort'])) {
1508 27
            $this->query['sort'] = [];
1509
        }
1510
1511 27
        $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order];
1512
1513 27
        foreach ($fields as $fieldName => $order) {
1514 14
            if (is_string($order)) {
1515 9
                $order = strtolower($order) === 'asc' ? 1 : -1;
1516
            }
1517 14
            $this->query['sort'][$fieldName] = (int) $order;
1518
        }
1519
1520 27
        return $this;
1521
    }
1522
1523
    /**
1524
     * Specify a projected metadata field on which to sort the query.
1525
     *
1526
     * Sort order is not configurable for metadata fields. Sorting by a metadata
1527
     * field requires the same field and $meta expression to exist in the
1528
     * projection document. This method will call {@link Builder::selectMeta()}
1529
     * if the field is not already set in the projection.
1530
     *
1531
     * @see http://docs.mongodb.org/master/reference/operator/projection/meta/#sort
1532
     */
1533 2
    public function sortMeta(string $fieldName, string $metaDataKeyword) : self
1534
    {
1535
        /* It's possible that the field is already projected without the $meta
1536
         * operator. We'll assume that the user knows what they're doing in that
1537
         * case and will not attempt to override the projection.
1538
         */
1539 2
        if (! isset($this->query['select'][$fieldName])) {
1540 1
            $this->selectMeta($fieldName, $metaDataKeyword);
1541
        }
1542
1543 2
        $this->query['sort'][$fieldName] = ['$meta' => $metaDataKeyword];
1544
1545 2
        return $this;
1546
    }
1547
1548
    /**
1549
     * Specify $text criteria for the current field.
1550
     *
1551
     * The $language option may be set with {@link Builder::language()}.
1552
     *
1553
     * @see Expr::text()
1554
     * @see http://docs.mongodb.org/master/reference/operator/query/text/
1555
     */
1556 1
    public function text(string $search) : self
1557
    {
1558 1
        $this->expr->text($search);
1559 1
        return $this;
1560
    }
1561
1562
    /**
1563
     * Specify $type criteria for the current field.
1564
     *
1565
     * @see Expr::type()
1566
     * @see http://docs.mongodb.org/manual/reference/operator/type/
1567
     *
1568
     * @param int|string $type
1569
     */
1570 2
    public function type($type) : self
1571
    {
1572 2
        $this->expr->type($type);
1573 2
        return $this;
1574
    }
1575
1576
    /**
1577
     * Unset the current field.
1578
     *
1579
     * The field will be removed from the document (not set to null).
1580
     *
1581
     * @see Expr::unsetField()
1582
     * @see http://docs.mongodb.org/manual/reference/operator/unset/
1583
     */
1584 4
    public function unsetField() : self
1585
    {
1586 4
        $this->expr->unsetField();
1587 4
        return $this;
1588
    }
1589
1590 23
    public function updateOne(?string $documentName = null) : self
1591
    {
1592 23
        $this->setDocumentName($documentName);
1593 23
        $this->query['type']     = Query::TYPE_UPDATE;
1594 23
        $this->query['multiple'] = false;
1595
1596 23
        return $this;
1597
    }
1598
1599 3
    public function updateMany(?string $documentName = null) : self
1600
    {
1601 3
        $this->setDocumentName($documentName);
1602 3
        $this->query['type']     = Query::TYPE_UPDATE;
1603 3
        $this->query['multiple'] = true;
1604
1605 3
        return $this;
1606
    }
1607
1608
    /**
1609
     * Set the "upsert" option for an update or findAndUpdate query.
1610
     */
1611 7
    public function upsert(bool $bool = true) : self
1612
    {
1613 7
        $this->query['upsert'] = $bool;
1614 7
        return $this;
1615
    }
1616
1617
    /**
1618
     * Specify a JavaScript expression to use for matching documents.
1619
     *
1620
     * @see Expr::where()
1621
     * @see http://docs.mongodb.org/manual/reference/operator/where/
1622
     *
1623
     * @param string|Javascript $javascript
1624
     */
1625 3
    public function where($javascript) : self
1626
    {
1627 3
        $this->expr->where($javascript);
1628 3
        return $this;
1629
    }
1630
1631
    /**
1632
     * Get Discriminator Values
1633
     *
1634
     * @param string[] $classNames
1635
     *
1636
     * @throws InvalidArgumentException If the number of found collections > 1.
1637
     */
1638 2
    private function getDiscriminatorValues($classNames) : array
1639
    {
1640 2
        $discriminatorValues = [];
1641 2
        $collections         = [];
1642 2
        foreach ($classNames as $className) {
1643 2
            $class                 = $this->dm->getClassMetadata($className);
1644 2
            $discriminatorValues[] = $class->discriminatorValue;
0 ignored issues
show
Accessing discriminatorValue on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1645 2
            $key                   = $this->dm->getDocumentDatabase($className)->getDatabaseName() . '.' . $class->getCollection();
1646 2
            $collections[$key]     = $key;
1647
        }
1648 2
        if (count($collections) > 1) {
1649 1
            throw new InvalidArgumentException('Documents involved are not all mapped to the same database collection.');
1650
        }
1651 1
        return $discriminatorValues;
1652
    }
1653
1654
    /**
1655
     * @param string[]|string|null $documentName an array of document names or just one.
1656
     */
1657 285
    private function setDocumentName($documentName)
1658
    {
1659 285
        if (is_array($documentName)) {
1660 2
            $documentNames = $documentName;
1661 2
            $documentName  = $documentNames[0];
1662
1663 2
            $metadata            = $this->dm->getClassMetadata($documentName);
1664 2
            $discriminatorField  = $metadata->discriminatorField ?? ClassMetadata::DEFAULT_DISCRIMINATOR_FIELD;
0 ignored issues
show
Accessing discriminatorField on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1665 2
            $discriminatorValues = $this->getDiscriminatorValues($documentNames);
1666
1667
            // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
1668 1
            if ($metadata->defaultDiscriminatorValue && in_array($metadata->defaultDiscriminatorValue, $discriminatorValues)) {
0 ignored issues
show
Accessing defaultDiscriminatorValue on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1669 1
                $discriminatorValues[] = null;
1670
            }
1671
1672 1
            $this->field($discriminatorField)->in($discriminatorValues);
1673
        }
1674
1675 284
        if ($documentName === null) {
1676 41
            return;
1677
        }
1678
1679 284
        $this->collection = $this->dm->getDocumentCollection($documentName);
1680 284
        $this->class      = $this->dm->getClassMetadata($documentName);
1681
1682
        // Expr also needs to know
1683 284
        $this->expr->setClassMetadata($this->class);
0 ignored issues
show
$this->class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1684 284
    }
1685
}
1686