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

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

Labels
Severity

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 Doctrine\ODM\MongoDB\Mapping\MappingException;
11
use GeoJson\Geometry\Geometry;
12
use GeoJson\Geometry\Point;
13
use InvalidArgumentException;
14
use LogicException;
15
use MongoDB\BSON\Binary;
16
use MongoDB\BSON\Javascript;
17
use function array_key_exists;
18
use function array_map;
19
use function array_merge;
20
use function array_values;
21
use function assert;
22
use function explode;
23
use function func_get_args;
24
use function in_array;
25
use function is_array;
26
use function is_string;
27
use function key;
28
use function sprintf;
29
use function strpos;
30
use function strtolower;
31
32
/**
33
 * Query expression builder for ODM.
34
 */
35
class Expr
36
{
37
    /**
38
     * The query criteria array.
39
     *
40
     * @var array
41
     */
42
    private $query = [];
43
44
    /**
45
     * The "new object" array containing either a full document or a number of
46
     * atomic update operators.
47
     *
48
     * @see docs.mongodb.org/manual/reference/method/db.collection.update/#update-parameter
49
     *
50
     * @var array
51
     */
52
    private $newObj = [];
53
54
    /**
55
     * The current field we are operating on.
56
     *
57
     * @var string|null
58
     */
59
    private $currentField;
60
61
    /**
62
     * The DocumentManager instance for this query
63
     *
64
     * @var DocumentManager
65
     */
66
    private $dm;
67
68
    /**
69
     * The ClassMetadata instance for the document being queried
70
     *
71
     * @var ClassMetadata
72
     */
73
    private $class;
74
75 392
    public function __construct(DocumentManager $dm)
76
    {
77 392
        $this->dm = $dm;
78 392
    }
79
80
    /**
81
     * Add one or more $and clauses to the current query.
82
     *
83
     * @see Builder::addAnd()
84
     * @see http://docs.mongodb.org/manual/reference/operator/and/
85
     *
86
     * @param array|Expr $expression
87
     * @param array|Expr ...$expressions
88
     */
89 3
    public function addAnd($expression, ...$expressions) : self
90
    {
91 3
        if (! isset($this->query['$and'])) {
92 3
            $this->query['$and'] = [];
93
        }
94
95 3
        $this->query['$and'] = array_merge(
96 3
            $this->query['$and'],
97 3
            array_map(
98
                static function ($expression) {
99 3
                    return $expression instanceof Expr ? $expression->getQuery() : $expression;
100 3
                },
101 3
                func_get_args()
102
            )
103
        );
104
105 3
        return $this;
106
    }
107
108
    /**
109
     * Add one or more $nor clauses to the current query.
110
     *
111
     * @see Builder::addNor()
112
     * @see http://docs.mongodb.org/manual/reference/operator/nor/
113
     *
114
     * @param array|Expr $expression
115
     * @param array|Expr ...$expressions
116
     */
117 1
    public function addNor($expression, ...$expressions) : self
118
    {
119 1
        if (! isset($this->query['$nor'])) {
120 1
            $this->query['$nor'] = [];
121
        }
122
123 1
        $this->query['$nor'] = array_merge(
124 1
            $this->query['$nor'],
125
            array_map(static function ($expression) {
126 1
                return $expression instanceof Expr ? $expression->getQuery() : $expression;
127 1
            }, func_get_args())
128
        );
129
130 1
        return $this;
131
    }
132
133
    /**
134
     * Add one or more $or clauses to the current query.
135
     *
136
     * @see Builder::addOr()
137
     * @see http://docs.mongodb.org/manual/reference/operator/or/
138
     *
139
     * @param array|Expr $expression
140
     * @param array|Expr ...$expressions
141
     */
142 5
    public function addOr($expression, ...$expressions) : self
143
    {
144 5
        if (! isset($this->query['$or'])) {
145 5
            $this->query['$or'] = [];
146
        }
147
148 5
        $this->query['$or'] = array_merge(
149 5
            $this->query['$or'],
150
            array_map(static function ($expression) {
151 5
                return $expression instanceof Expr ? $expression->getQuery() : $expression;
152 5
            }, func_get_args())
153
        );
154
155 5
        return $this;
156
    }
157
158
    /**
159
     * Append one or more values to the current array field only if they do not
160
     * already exist in the array.
161
     *
162
     * If the field does not exist, it will be set to an array containing the
163
     * unique value(s) in the argument. If the field is not an array, the query
164
     * will yield an error.
165
     *
166
     * Multiple values may be specified by provided an Expr object and using
167
     * {@link Expr::each()}.
168
     *
169
     * @see Builder::addToSet()
170
     * @see http://docs.mongodb.org/manual/reference/operator/addToSet/
171
     * @see http://docs.mongodb.org/manual/reference/operator/each/
172
     *
173
     * @param mixed|Expr $valueOrExpression
174
     */
175 5
    public function addToSet($valueOrExpression) : self
176
    {
177 5
        if ($valueOrExpression instanceof Expr) {
178 1
            $valueOrExpression = $valueOrExpression->getQuery();
179
        }
180
181 5
        $this->requiresCurrentField();
182 5
        $this->newObj['$addToSet'][$this->currentField] = $valueOrExpression;
183 5
        return $this;
184
    }
185
186
    /**
187
     * Specify $all criteria for the current field.
188
     *
189
     * @see Builder::all()
190
     * @see http://docs.mongodb.org/manual/reference/operator/all/
191
     */
192 2
    public function all(array $values) : self
193
    {
194 2
        return $this->operator('$all', $values);
195
    }
196
197
    /**
198
     * Apply a bitwise operation on the current field
199
     *
200
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
201
     */
202 3
    protected function bit(string $operator, int $value) : self
203
    {
204 3
        $this->requiresCurrentField();
205 3
        $this->newObj['$bit'][$this->currentField][$operator] = $value;
206 3
        return $this;
207
    }
208
209
    /**
210
     * Apply a bitwise and operation on the current field.
211
     *
212
     * @see Builder::bitAnd()
213
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
214
     */
215 1
    public function bitAnd(int $value) : self
216
    {
217 1
        return $this->bit('and', $value);
218
    }
219
220
    /**
221
     * Apply a bitwise or operation on the current field.
222
     *
223
     * @see Builder::bitOr()
224
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
225
     */
226 1
    public function bitOr(int $value) : self
227
    {
228 1
        return $this->bit('or', $value);
229
    }
230
231
    /**
232
     * Matches documents where all of the bit positions given by the query are
233
     * clear.
234
     *
235
     * @see Builder::bitsAllClear()
236
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllClear/
237
     *
238
     * @param int|array|Binary $value
239
     */
240
    public function bitsAllClear($value) : self
241
    {
242
        $this->requiresCurrentField();
243
        return $this->operator('$bitsAllClear', $value);
244
    }
245
246
    /**
247
     * Matches documents where all of the bit positions given by the query are
248
     * set.
249
     *
250
     * @see Builder::bitsAllSet()
251
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllSet/
252
     *
253
     * @param int|array|Binary $value
254
     */
255
    public function bitsAllSet($value) : self
256
    {
257
        $this->requiresCurrentField();
258
        return $this->operator('$bitsAllSet', $value);
259
    }
260
261
    /**
262
     * Matches documents where any of the bit positions given by the query are
263
     * clear.
264
     *
265
     * @see Builder::bitsAnyClear()
266
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnyClear/
267
     *
268
     * @param int|array|Binary $value
269
     */
270
    public function bitsAnyClear($value) : self
271
    {
272
        $this->requiresCurrentField();
273
        return $this->operator('$bitsAnyClear', $value);
274
    }
275
276
    /**
277
     * Matches documents where any of the bit positions given by the query are
278
     * set.
279
     *
280
     * @see Builder::bitsAnySet()
281
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnySet/
282
     *
283
     * @param int|array|Binary $value
284
     */
285
    public function bitsAnySet($value) : self
286
    {
287
        $this->requiresCurrentField();
288
        return $this->operator('$bitsAnySet', $value);
289
    }
290
291
    /**
292
     * Apply a bitwise xor operation on the current field.
293
     *
294
     * @see Builder::bitXor()
295
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
296
     */
297 1
    public function bitXor(int $value) : self
298
    {
299 1
        return $this->bit('xor', $value);
300
    }
301
302
    /**
303
     * A boolean flag to enable or disable case sensitive search for $text
304
     * criteria.
305
     *
306
     * This method must be called after text().
307
     *
308
     * @see Builder::caseSensitive()
309
     * @see http://docs.mongodb.org/manual/reference/operator/text/
310
     *
311
     * @throws BadMethodCallException If the query does not already have $text criteria.
312
     */
313 3
    public function caseSensitive(bool $caseSensitive) : self
314
    {
315 3
        if (! isset($this->query['$text'])) {
316 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
317
        }
318
319
        // Remove caseSensitive option to keep support for older database versions
320 2
        if ($caseSensitive) {
321 2
            $this->query['$text']['$caseSensitive'] = true;
322 1
        } elseif (isset($this->query['$text']['$caseSensitive'])) {
323 1
            unset($this->query['$text']['$caseSensitive']);
324
        }
325
326 2
        return $this;
327
    }
328
329
    /**
330
     * Associates a comment to any expression taking a query predicate.
331
     *
332
     * @see Builder::comment()
333
     * @see http://docs.mongodb.org/manual/reference/operator/query/comment/
334
     */
335
    public function comment(string $comment) : self
336
    {
337
        $this->query['$comment'] = $comment;
338
        return $this;
339
    }
340
341
    /**
342
     * Sets the value of the current field to the current date, either as a date or a timestamp.
343
     *
344
     * @see Builder::currentDate()
345
     * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
346
     *
347
     * @throws InvalidArgumentException If an invalid type is given.
348
     */
349 3
    public function currentDate(string $type = 'date') : self
350
    {
351 3
        if (! in_array($type, ['date', 'timestamp'])) {
352 1
            throw new InvalidArgumentException('Type for currentDate operator must be date or timestamp.');
353
        }
354
355 2
        $this->requiresCurrentField();
356 2
        $this->newObj['$currentDate'][$this->currentField]['$type'] = $type;
357 2
        return $this;
358
    }
359
360
    /**
361
     * A boolean flag to enable or disable diacritic sensitive search for $text
362
     * criteria.
363
     *
364
     * This method must be called after text().
365
     *
366
     * @see Builder::diacriticSensitive()
367
     * @see http://docs.mongodb.org/manual/reference/operator/text/
368
     *
369
     * @throws BadMethodCallException If the query does not already have $text criteria.
370
     */
371 3
    public function diacriticSensitive(bool $diacriticSensitive) : self
372
    {
373 3
        if (! isset($this->query['$text'])) {
374 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
375
        }
376
377
        // Remove diacriticSensitive option to keep support for older database versions
378 2
        if ($diacriticSensitive) {
379 2
            $this->query['$text']['$diacriticSensitive'] = true;
380 1
        } elseif (isset($this->query['$text']['$diacriticSensitive'])) {
381 1
            unset($this->query['$text']['$diacriticSensitive']);
382
        }
383
384 2
        return $this;
385
    }
386
387
    /**
388
     * Add $each criteria to the expression for a $push operation.
389
     *
390
     * @see Expr::push()
391
     * @see http://docs.mongodb.org/manual/reference/operator/each/
392
     */
393 4
    public function each(array $values) : self
394
    {
395 4
        return $this->operator('$each', $values);
396
    }
397
398
    /**
399
     * Specify $elemMatch criteria for the current field.
400
     *
401
     * @see Builder::elemMatch()
402
     * @see http://docs.mongodb.org/manual/reference/operator/elemMatch/
403
     *
404
     * @param array|Expr $expression
405
     */
406 4
    public function elemMatch($expression) : self
407
    {
408 4
        return $this->operator('$elemMatch', $expression instanceof Expr ? $expression->getQuery() : $expression);
409
    }
410
411
    /**
412
     * Specify an equality match for the current field.
413
     *
414
     * @see Builder::equals()
415
     *
416
     * @param mixed $value
417
     */
418 104
    public function equals($value) : self
419
    {
420 104
        if ($this->currentField) {
421 103
            $this->query[$this->currentField] = $value;
422
        } else {
423 1
            $this->query = $value;
424
        }
425 104
        return $this;
426
    }
427
428
    /**
429
     * Specify $exists criteria for the current field.
430
     *
431
     * @see Builder::exists()
432
     * @see http://docs.mongodb.org/manual/reference/operator/exists/
433
     */
434 5
    public function exists(bool $bool) : self
435
    {
436 5
        return $this->operator('$exists', $bool);
437
    }
438
439
    /**
440
     * Set the current field for building the expression.
441
     *
442
     * @see Builder::field()
443
     */
444 192
    public function field(string $field) : self
445
    {
446 192
        $this->currentField = $field;
447 192
        return $this;
448
    }
449
450
    /**
451
     * Add $geoIntersects criteria with a GeoJSON geometry to the expression.
452
     *
453
     * The geometry parameter GeoJSON object or an array corresponding to the
454
     * geometry's JSON representation.
455
     *
456
     * @see Builder::geoIntersects()
457
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
458
     *
459
     * @param array|Geometry $geometry
460
     */
461 2
    public function geoIntersects($geometry) : self
462
    {
463 2
        if ($geometry instanceof Geometry) {
0 ignored issues
show
The class GeoJson\Geometry\Geometry does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
464 1
            $geometry = $geometry->jsonSerialize();
465
        }
466
467 2
        return $this->operator('$geoIntersects', ['$geometry' => $geometry]);
468
    }
469
470
    /**
471
     * Add $geoWithin criteria with a GeoJSON geometry to the expression.
472
     *
473
     * The geometry parameter GeoJSON object or an array corresponding to the
474
     * geometry's JSON representation.
475
     *
476
     * @see Builder::geoWithin()
477
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
478
     *
479
     * @param array|Geometry $geometry
480
     */
481 2
    public function geoWithin($geometry) : self
482
    {
483 2
        if ($geometry instanceof Geometry) {
0 ignored issues
show
The class GeoJson\Geometry\Geometry does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
484 1
            $geometry = $geometry->jsonSerialize();
485
        }
486
487 2
        return $this->operator('$geoWithin', ['$geometry' => $geometry]);
488
    }
489
490
    /**
491
     * Add $geoWithin criteria with a $box shape to the expression.
492
     *
493
     * A rectangular polygon will be constructed from a pair of coordinates
494
     * corresponding to the bottom left and top right corners.
495
     *
496
     * Note: the $box operator only supports legacy coordinate pairs and 2d
497
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
498
     *
499
     * @see Builder::geoWithinBox()
500
     * @see http://docs.mongodb.org/manual/reference/operator/box/
501
     */
502 1
    public function geoWithinBox(float $x1, float $y1, float $x2, float $y2) : self
503
    {
504 1
        $shape = ['$box' => [[$x1, $y1], [$x2, $y2]]];
505
506 1
        return $this->operator('$geoWithin', $shape);
507
    }
508
509
    /**
510
     * Add $geoWithin criteria with a $center shape to the expression.
511
     *
512
     * Note: the $center operator only supports legacy coordinate pairs and 2d
513
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
514
     *
515
     * @see Builider::geoWithinCenter()
516
     * @see http://docs.mongodb.org/manual/reference/operator/center/
517
     */
518 1
    public function geoWithinCenter(float $x, float $y, float $radius) : self
519
    {
520 1
        $shape = ['$center' => [[$x, $y], $radius]];
521
522 1
        return $this->operator('$geoWithin', $shape);
523
    }
524
525
    /**
526
     * Add $geoWithin criteria with a $centerSphere shape to the expression.
527
     *
528
     * Note: the $centerSphere operator supports both 2d and 2dsphere indexes.
529
     *
530
     * @see Builder::geoWithinCenterSphere()
531
     * @see http://docs.mongodb.org/manual/reference/operator/centerSphere/
532
     */
533 1
    public function geoWithinCenterSphere(float $x, float $y, float $radius) : self
534
    {
535 1
        $shape = ['$centerSphere' => [[$x, $y], $radius]];
536
537 1
        return $this->operator('$geoWithin', $shape);
538
    }
539
540
    /**
541
     * Add $geoWithin criteria with a $polygon shape to the expression.
542
     *
543
     * Point coordinates are in x, y order (easting, northing for projected
544
     * coordinates, longitude, latitude for geographic coordinates).
545
     *
546
     * The last point coordinate is implicitly connected with the first.
547
     *
548
     * Note: the $polygon operator only supports legacy coordinate pairs and 2d
549
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
550
     *
551
     * @see Builder::geoWithinPolygon()
552
     * @see http://docs.mongodb.org/manual/reference/operator/polygon/
553
     *
554
     * @param array $point1    First point of the polygon
555
     * @param array $point2    Second point of the polygon
556
     * @param array $point3    Third point of the polygon
557
     * @param array ...$points Additional points of the polygon
558
     *
559
     * @throws InvalidArgumentException If less than three points are given.
560
     */
561 1
    public function geoWithinPolygon($point1, $point2, $point3, ...$points) : self
562
    {
563 1
        $shape = ['$polygon' => func_get_args()];
564
565 1
        return $this->operator('$geoWithin', $shape);
566
    }
567
568
    /**
569
     * Return the current field.
570
     */
571 2
    public function getCurrentField() : ?string
572
    {
573 2
        return $this->currentField;
574
    }
575
576
    /**
577
     * Gets prepared newObj part of expression.
578
     */
579 177
    public function getNewObj() : array
580
    {
581 177
        return $this->dm->getUnitOfWork()
582 177
            ->getDocumentPersister($this->class->name)
583 177
            ->prepareQueryOrNewObj($this->newObj, true);
584
    }
585
586
    /**
587
     * Gets prepared query part of expression.
588
     */
589 253
    public function getQuery() : array
590
    {
591 253
        return $this->dm->getUnitOfWork()
592 253
            ->getDocumentPersister($this->class->name)
593 253
            ->prepareQueryOrNewObj($this->query);
594
    }
595
596
    /**
597
     * Specify $gt criteria for the current field.
598
     *
599
     * @see Builder::gt()
600
     * @see http://docs.mongodb.org/manual/reference/operator/gt/
601
     *
602
     * @param mixed $value
603
     */
604 2
    public function gt($value) : self
605
    {
606 2
        return $this->operator('$gt', $value);
607
    }
608
609
    /**
610
     * Specify $gte criteria for the current field.
611
     *
612
     * @see Builder::gte()
613
     * @see http://docs.mongodb.org/manual/reference/operator/gte/
614
     *
615
     * @param mixed $value
616
     */
617 2
    public function gte($value) : self
618
    {
619 2
        return $this->operator('$gte', $value);
620
    }
621
622
    /**
623
     * Specify $in criteria for the current field.
624
     *
625
     * @see Builder::in()
626
     * @see http://docs.mongodb.org/manual/reference/operator/in/
627
     */
628 31
    public function in(array $values) : self
629
    {
630 31
        return $this->operator('$in', array_values($values));
631
    }
632
633
    /**
634
     * Increment the current field.
635
     *
636
     * If the field does not exist, it will be set to this value.
637
     *
638
     * @see Builder::inc()
639
     * @see http://docs.mongodb.org/manual/reference/operator/inc/
640
     *
641
     * @param float|int $value
642
     */
643 5
    public function inc($value) : self
644
    {
645 5
        $this->requiresCurrentField();
646 5
        $this->newObj['$inc'][$this->currentField] = $value;
647 5
        return $this;
648
    }
649
650
    /**
651
     * Checks that the current field includes a reference to the supplied document.
652
     */
653 6
    public function includesReferenceTo(object $document) : self
654
    {
655 6
        $this->requiresCurrentField();
656 6
        $mapping   = $this->getReferenceMapping();
657 4
        $reference = $this->dm->createReference($document, $mapping);
658 4
        $storeAs   = $mapping['storeAs'] ?? null;
659 4
        $keys      = [];
660
661
        switch ($storeAs) {
662 4
            case ClassMetadata::REFERENCE_STORE_AS_ID:
663 2
                $this->query[$mapping['name']] = $reference;
664 2
                return $this;
665
                break;
666
667 3
            case ClassMetadata::REFERENCE_STORE_AS_REF:
668
                $keys = ['id' => true];
669
                break;
670
671 3
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
672 1
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
673 3
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
674
675 3
                if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
676 2
                    unset($keys['$db']);
677
                }
678
679 3
                if (isset($mapping['targetDocument'])) {
680 1
                    unset($keys['$ref'], $keys['$db']);
681
                }
682 3
                break;
683
684
            default:
685
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
686
        }
687
688 3
        foreach ($keys as $key => $value) {
689 3
            $this->query[$mapping['name']]['$elemMatch'][$key] = $reference[$key];
690
        }
691
692 3
        return $this;
693
    }
