Completed
Push — master ( 26ecbc...8c0c5d )
by Maciej
14s
created

lib/Doctrine/ODM/MongoDB/Query/Builder.php (2 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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->class->readPreference of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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;
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;
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
Bug Best Practice introduced by
The expression $metadata->defaultDiscriminatorValue of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
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);
1684 284
    }
1685
}
1686