694
695
    /**
696
     * Set the $language option for $text criteria.
697
     *
698
     * This method must be called after text().
699
     *
700
     * @see Builder::language()
701
     * @see http://docs.mongodb.org/manual/reference/operator/text/
702
     *
703
     * @throws BadMethodCallException If the query does not already have $text criteria.
704
     */
705 2
    public function language(string $language) : self
706
    {
707 2
        if (! isset($this->query['$text'])) {
708 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
709
        }
710
711 1
        $this->query['$text']['$language'] = $language;
712
713 1
        return $this;
714
    }
715
716
    /**
717
     * Specify $lt criteria for the current field.
718
     *
719
     * @see Builder::lte()
720
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
721
     *
722
     * @param mixed $value
723
     */
724 4
    public function lt($value) : self
725
    {
726 4
        return $this->operator('$lt', $value);
727
    }
728
729
    /**
730
     * Specify $lte criteria for the current field.
731
     *
732
     * @see Builder::lte()
733
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
734
     *
735
     * @param mixed $value
736
     */
737 2
    public function lte($value) : self
738
    {
739 2
        return $this->operator('$lte', $value);
740
    }
741
742
    /**
743
     * Updates the value of the field to a specified value if the specified value is greater than the current value of the field.
744
     *
745
     * @see Builder::max()
746
     * @see http://docs.mongodb.org/manual/reference/operator/update/max/
747
     *
748
     * @param mixed $value
749
     */
750
    public function max($value) : self
751
    {
752
        $this->requiresCurrentField();
753
        $this->newObj['$max'][$this->currentField] = $value;
754
        return $this;
755
    }
756
757
    /**
758
     * Updates the value of the field to a specified value if the specified value is less than the current value of the field.
759
     *
760
     * @see Builder::min()
761
     * @see http://docs.mongodb.org/manual/reference/operator/update/min/
762
     *
763
     * @param mixed $value
764
     */
765
    public function min($value) : self
766
    {
767
        $this->requiresCurrentField();
768
        $this->newObj['$min'][$this->currentField] = $value;
769
        return $this;
770
    }
771
772
    /**
773
     * Specify $mod criteria for the current field.
774
     *
775
     * @see Builder::mod()
776
     * @see http://docs.mongodb.org/manual/reference/operator/mod/
777
     *
778
     * @param float|int $divisor
779
     * @param float|int $remainder
780
     */
781
    public function mod($divisor, $remainder = 0) : self
782
    {
783
        return $this->operator('$mod', [$divisor, $remainder]);
784
    }
785
786
    /**
787
     * Multiply the current field.
788
     *
789
     * If the field does not exist, it will be set to 0.
790
     *
791
     * @see Builder::mul()
792
     * @see http://docs.mongodb.org/manual/reference/operator/mul/
793
     *
794
     * @param float|int $value
795
     */
796
    public function mul($value) : self
797
    {
798
        $this->requiresCurrentField();
799
        $this->newObj['$mul'][$this->currentField] = $value;
800
        return $this;
801
    }
802
803
    /**
804
     * Add $near criteria to the expression.
805
     *
806
     * A GeoJSON point may be provided as the first and only argument for
807
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
808
     * an array corresponding to the point's JSON representation.
809
     *
810
     * @see Builder::near()
811
     * @see http://docs.mongodb.org/manual/reference/operator/near/
812
     *
813
     * @param float|array|Point $x
814
     * @param float             $y
815
     */
816 3
    public function near($x, $y = null) : self
817
    {
818 3
        if ($x instanceof Point) {
0 ignored issues
show
The class GeoJson\Geometry\Point does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
819 1
            $x = $x->jsonSerialize();
820
        }
821
822 3
        if (is_array($x)) {
823 2
            return $this->operator('$near', ['$geometry' => $x]);
824
        }
825
826 1
        return $this->operator('$near', [$x, $y]);
827
    }
828
829
    /**
830
     * Add $nearSphere criteria to the expression.
831
     *
832
     * A GeoJSON point may be provided as the first and only argument for
833
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
834
     * an array corresponding to the point's JSON representation.
835
     *
836
     * @see Builder::nearSphere()
837
     * @see http://docs.mongodb.org/manual/reference/operator/nearSphere/
838
     *
839
     * @param float|array|Point $x
840
     * @param float             $y
841
     */
842 3
    public function nearSphere($x, $y = null) : self
843
    {
844 3
        if ($x instanceof Point) {
0 ignored issues
show
The class GeoJson\Geometry\Point does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
845 1
            $x = $x->jsonSerialize();
846
        }
847
848 3
        if (is_array($x)) {
849 2
            return $this->operator('$nearSphere', ['$geometry' => $x]);
850
        }
851
852 1
        return $this->operator('$nearSphere', [$x, $y]);
853
    }
854
855
    /**
856
     * Negates an expression for the current field.
857
     *
858
     * @see Builder::not()
859
     * @see http://docs.mongodb.org/manual/reference/operator/not/
860
     *
861
     * @param array|Expr $expression
862
     */
863 2
    public function not($expression) : self
864
    {
865 2
        return $this->operator('$not', $expression instanceof Expr ? $expression->getQuery() : $expression);
866
    }
867
868
    /**
869
     * Specify $ne criteria for the current field.
870
     *
871
     * @see Builder::notEqual()
872
     * @see http://docs.mongodb.org/manual/reference/operator/ne/
873
     *
874
     * @param mixed $value
875
     */
876 6
    public function notEqual($value) : self
877
    {
878 6
        return $this->operator('$ne', $value);
879
    }
880
881
    /**
882
     * Specify $nin criteria for the current field.
883
     *
884
     * @see Builder::notIn()
885
     * @see http://docs.mongodb.org/manual/reference/operator/nin/
886
     */
887 6
    public function notIn(array $values) : self
888
    {
889 6
        return $this->operator('$nin', array_values($values));
890
    }
891
892
    /**
893
     * Defines an operator and value on the expression.
894
     *
895
     * If there is a current field, the operator will be set on it; otherwise,
896
     * the operator is set at the top level of the query.
897
     *
898
     * @param mixed $value
899
     */
900 81
    public function operator(string $operator, $value) : self
901
    {
902 81
        $this->wrapEqualityCriteria();
903
904 81
        if ($this->currentField) {
905 56
            $this->query[$this->currentField][$operator] = $value;
906
        } else {
907 27
            $this->query[$operator] = $value;
908
        }
909 81
        return $this;
910
    }
911
912
    /**
913
     * Remove the first element from the current array field.
914
     *
915
     * @see Builder::popFirst()
916
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
917
     */
918 2
    public function popFirst() : self
919
    {
920 2
        $this->requiresCurrentField();
921 2
        $this->newObj['$pop'][$this->currentField] = -1;
922 2
        return $this;
923
    }
924
925
    /**
926
     * Remove the last element from the current array field.
927
     *
928
     * @see Builder::popLast()
929
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
930
     */
931 1
    public function popLast() : self
932
    {
933 1
        $this->requiresCurrentField();
934 1
        $this->newObj['$pop'][$this->currentField] = 1;
935 1
        return $this;
936
    }
937
938
    /**
939
     * Add $position criteria to the expression for a $push operation.
940
     *
941
     * This is useful in conjunction with {@link Expr::each()} for a
942
     * {@link Expr::push()} operation.
943
     *
944
     * @see http://docs.mongodb.org/manual/reference/operator/update/position/
945
     */
946 1
    public function position(int $position) : self
947
    {
948 1
        return $this->operator('$position', $position);
949
    }
950
951
    /**
952
     * Remove all elements matching the given value or expression from the
953
     * current array field.
954
     *
955
     * @see Builder::pull()
956
     * @see http://docs.mongodb.org/manual/reference/operator/pull/
957
     *
958
     * @param mixed|Expr $valueOrExpression
959
     */
960 2
    public function pull($valueOrExpression) : self
961
    {
962 2
        if ($valueOrExpression instanceof Expr) {
963 1
            $valueOrExpression = $valueOrExpression->getQuery();
964
        }
965
966 2
        $this->requiresCurrentField();
967 2
        $this->newObj['$pull'][$this->currentField] = $valueOrExpression;
968 2
        return $this;
969
    }
970
971
    /**
972
     * Remove all elements matching any of the given values from the current
973
     * array field.
974
     *
975
     * @see Builder::pullAll()
976
     * @see http://docs.mongodb.org/manual/reference/operator/pullAll/
977
     */
978
    public function pullAll(array $values) : self
979
    {
980
        $this->requiresCurrentField();
981
        $this->newObj['$pullAll'][$this->currentField] = $values;
982
        return $this;
983
    }
984
985
    /**
986
     * Append one or more values to the current array field.
987
     *
988
     * If the field does not exist, it will be set to an array containing the
989
     * value(s) in the argument. If the field is not an array, the query
990
     * will yield an error.
991
     *
992
     * Multiple values may be specified by providing an Expr object and using
993
     * {@link Expr::each()}. {@link Expr::slice()} and {@link Expr::sort()} may
994
     * also be used to limit and order array elements, respectively.
995
     *
996
     * @see Builder::push()
997
     * @see http://docs.mongodb.org/manual/reference/operator/push/
998
     * @see http://docs.mongodb.org/manual/reference/operator/each/
999
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1000
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1001
     *
1002
     * @param mixed|Expr $valueOrExpression
1003
     */
1004 8
    public function push($valueOrExpression) : self
1005
    {
1006 8
        if ($valueOrExpression instanceof Expr) {
1007 3
            $valueOrExpression = array_merge(
1008 3
                ['$each' => []],
1009 3
                $valueOrExpression->getQuery()
1010
            );
1011
        }
1012
1013 8
        $this->requiresCurrentField();
1014 8
        $this->newObj['$push'][$this->currentField] = $valueOrExpression;
1015 8
        return $this;
1016
    }
1017
1018
    /**
1019
     * Specify $gte and $lt criteria for the current field.
1020
     *
1021
     * This method is shorthand for specifying $gte criteria on the lower bound
1022
     * and $lt criteria on the upper bound. The upper bound is not inclusive.
1023
     *
1024
     * @see Builder::range()
1025
     *
1026
     * @param mixed $start
1027
     * @param mixed $end
1028
     */
1029 2
    public function range($start, $end) : self
1030
    {
1031 2
        return $this->operator('$gte', $start)->operator('$lt', $end);
1032
    }
1033
1034
    /**
1035
     * Checks that the value of the current field is a reference to the supplied document.
1036
     */
1037 13
    public function references(object $document) : self
1038
    {
1039 13
        $this->requiresCurrentField();
1040 13
        $mapping   = $this->getReferenceMapping();
1041 11
        $reference = $this->dm->createReference($document, $mapping);
1042 11
        $storeAs   = $mapping['storeAs'] ?? null;
1043 11
        $keys      = [];
1044
1045
        switch ($storeAs) {
1046 11
            case ClassMetadata::REFERENCE_STORE_AS_ID:
1047 4
                $this->query[$mapping['name']] = $reference;
1048 4
                return $this;
1049
                break;
1050
1051 8
            case ClassMetadata::REFERENCE_STORE_AS_REF:
1052
                $keys = ['id' => true];
1053
                break;
1054
1055 8
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
1056 3
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
1057 8
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
1058
1059 8
                if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
1060 5
                    unset($keys['$db']);
1061
                }
1062
1063 8
                if (isset($mapping['targetDocument'])) {
1064 4
                    unset($keys['$ref'], $keys['$db']);
1065
                }
1066 8
                break;
1067
1068
            default:
1069
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
1070
        }
1071
1072 8
        foreach ($keys as $key => $value) {
1073 8
            $this->query[$mapping['name'] . '.' . $key] = $reference[$key];
1074
        }
1075
1076 8
        return $this;
1077
    }
1078
1079
    /**
1080
     * Rename the current field.
1081
     *
1082
     * @see Builder::rename()
1083
     * @see http://docs.mongodb.org/manual/reference/operator/rename/
1084
     */
1085
    public function rename(string $name) : self
1086
    {
1087
        $this->requiresCurrentField();
1088
        $this->newObj['$rename'][$this->currentField] = $name;
1089
        return $this;
1090
    }
1091
1092
    /**
1093
     * Set the current field to a value.
1094
     *
1095
     * This is only relevant for insert, update, or findAndUpdate queries. For
1096
     * update and findAndUpdate queries, the $atomic parameter will determine
1097
     * whether or not a $set operator is used.
1098
     *
1099
     * @see Builder::set()
1100
     * @see http://docs.mongodb.org/manual/reference/operator/set/
1101
     *
1102
     * @param mixed $value
1103
     */
1104 19
    public function set($value, bool $atomic = true) : self
1105
    {
1106 19
        $this->requiresCurrentField();
1107 19
        assert($this->currentField !== null);
1108
1109 19
        if ($atomic) {
1110 16
            $this->newObj['$set'][$this->currentField] = $value;
1111 16
            return $this;
1112
        }
1113
1114 3
        if (strpos($this->currentField, '.') === false) {
1115 2
            $this->newObj[$this->currentField] = $value;
1116 2
            return $this;
1117
        }
1118
1119 2
        $keys    = explode('.', $this->currentField);
1120 2
        $current = &$this->newObj;
1121 2
        foreach ($keys as $key) {
1122 2
            $current = &$current[$key];
1123
        }
1124 2
        $current = $value;
1125
1126 2
        return $this;
1127
    }
1128
1129
    /**
1130
     * Sets ClassMetadata for document being queried.
1131
     */
1132 390
    public function setClassMetadata(ClassMetadata $class) : void
1133
    {
1134 390
        $this->class = $class;
1135 390
    }
1136
1137
    /**
1138
     * Set the "new object".
1139
     *
1140
     * @see Builder::setNewObj()
1141
     */
1142
    public function setNewObj(array $newObj) : self
1143
    {
1144
        $this->newObj = $newObj;
1145
        return $this;
1146
    }
1147
1148
    /**
1149
     * Set the current field to the value if the document is inserted in an
1150
     * upsert operation.
1151
     *
1152
     * If an update operation with upsert: true results in an insert of a
1153
     * document, then $setOnInsert assigns the specified values to the fields in
1154
     * the document. If the update operation does not result in an insert,
1155
     * $setOnInsert does nothing.
1156
     *
1157
     * @see Builder::setOnInsert()
1158
     * @see https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/
1159
     *
1160
     * @param mixed $value
1161
     */
1162 1
    public function setOnInsert($value) : self
1163
    {
1164 1
        $this->requiresCurrentField();
1165 1
        $this->newObj['$setOnInsert'][$this->currentField] = $value;
1166
1167 1
        return $this;
1168
    }
1169
1170
    /**
1171
     * Set the query criteria.
1172
     *
1173
     * @see Builder::setQueryArray()
1174
     */
1175 18
    public function setQuery(array $query) : self
1176
    {
1177 18
        $this->query = $query;
1178 18
        return $this;
1179
    }
1180
1181
    /**
1182
     * Specify $size criteria for the current field.
1183
     *
1184
     * @see Builder::size()
1185
     * @see http://docs.mongodb.org/manual/reference/operator/size/
1186
     */
1187
    public function size(int $size) : self
1188
    {
1189
        return $this->operator('$size', $size);
1190
    }
1191
1192
    /**
1193
     * Add $slice criteria to the expression for a $push operation.
1194
     *
1195
     * This is useful in conjunction with {@link Expr::each()} for a
1196
     * {@link Expr::push()} operation. {@link Builder::selectSlice()} should be
1197
     * used for specifying $slice for a query projection.
1198
     *
1199
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1200
     */
1201 2
    public function slice(int $slice) : self
1202
    {
1203 2
        return $this->operator('$slice', $slice);
1204
    }
1205
1206
    /**
1207
     * Add $sort criteria to the expression for a $push operation.
1208
     *
1209
     * If sorting by multiple fields, the first argument should be an array of
1210
     * field name (key) and order (value) pairs.
1211
     *
1212
     * This is useful in conjunction with {@link Expr::each()} for a
1213
     * {@link Expr::push()} operation. {@link Builder::sort()} should be used to
1214
     * sort the results of a query.
1215
     *
1216
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1217
     *
1218
     * @param array|string $fieldName Field name or array of field/order pairs
1219
     * @param int|string   $order     Field order (if one field is specified)
1220
     */
1221 2
    public function sort($fieldName, $order = null) : self
1222
    {
1223 2
        $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order];
1224
1225
        return $this->operator('$sort', array_map(function ($order) {
1226 2
            return $this->normalizeSortOrder($order);
1227 2
        }, $fields));
1228
    }
1229
1230
    /**
1231
     * Specify $text criteria for the current query.
1232
     *
1233
     * The $language option may be set with {@link Expr::language()}.
1234
     *
1235
     * @see Builder::text()
1236
     * @see http://docs.mongodb.org/master/reference/operator/query/text/
1237
     */
1238 6
    public function text(string $search) : self
1239
    {
1240 6
        $this->query['$text'] = ['$search' => $search];
1241 6
        return $this;
1242
    }
1243
1244
    /**
1245
     * Specify $type criteria for the current field.
1246
     *
1247
     * @see Builder::type()
1248
     * @see http://docs.mongodb.org/manual/reference/operator/type/
1249
     *
1250
     * @param int|string $type
1251
     */
1252 1
    public function type($type) : self
1253
    {
1254 1
        return $this->operator('$type', $type);
1255
    }
1256
1257
    /**
1258
     * Unset the current field.
1259
     *
1260
     * The field will be removed from the document (not set to null).
1261
     *
1262
     * @see Builder::unsetField()
1263
     * @see http://docs.mongodb.org/manual/reference/operator/unset/
1264
     */
1265 3
    public function unsetField() : self
1266
    {
1267 3
        $this->requiresCurrentField();
1268 3
        $this->newObj['$unset'][$this->currentField] = 1;
1269 3
        return $this;
1270
    }
1271
1272
    /**
1273
     * Specify a JavaScript expression to use for matching documents.
1274
     *
1275
     * @see Builder::where()
1276
     * @see http://docs.mongodb.org/manual/reference/operator/where/
1277
     *
1278
     * @param string|Javascript $javascript
1279
     */
1280 3
    public function where($javascript) : self
1281
    {
1282 3
        $this->query['$where'] = $javascript;
1283 3
        return $this;
1284
    }
1285
1286
    /**
1287
     * Gets reference mapping for current field from current class or its descendants.
1288
     *
1289
     * @throws MappingException
1290
     */
1291 19
    private function getReferenceMapping() : array
1292
    {
1293 19
        $this->requiresCurrentField();
1294 19
        assert($this->currentField !== null);
1295
1296
        try {
1297 19
            return $this->class->getFieldMapping($this->currentField);
1298 6
        } catch (MappingException $e) {
1299 6
            if (empty($this->class->discriminatorMap)) {
1300
                throw $e;
1301
            }
1302 6
            $mapping = null;
1303 6
            $foundIn = null;
1304 6
            foreach ($this->class->discriminatorMap as $child) {
1305 6
                $childClass = $this->dm->getClassMetadata($child);
1306 6
                if (! $childClass->hasAssociation($this->currentField)) {
1307 4
                    continue;
1308
                }
1309
1310 4
                if ($foundIn !== null && $mapping !== null && $mapping !== $childClass->getFieldMapping($this->currentField)) {
1311 2
                    throw MappingException::referenceFieldConflict($this->currentField, $foundIn->name, $childClass->name);
1312
                }
1313 4
                $mapping = $childClass->getFieldMapping($this->currentField);
1314 4
                $foundIn = $childClass;
1315
            }
1316 4
            if ($mapping === null) {
1317 2
                throw MappingException::mappingNotFoundInClassNorDescendants($this->class->name, $this->currentField);
1318
            }
1319
1320 2
            return $mapping;
1321
        }
1322
    }
1323
1324
    /**
1325
     * @param int|string $order
1326
     */
1327 2
    private function normalizeSortOrder($order) : int
1328
    {
1329 2
        if (is_string($order)) {
1330
            $order = strtolower($order) === 'asc' ? 1 : -1;
1331
        }
1332
1333 2
        return $order;
1334
    }
1335
1336
    /**
1337
     * Ensure that a current field has been set.
1338
     *
1339
     * @throws LogicException If a current field has not been set.
1340
     */
1341 69
    private function requiresCurrentField() : void
1342
    {
1343 69
        if (! $this->currentField) {
1344
            throw new LogicException('This method requires you set a current field using field().');
1345
        }
1346 69
    }
1347
1348
    /**
1349
     * Wraps equality criteria with an operator.
1350
     *
1351
     * If equality criteria was previously specified for a field, it cannot be
1352
     * merged with other operators without first being wrapped in an operator of
1353
     * its own. Ideally, we would wrap it with $eq, but that is only available
1354
     * in MongoDB 2.8. Using a single-element $in is backwards compatible.
1355
     *
1356
     * @see Expr::operator()
1357
     */
1358 81
    private function wrapEqualityCriteria() : void
1359
    {
1360
        /* If the current field has no criteria yet, do nothing. This ensures
1361
         * that we do not inadvertently inject {"$in": null} into the query.
1362
         */
1363 81
        if ($this->currentField && ! isset($this->query[$this->currentField]) && ! array_key_exists($this->currentField, $this->query)) {
1364 55
            return;
1365
        }
1366
1367 31
        if ($this->currentField) {
1368 5
            $query = &$this->query[$this->currentField];
1369
        } else {
1370 27
            $query = &$this->query;
1371
        }
1372
1373
        /* If the query is an empty array, we'll assume that the user has not
1374
         * specified criteria. Otherwise, check if the array includes a query
1375
         * operator (checking the first key is sufficient). If neither of these
1376
         * conditions are met, we'll wrap the query value with $in.
1377
         */
1378 31
        if (is_array($query)) {
1379 31
            $key = key($query);
1380
1381 31
            if (empty($query) || (is_string($key) && strpos($key, '$') === 0)) {
1382 31
                return;
1383
            }
1384
        }
1385
1386 2
        $query = ['$in' => [$query]];
1387 2
    }
1388
}
1389