Expr::text()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
0 ignored issues
show
Unused Code introduced by
The parameter $expression is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expressions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
0 ignored issues
show
Unused Code introduced by
The parameter $expression is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expressions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
0 ignored issues
show
Unused Code introduced by
The parameter $expression is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expressions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
184 5
        return $this;
185
    }
186
187
    /**
188
     * Specify $all criteria for the current field.
189
     *
190
     * @see Builder::all()
191
     * @see http://docs.mongodb.org/manual/reference/operator/all/
192
     */
193 2
    public function all(array $values) : self
194
    {
195 2
        return $this->operator('$all', $values);
196
    }
197
198
    /**
199
     * Apply a bitwise operation on the current field
200
     *
201
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
202
     */
203 3
    protected function bit(string $operator, int $value) : self
204
    {
205 3
        $this->requiresCurrentField();
206 3
        $this->newObj['$bit'][$this->currentField][$operator] = $value;
207
208 3
        return $this;
209
    }
210
211
    /**
212
     * Apply a bitwise and operation on the current field.
213
     *
214
     * @see Builder::bitAnd()
215
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
216
     */
217 1
    public function bitAnd(int $value) : self
218
    {
219 1
        return $this->bit('and', $value);
220
    }
221
222
    /**
223
     * Apply a bitwise or operation on the current field.
224
     *
225
     * @see Builder::bitOr()
226
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
227
     */
228 1
    public function bitOr(int $value) : self
229
    {
230 1
        return $this->bit('or', $value);
231
    }
232
233
    /**
234
     * Matches documents where all of the bit positions given by the query are
235
     * clear.
236
     *
237
     * @see Builder::bitsAllClear()
238
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllClear/
239
     *
240
     * @param int|array|Binary $value
241
     */
242
    public function bitsAllClear($value) : self
243
    {
244
        $this->requiresCurrentField();
245
246
        return $this->operator('$bitsAllClear', $value);
247
    }
248
249
    /**
250
     * Matches documents where all of the bit positions given by the query are
251
     * set.
252
     *
253
     * @see Builder::bitsAllSet()
254
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllSet/
255
     *
256
     * @param int|array|Binary $value
257
     */
258
    public function bitsAllSet($value) : self
259
    {
260
        $this->requiresCurrentField();
261
262
        return $this->operator('$bitsAllSet', $value);
263
    }
264
265
    /**
266
     * Matches documents where any of the bit positions given by the query are
267
     * clear.
268
     *
269
     * @see Builder::bitsAnyClear()
270
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnyClear/
271
     *
272
     * @param int|array|Binary $value
273
     */
274
    public function bitsAnyClear($value) : self
275
    {
276
        $this->requiresCurrentField();
277
278
        return $this->operator('$bitsAnyClear', $value);
279
    }
280
281
    /**
282
     * Matches documents where any of the bit positions given by the query are
283
     * set.
284
     *
285
     * @see Builder::bitsAnySet()
286
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnySet/
287
     *
288
     * @param int|array|Binary $value
289
     */
290
    public function bitsAnySet($value) : self
291
    {
292
        $this->requiresCurrentField();
293
294
        return $this->operator('$bitsAnySet', $value);
295
    }
296
297
    /**
298
     * Apply a bitwise xor operation on the current field.
299
     *
300
     * @see Builder::bitXor()
301
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
302
     */
303 1
    public function bitXor(int $value) : self
304
    {
305 1
        return $this->bit('xor', $value);
306
    }
307
308
    /**
309
     * A boolean flag to enable or disable case sensitive search for $text
310
     * criteria.
311
     *
312
     * This method must be called after text().
313
     *
314
     * @see Builder::caseSensitive()
315
     * @see http://docs.mongodb.org/manual/reference/operator/text/
316
     *
317
     * @throws BadMethodCallException If the query does not already have $text criteria.
318
     */
319 3
    public function caseSensitive(bool $caseSensitive) : self
320
    {
321 3
        if (! isset($this->query['$text'])) {
322 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
323
        }
324
325
        // Remove caseSensitive option to keep support for older database versions
326 2
        if ($caseSensitive) {
327 2
            $this->query['$text']['$caseSensitive'] = true;
328 1
        } elseif (isset($this->query['$text']['$caseSensitive'])) {
329 1
            unset($this->query['$text']['$caseSensitive']);
330
        }
331
332 2
        return $this;
333
    }
334
335
    /**
336
     * Associates a comment to any expression taking a query predicate.
337
     *
338
     * @see Builder::comment()
339
     * @see http://docs.mongodb.org/manual/reference/operator/query/comment/
340
     */
341
    public function comment(string $comment) : self
342
    {
343
        $this->query['$comment'] = $comment;
344
345
        return $this;
346
    }
347
348
    /**
349
     * Sets the value of the current field to the current date, either as a date or a timestamp.
350
     *
351
     * @see Builder::currentDate()
352
     * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
353
     *
354
     * @throws InvalidArgumentException If an invalid type is given.
355
     */
356 3
    public function currentDate(string $type = 'date') : self
357
    {
358 3
        if (! in_array($type, ['date', 'timestamp'])) {
359 1
            throw new InvalidArgumentException('Type for currentDate operator must be date or timestamp.');
360
        }
361
362 2
        $this->requiresCurrentField();
363 2
        $this->newObj['$currentDate'][$this->currentField]['$type'] = $type;
364
365 2
        return $this;
366
    }
367
368
    /**
369
     * A boolean flag to enable or disable diacritic sensitive search for $text
370
     * criteria.
371
     *
372
     * This method must be called after text().
373
     *
374
     * @see Builder::diacriticSensitive()
375
     * @see http://docs.mongodb.org/manual/reference/operator/text/
376
     *
377
     * @throws BadMethodCallException If the query does not already have $text criteria.
378
     */
379 3
    public function diacriticSensitive(bool $diacriticSensitive) : self
380
    {
381 3
        if (! isset($this->query['$text'])) {
382 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
383
        }
384
385
        // Remove diacriticSensitive option to keep support for older database versions
386 2
        if ($diacriticSensitive) {
387 2
            $this->query['$text']['$diacriticSensitive'] = true;
388 1
        } elseif (isset($this->query['$text']['$diacriticSensitive'])) {
389 1
            unset($this->query['$text']['$diacriticSensitive']);
390
        }
391
392 2
        return $this;
393
    }
394
395
    /**
396
     * Add $each criteria to the expression for a $push operation.
397
     *
398
     * @see Expr::push()
399
     * @see http://docs.mongodb.org/manual/reference/operator/each/
400
     */
401 4
    public function each(array $values) : self
402
    {
403 4
        return $this->operator('$each', $values);
404
    }
405
406
    /**
407
     * Specify $elemMatch criteria for the current field.
408
     *
409
     * @see Builder::elemMatch()
410
     * @see http://docs.mongodb.org/manual/reference/operator/elemMatch/
411
     *
412
     * @param array|Expr $expression
413
     */
414 4
    public function elemMatch($expression) : self
415
    {
416 4
        return $this->operator('$elemMatch', $expression instanceof Expr ? $expression->getQuery() : $expression);
417
    }
418
419
    /**
420
     * Specify an equality match for the current field.
421
     *
422
     * @see Builder::equals()
423
     *
424
     * @param mixed $value
425
     */
426 104
    public function equals($value) : self
427
    {
428 104
        if ($this->currentField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentField 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...
429 103
            $this->query[$this->currentField] = $value;
430
        } else {
431 1
            $this->query = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type * is incompatible with the declared type array of property $query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
432
        }
433
434 104
        return $this;
435
    }
436
437
    /**
438
     * Specify $exists criteria for the current field.
439
     *
440
     * @see Builder::exists()
441
     * @see http://docs.mongodb.org/manual/reference/operator/exists/
442
     */
443 5
    public function exists(bool $bool) : self
444
    {
445 5
        return $this->operator('$exists', $bool);
446
    }
447
448
    /**
449
     * Set the current field for building the expression.
450
     *
451
     * @see Builder::field()
452
     */
453 192
    public function field(string $field) : self
454
    {
455 192
        $this->currentField = $field;
456
457 192
        return $this;
458
    }
459
460
    /**
461
     * Add $geoIntersects criteria with a GeoJSON geometry to the expression.
462
     *
463
     * The geometry parameter GeoJSON object or an array corresponding to the
464
     * geometry's JSON representation.
465
     *
466
     * @see Builder::geoIntersects()
467
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
468
     *
469
     * @param array|Geometry $geometry
470
     */
471 2
    public function geoIntersects($geometry) : self
472
    {
473 2
        if ($geometry instanceof Geometry) {
0 ignored issues
show
Bug introduced by
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...
474 1
            $geometry = $geometry->jsonSerialize();
475
        }
476
477 2
        return $this->operator('$geoIntersects', ['$geometry' => $geometry]);
478
    }
479
480
    /**
481
     * Add $geoWithin criteria with a GeoJSON geometry to the expression.
482
     *
483
     * The geometry parameter GeoJSON object or an array corresponding to the
484
     * geometry's JSON representation.
485
     *
486
     * @see Builder::geoWithin()
487
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
488
     *
489
     * @param array|Geometry $geometry
490
     */
491 2
    public function geoWithin($geometry) : self
492
    {
493 2
        if ($geometry instanceof Geometry) {
0 ignored issues
show
Bug introduced by
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...
494 1
            $geometry = $geometry->jsonSerialize();
495
        }
496
497 2
        return $this->operator('$geoWithin', ['$geometry' => $geometry]);
498
    }
499
500
    /**
501
     * Add $geoWithin criteria with a $box shape to the expression.
502
     *
503
     * A rectangular polygon will be constructed from a pair of coordinates
504
     * corresponding to the bottom left and top right corners.
505
     *
506
     * Note: the $box operator only supports legacy coordinate pairs and 2d
507
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
508
     *
509
     * @see Builder::geoWithinBox()
510
     * @see http://docs.mongodb.org/manual/reference/operator/box/
511
     */
512 1
    public function geoWithinBox(float $x1, float $y1, float $x2, float $y2) : self
513
    {
514 1
        $shape = ['$box' => [[$x1, $y1], [$x2, $y2]]];
515
516 1
        return $this->operator('$geoWithin', $shape);
517
    }
518
519
    /**
520
     * Add $geoWithin criteria with a $center shape to the expression.
521
     *
522
     * Note: the $center operator only supports legacy coordinate pairs and 2d
523
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
524
     *
525
     * @see Builider::geoWithinCenter()
526
     * @see http://docs.mongodb.org/manual/reference/operator/center/
527
     */
528 1
    public function geoWithinCenter(float $x, float $y, float $radius) : self
529
    {
530 1
        $shape = ['$center' => [[$x, $y], $radius]];
531
532 1
        return $this->operator('$geoWithin', $shape);
533
    }
534
535
    /**
536
     * Add $geoWithin criteria with a $centerSphere shape to the expression.
537
     *
538
     * Note: the $centerSphere operator supports both 2d and 2dsphere indexes.
539
     *
540
     * @see Builder::geoWithinCenterSphere()
541
     * @see http://docs.mongodb.org/manual/reference/operator/centerSphere/
542
     */
543 1
    public function geoWithinCenterSphere(float $x, float $y, float $radius) : self
544
    {
545 1
        $shape = ['$centerSphere' => [[$x, $y], $radius]];
546
547 1
        return $this->operator('$geoWithin', $shape);
548
    }
549
550
    /**
551
     * Add $geoWithin criteria with a $polygon shape to the expression.
552
     *
553
     * Point coordinates are in x, y order (easting, northing for projected
554
     * coordinates, longitude, latitude for geographic coordinates).
555
     *
556
     * The last point coordinate is implicitly connected with the first.
557
     *
558
     * Note: the $polygon operator only supports legacy coordinate pairs and 2d
559
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
560
     *
561
     * @see Builder::geoWithinPolygon()
562
     * @see http://docs.mongodb.org/manual/reference/operator/polygon/
563
     *
564
     * @param array $point1    First point of the polygon
565
     * @param array $point2    Second point of the polygon
566
     * @param array $point3    Third point of the polygon
567
     * @param array ...$points Additional points of the polygon
568
     *
569
     * @throws InvalidArgumentException If less than three points are given.
570
     */
571 1
    public function geoWithinPolygon($point1, $point2, $point3, ...$points) : self
0 ignored issues
show
Unused Code introduced by
The parameter $point1 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $point2 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $point3 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $points is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
572
    {
573 1
        $shape = ['$polygon' => func_get_args()];
574
575 1
        return $this->operator('$geoWithin', $shape);
576
    }
577
578
    /**
579
     * Return the current field.
580
     */
581 2
    public function getCurrentField() : ?string
582
    {
583 2
        return $this->currentField;
584
    }
585
586
    /**
587
     * Gets prepared newObj part of expression.
588
     */
589 182
    public function getNewObj() : array
590
    {
591 182
        return $this->dm->getUnitOfWork()
592 182
            ->getDocumentPersister($this->class->name)
593 182
            ->prepareQueryOrNewObj($this->newObj, true);
594
    }
595
596
    /**
597
     * Gets prepared query part of expression.
598
     */
599 258
    public function getQuery() : array
600
    {
601 258
        return $this->dm->getUnitOfWork()
602 258
            ->getDocumentPersister($this->class->name)
603 258
            ->prepareQueryOrNewObj($this->query);
604
    }
605
606
    /**
607
     * Specify $gt criteria for the current field.
608
     *
609
     * @see Builder::gt()
610
     * @see http://docs.mongodb.org/manual/reference/operator/gt/
611
     *
612
     * @param mixed $value
613
     */
614 2
    public function gt($value) : self
615
    {
616 2
        return $this->operator('$gt', $value);
617
    }
618
619
    /**
620
     * Specify $gte criteria for the current field.
621
     *
622
     * @see Builder::gte()
623
     * @see http://docs.mongodb.org/manual/reference/operator/gte/
624
     *
625
     * @param mixed $value
626
     */
627 2
    public function gte($value) : self
628
    {
629 2
        return $this->operator('$gte', $value);
630
    }
631
632
    /**
633
     * Specify $in criteria for the current field.
634
     *
635
     * @see Builder::in()
636
     * @see http://docs.mongodb.org/manual/reference/operator/in/
637
     */
638 31
    public function in(array $values) : self
639
    {
640 31
        return $this->operator('$in', array_values($values));
641
    }
642
643
    /**
644
     * Increment the current field.
645
     *
646
     * If the field does not exist, it will be set to this value.
647
     *
648
     * @see Builder::inc()
649
     * @see http://docs.mongodb.org/manual/reference/operator/inc/
650
     *
651
     * @param float|int $value
652
     */
653 5
    public function inc($value) : self
654
    {
655 5
        $this->requiresCurrentField();
656 5
        $this->newObj['$inc'][$this->currentField] = $value;
657
658 5
        return $this;
659
    }
660
661
    /**
662
     * Checks that the current field includes a reference to the supplied document.
663
     */
664 6
    public function includesReferenceTo(object $document) : self
665
    {
666 6
        $this->requiresCurrentField();
667 6
        $mapping   = $this->getReferenceMapping();
668 4
        $reference = $this->dm->createReference($document, $mapping);
669 4
        $storeAs   = $mapping['storeAs'] ?? null;
670 4
        $keys      = [];
0 ignored issues
show
Unused Code introduced by
$keys is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
671
672
        switch ($storeAs) {
673 4
            case ClassMetadata::REFERENCE_STORE_AS_ID:
674 2
                $this->query[$mapping['name']] = $reference;
675
676 2
                return $this;
677 3
            case ClassMetadata::REFERENCE_STORE_AS_REF:
678
                $keys = ['id' => true];
679
                break;
680
681 3
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
682 1
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
683 3
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
684
685 3
                if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
686 2
                    unset($keys['$db']);
687
                }
688
689 3
                if (isset($mapping['targetDocument'])) {
690 1
                    unset($keys['$ref'], $keys['$db']);
691
                }
692 3
                break;
693
694
            default:
695
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
696
        }
697
698 3
        foreach ($keys as $key => $value) {
699 3
            $this->query[$mapping['name']]['$elemMatch'][$key] = $reference[$key];
700
        }
701
702 3
        return $this;
703
    }
704
705
    /**
706
     * Set the $language option for $text criteria.
707
     *
708
     * This method must be called after text().
709
     *
710
     * @see Builder::language()
711
     * @see http://docs.mongodb.org/manual/reference/operator/text/
712
     *
713
     * @throws BadMethodCallException If the query does not already have $text criteria.
714
     */
715 2
    public function language(string $language) : self
716
    {
717 2
        if (! isset($this->query['$text'])) {
718 1
            throw new BadMethodCallException('This method requires a $text operator (call text() first)');
719
        }
720
721 1
        $this->query['$text']['$language'] = $language;
722
723 1
        return $this;
724
    }
725
726
    /**
727
     * Specify $lt criteria for the current field.
728
     *
729
     * @see Builder::lte()
730
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
731
     *
732
     * @param mixed $value
733
     */
734 4
    public function lt($value) : self
735
    {
736 4
        return $this->operator('$lt', $value);
737
    }
738
739
    /**
740
     * Specify $lte criteria for the current field.
741
     *
742
     * @see Builder::lte()
743
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
744
     *
745
     * @param mixed $value
746
     */
747 2
    public function lte($value) : self
748
    {
749 2
        return $this->operator('$lte', $value);
750
    }
751
752
    /**
753
     * Updates the value of the field to a specified value if the specified value is greater than the current value of the field.
754
     *
755
     * @see Builder::max()
756
     * @see http://docs.mongodb.org/manual/reference/operator/update/max/
757
     *
758
     * @param mixed $value
759
     */
760
    public function max($value) : self
761
    {
762
        $this->requiresCurrentField();
763
        $this->newObj['$max'][$this->currentField] = $value;
764
765
        return $this;
766
    }
767
768
    /**
769
     * Updates the value of the field to a specified value if the specified value is less than the current value of the field.
770
     *
771
     * @see Builder::min()
772
     * @see http://docs.mongodb.org/manual/reference/operator/update/min/
773
     *
774
     * @param mixed $value
775
     */
776
    public function min($value) : self
777
    {
778
        $this->requiresCurrentField();
779
        $this->newObj['$min'][$this->currentField] = $value;
780
781
        return $this;
782
    }
783
784
    /**
785
     * Specify $mod criteria for the current field.
786
     *
787
     * @see Builder::mod()
788
     * @see http://docs.mongodb.org/manual/reference/operator/mod/
789
     *
790
     * @param float|int $divisor
791
     * @param float|int $remainder
792
     */
793
    public function mod($divisor, $remainder = 0) : self
794
    {
795
        return $this->operator('$mod', [$divisor, $remainder]);
796
    }
797
798
    /**
799
     * Multiply the current field.
800
     *
801
     * If the field does not exist, it will be set to 0.
802
     *
803
     * @see Builder::mul()
804
     * @see http://docs.mongodb.org/manual/reference/operator/mul/
805
     *
806
     * @param float|int $value
807
     */
808
    public function mul($value) : self
809
    {
810
        $this->requiresCurrentField();
811
        $this->newObj['$mul'][$this->currentField] = $value;
812
813
        return $this;
814
    }
815
816
    /**
817
     * Add $near criteria to the expression.
818
     *
819
     * A GeoJSON point may be provided as the first and only argument for
820
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
821
     * an array corresponding to the point's JSON representation.
822
     *
823
     * @see Builder::near()
824
     * @see http://docs.mongodb.org/manual/reference/operator/near/
825
     *
826
     * @param float|array|Point $x
827
     * @param float             $y
828
     */
829 3
    public function near($x, $y = null) : self
830
    {
831 3
        if ($x instanceof Point) {
0 ignored issues
show
Bug introduced by
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...
832 1
            $x = $x->jsonSerialize();
833
        }
834
835 3
        if (is_array($x)) {
836 2
            return $this->operator('$near', ['$geometry' => $x]);
837
        }
838
839 1
        return $this->operator('$near', [$x, $y]);
840
    }
841
842
    /**
843
     * Add $nearSphere criteria to the expression.
844
     *
845
     * A GeoJSON point may be provided as the first and only argument for
846
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
847
     * an array corresponding to the point's JSON representation.
848
     *
849
     * @see Builder::nearSphere()
850
     * @see http://docs.mongodb.org/manual/reference/operator/nearSphere/
851
     *
852
     * @param float|array|Point $x
853
     * @param float             $y
854
     */
855 3
    public function nearSphere($x, $y = null) : self
856
    {
857 3
        if ($x instanceof Point) {
0 ignored issues
show
Bug introduced by
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...
858 1
            $x = $x->jsonSerialize();
859
        }
860
861 3
        if (is_array($x)) {
862 2
            return $this->operator('$nearSphere', ['$geometry' => $x]);
863
        }
864
865 1
        return $this->operator('$nearSphere', [$x, $y]);
866
    }
867
868
    /**
869
     * Negates an expression for the current field.
870
     *
871
     * @see Builder::not()
872
     * @see http://docs.mongodb.org/manual/reference/operator/not/
873
     *
874
     * @param array|Expr $expression
875
     */
876 2
    public function not($expression) : self
877
    {
878 2
        return $this->operator('$not', $expression instanceof Expr ? $expression->getQuery() : $expression);
879
    }
880
881
    /**
882
     * Specify $ne criteria for the current field.
883
     *
884
     * @see Builder::notEqual()
885
     * @see http://docs.mongodb.org/manual/reference/operator/ne/
886
     *
887
     * @param mixed $value
888
     */
889 6
    public function notEqual($value) : self
890
    {
891 6
        return $this->operator('$ne', $value);
892
    }
893
894
    /**
895
     * Specify $nin criteria for the current field.
896
     *
897
     * @see Builder::notIn()
898
     * @see http://docs.mongodb.org/manual/reference/operator/nin/
899
     */
900 6
    public function notIn(array $values) : self
901
    {
902 6
        return $this->operator('$nin', array_values($values));
903
    }
904
905
    /**
906
     * Defines an operator and value on the expression.
907
     *
908
     * If there is a current field, the operator will be set on it; otherwise,
909
     * the operator is set at the top level of the query.
910
     *
911
     * @param mixed $value
912
     */
913 81
    public function operator(string $operator, $value) : self
914
    {
915 81
        $this->wrapEqualityCriteria();
916
917 81
        if ($this->currentField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentField 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...
918 56
            $this->query[$this->currentField][$operator] = $value;
919
        } else {
920 27
            $this->query[$operator] = $value;
921
        }
922
923 81
        return $this;
924
    }
925
926
    /**
927
     * Remove the first element from the current array field.
928
     *
929
     * @see Builder::popFirst()
930
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
931
     */
932 2
    public function popFirst() : self
933
    {
934 2
        $this->requiresCurrentField();
935 2
        $this->newObj['$pop'][$this->currentField] = -1;
936
937 2
        return $this;
938
    }
939
940
    /**
941
     * Remove the last element from the current array field.
942
     *
943
     * @see Builder::popLast()
944
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
945
     */
946 1
    public function popLast() : self
947
    {
948 1
        $this->requiresCurrentField();
949 1
        $this->newObj['$pop'][$this->currentField] = 1;
950
951 1
        return $this;
952
    }
953
954
    /**
955
     * Add $position criteria to the expression for a $push operation.
956
     *
957
     * This is useful in conjunction with {@link Expr::each()} for a
958
     * {@link Expr::push()} operation.
959
     *
960
     * @see http://docs.mongodb.org/manual/reference/operator/update/position/
961
     */
962 1
    public function position(int $position) : self
963
    {
964 1
        return $this->operator('$position', $position);
965
    }
966
967
    /**
968
     * Remove all elements matching the given value or expression from the
969
     * current array field.
970
     *
971
     * @see Builder::pull()
972
     * @see http://docs.mongodb.org/manual/reference/operator/pull/
973
     *
974
     * @param mixed|Expr $valueOrExpression
975
     */
976 2
    public function pull($valueOrExpression) : self
977
    {
978 2
        if ($valueOrExpression instanceof Expr) {
979 1
            $valueOrExpression = $valueOrExpression->getQuery();
980
        }
981
982 2
        $this->requiresCurrentField();
983 2
        $this->newObj['$pull'][$this->currentField] = $valueOrExpression;
984
985 2
        return $this;
986
    }
987
988
    /**
989
     * Remove all elements matching any of the given values from the current
990
     * array field.
991
     *
992
     * @see Builder::pullAll()
993
     * @see http://docs.mongodb.org/manual/reference/operator/pullAll/
994
     */
995
    public function pullAll(array $values) : self
996
    {
997
        $this->requiresCurrentField();
998
        $this->newObj['$pullAll'][$this->currentField] = $values;
999
1000
        return $this;
1001
    }
1002
1003
    /**
1004
     * Append one or more values to the current array field.
1005
     *
1006
     * If the field does not exist, it will be set to an array containing the
1007
     * value(s) in the argument. If the field is not an array, the query
1008
     * will yield an error.
1009
     *
1010
     * Multiple values may be specified by providing an Expr object and using
1011
     * {@link Expr::each()}. {@link Expr::slice()} and {@link Expr::sort()} may
1012
     * also be used to limit and order array elements, respectively.
1013
     *
1014
     * @see Builder::push()
1015
     * @see http://docs.mongodb.org/manual/reference/operator/push/
1016
     * @see http://docs.mongodb.org/manual/reference/operator/each/
1017
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1018
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1019
     *
1020
     * @param mixed|Expr $valueOrExpression
1021
     */
1022 8
    public function push($valueOrExpression) : self
1023
    {
1024 8
        if ($valueOrExpression instanceof Expr) {
1025 3
            $valueOrExpression = array_merge(
1026 3
                ['$each' => []],
1027 3
                $valueOrExpression->getQuery()
1028
            );
1029
        }
1030
1031 8
        $this->requiresCurrentField();
1032 8
        $this->newObj['$push'][$this->currentField] = $valueOrExpression;
1033
1034 8
        return $this;
1035
    }
1036
1037
    /**
1038
     * Specify $gte and $lt criteria for the current field.
1039
     *
1040
     * This method is shorthand for specifying $gte criteria on the lower bound
1041
     * and $lt criteria on the upper bound. The upper bound is not inclusive.
1042
     *
1043
     * @see Builder::range()
1044
     *
1045
     * @param mixed $start
1046
     * @param mixed $end
1047
     */
1048 2
    public function range($start, $end) : self
1049
    {
1050 2
        return $this->operator('$gte', $start)->operator('$lt', $end);
1051
    }
1052
1053
    /**
1054
     * Checks that the value of the current field is a reference to the supplied document.
1055
     */
1056 13
    public function references(object $document) : self
1057
    {
1058 13
        $this->requiresCurrentField();
1059 13
        $mapping   = $this->getReferenceMapping();
1060 11
        $reference = $this->dm->createReference($document, $mapping);
1061 11
        $storeAs   = $mapping['storeAs'] ?? null;
1062 11
        $keys      = [];
0 ignored issues
show
Unused Code introduced by
$keys is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1063
1064
        switch ($storeAs) {
1065 11
            case ClassMetadata::REFERENCE_STORE_AS_ID:
1066 4
                $this->query[$mapping['name']] = $reference;
1067
1068 4
                return $this;
1069 8
            case ClassMetadata::REFERENCE_STORE_AS_REF:
1070
                $keys = ['id' => true];
1071
                break;
1072
1073 8
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
1074 2
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
1075 8
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
1076
1077 8
                if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
1078 6
                    unset($keys['$db']);
1079
                }
1080
1081 8
                if (isset($mapping['targetDocument'])) {
1082 4
                    unset($keys['$ref'], $keys['$db']);
1083
                }
1084 8
                break;
1085
1086
            default:
1087
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
1088
        }
1089
1090 8
        foreach ($keys as $key => $value) {
1091 8
            $this->query[$mapping['name'] . '.' . $key] = $reference[$key];
1092
        }
1093
1094 8
        return $this;
1095
    }
1096
1097
    /**
1098
     * Rename the current field.
1099
     *
1100
     * @see Builder::rename()
1101
     * @see http://docs.mongodb.org/manual/reference/operator/rename/
1102
     */
1103
    public function rename(string $name) : self
1104
    {
1105
        $this->requiresCurrentField();
1106
        $this->newObj['$rename'][$this->currentField] = $name;
1107
1108
        return $this;
1109
    }
1110
1111
    /**
1112
     * Set the current field to a value.
1113
     *
1114
     * This is only relevant for insert, update, or findAndUpdate queries. For
1115
     * update and findAndUpdate queries, the $atomic parameter will determine
1116
     * whether or not a $set operator is used.
1117
     *
1118
     * @see Builder::set()
1119
     * @see http://docs.mongodb.org/manual/reference/operator/set/
1120
     *
1121
     * @param mixed $value
1122
     */
1123 19
    public function set($value, bool $atomic = true) : self
1124
    {
1125 19
        $this->requiresCurrentField();
1126 19
        assert($this->currentField !== null);
1127
1128 19
        if ($atomic) {
1129 16
            $this->newObj['$set'][$this->currentField] = $value;
1130
1131 16
            return $this;
1132
        }
1133
1134 3
        if (strpos($this->currentField, '.') === false) {
1135 2
            $this->newObj[$this->currentField] = $value;
1136
1137 2
            return $this;
1138
        }
1139
1140 2
        $keys    = explode('.', $this->currentField);
1141 2
        $current = &$this->newObj;
1142 2
        foreach ($keys as $key) {
1143 2
            $current = &$current[$key];
1144
        }
1145 2
        $current = $value;
1146
1147 2
        return $this;
1148
    }
1149
1150
    /**
1151
     * Sets ClassMetadata for document being queried.
1152
     */
1153 390
    public function setClassMetadata(ClassMetadata $class) : void
1154
    {
1155 390
        $this->class = $class;
1156 390
    }
1157
1158
    /**
1159
     * Set the "new object".
1160
     *
1161
     * @see Builder::setNewObj()
1162
     */
1163 1
    public function setNewObj(array $newObj) : self
1164
    {
1165 1
        $this->newObj = $newObj;
1166
1167 1
        return $this;
1168
    }
1169
1170
    /**
1171
     * Set the current field to the value if the document is inserted in an
1172
     * upsert operation.
1173
     *
1174
     * If an update operation with upsert: true results in an insert of a
1175
     * document, then $setOnInsert assigns the specified values to the fields in
1176
     * the document. If the update operation does not result in an insert,
1177
     * $setOnInsert does nothing.
1178
     *
1179
     * @see Builder::setOnInsert()
1180
     * @see https://docs.mongodb.org/manual/reference/operator/update/setOnInsert/
1181
     *
1182
     * @param mixed $value
1183
     */
1184 1
    public function setOnInsert($value) : self
1185
    {
1186 1
        $this->requiresCurrentField();
1187 1
        $this->newObj['$setOnInsert'][$this->currentField] = $value;
1188
1189 1
        return $this;
1190
    }
1191
1192
    /**
1193
     * Set the query criteria.
1194
     *
1195
     * @see Builder::setQueryArray()
1196
     */
1197 19
    public function setQuery(array $query) : self
1198
    {
1199 19
        $this->query = $query;
1200
1201 19
        return $this;
1202
    }
1203
1204
    /**
1205
     * Specify $size criteria for the current field.
1206
     *
1207
     * @see Builder::size()
1208
     * @see http://docs.mongodb.org/manual/reference/operator/size/
1209
     */
1210
    public function size(int $size) : self
1211
    {
1212
        return $this->operator('$size', $size);
1213
    }
1214
1215
    /**
1216
     * Add $slice criteria to the expression for a $push operation.
1217
     *
1218
     * This is useful in conjunction with {@link Expr::each()} for a
1219
     * {@link Expr::push()} operation. {@link Builder::selectSlice()} should be
1220
     * used for specifying $slice for a query projection.
1221
     *
1222
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1223
     */
1224 2
    public function slice(int $slice) : self
1225
    {
1226 2
        return $this->operator('$slice', $slice);
1227
    }
1228
1229
    /**
1230
     * Add $sort criteria to the expression for a $push operation.
1231
     *
1232
     * If sorting by multiple fields, the first argument should be an array of
1233
     * field name (key) and order (value) pairs.
1234
     *
1235
     * This is useful in conjunction with {@link Expr::each()} for a
1236
     * {@link Expr::push()} operation. {@link Builder::sort()} should be used to
1237
     * sort the results of a query.
1238
     *
1239
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1240
     *
1241
     * @param array|string $fieldName Field name or array of field/order pairs
1242
     * @param int|string   $order     Field order (if one field is specified)
1243
     */
1244 2
    public function sort($fieldName, $order = null) : self
1245
    {
1246 2
        $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order];
1247
1248
        return $this->operator('$sort', array_map(function ($order) {
1249 2
            return $this->normalizeSortOrder($order);
1250 2
        }, $fields));
1251
    }
1252
1253
    /**
1254
     * Specify $text criteria for the current query.
1255
     *
1256
     * The $language option may be set with {@link Expr::language()}.
1257
     *
1258
     * @see Builder::text()
1259
     * @see http://docs.mongodb.org/master/reference/operator/query/text/
1260
     */
1261 6
    public function text(string $search) : self
1262
    {
1263 6
        $this->query['$text'] = ['$search' => $search];
1264
1265 6
        return $this;
1266
    }
1267
1268
    /**
1269
     * Specify $type criteria for the current field.
1270
     *
1271
     * @see Builder::type()
1272
     * @see http://docs.mongodb.org/manual/reference/operator/type/
1273
     *
1274
     * @param int|string $type
1275
     */
1276 1
    public function type($type) : self
1277
    {
1278 1
        return $this->operator('$type', $type);
1279
    }
1280
1281
    /**
1282
     * Unset the current field.
1283
     *
1284
     * The field will be removed from the document (not set to null).
1285
     *
1286
     * @see Builder::unsetField()
1287
     * @see http://docs.mongodb.org/manual/reference/operator/unset/
1288
     */
1289 3
    public function unsetField() : self
1290
    {
1291 3
        $this->requiresCurrentField();
1292 3
        $this->newObj['$unset'][$this->currentField] = 1;
1293
1294 3
        return $this;
1295
    }
1296
1297
    /**
1298
     * Specify a JavaScript expression to use for matching documents.
1299
     *
1300
     * @see Builder::where()
1301
     * @see http://docs.mongodb.org/manual/reference/operator/where/
1302
     *
1303
     * @param string|Javascript $javascript
1304
     */
1305 3
    public function where($javascript) : self
1306
    {
1307 3
        $this->query['$where'] = $javascript;
1308
1309 3
        return $this;
1310
    }
1311
1312
    /**
1313
     * Gets reference mapping for current field from current class or its descendants.
1314
     *
1315
     * @throws MappingException
1316
     */
1317 19
    private function getReferenceMapping() : array
1318
    {
1319 19
        $this->requiresCurrentField();
1320 19
        assert($this->currentField !== null);
1321
1322
        try {
1323 19
            return $this->class->getFieldMapping($this->currentField);
1324 6
        } catch (MappingException $e) {
1325 6
            if (empty($this->class->discriminatorMap)) {
1326
                throw $e;
1327
            }
1328 6
            $mapping = null;
1329 6
            $foundIn = null;
1330 6
            foreach ($this->class->discriminatorMap as $child) {
1331 6
                $childClass = $this->dm->getClassMetadata($child);
1332 6
                if (! $childClass->hasAssociation($this->currentField)) {
1333 4
                    continue;
1334
                }
1335
1336 4
                if ($foundIn !== null && $mapping !== null && $mapping !== $childClass->getFieldMapping($this->currentField)) {
1337 2
                    throw MappingException::referenceFieldConflict($this->currentField, $foundIn->name, $childClass->name);
1338
                }
1339 4
                $mapping = $childClass->getFieldMapping($this->currentField);
1340 4
                $foundIn = $childClass;
1341
            }
1342 4
            if ($mapping === null) {
1343 2
                throw MappingException::mappingNotFoundInClassNorDescendants($this->class->name, $this->currentField);
1344
            }
1345
1346 2
            return $mapping;
1347
        }
1348
    }
1349
1350
    /**
1351
     * @param int|string $order
1352
     */
1353 2
    private function normalizeSortOrder($order) : int
1354
    {
1355 2
        if (is_string($order)) {
1356
            $order = strtolower($order) === 'asc' ? 1 : -1;
1357
        }
1358
1359 2
        return $order;
1360
    }
1361
1362
    /**
1363
     * Ensure that a current field has been set.
1364
     *
1365
     * @throws LogicException If a current field has not been set.
1366
     */
1367 69
    private function requiresCurrentField() : void
1368
    {
1369 69
        if (! $this->currentField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentField of type string|null is loosely compared to false; 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...
1370
            throw new LogicException('This method requires you set a current field using field().');
1371
        }
1372 69
    }
1373
1374
    /**
1375
     * Wraps equality criteria with an operator.
1376
     *
1377
     * If equality criteria was previously specified for a field, it cannot be
1378
     * merged with other operators without first being wrapped in an operator of
1379
     * its own. Ideally, we would wrap it with $eq, but that is only available
1380
     * in MongoDB 2.8. Using a single-element $in is backwards compatible.
1381
     *
1382
     * @see Expr::operator()
1383
     */
1384 81
    private function wrapEqualityCriteria() : void
1385
    {
1386
        /* If the current field has no criteria yet, do nothing. This ensures
1387
         * that we do not inadvertently inject {"$in": null} into the query.
1388
         */
1389 81
        if ($this->currentField && ! isset($this->query[$this->currentField]) && ! array_key_exists($this->currentField, $this->query)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentField 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...
1390 55
            return;
1391
        }
1392
1393 31
        if ($this->currentField) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentField 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...
1394 5
            $query = &$this->query[$this->currentField];
1395
        } else {
1396 27
            $query = &$this->query;
1397
        }
1398
1399
        /* If the query is an empty array, we'll assume that the user has not
1400
         * specified criteria. Otherwise, check if the array includes a query
1401
         * operator (checking the first key is sufficient). If neither of these
1402
         * conditions are met, we'll wrap the query value with $in.
1403
         */
1404 31
        if (is_array($query)) {
1405 31
            $key = key($query);
1406
1407 31
            if (empty($query) || (is_string($key) && strpos($key, '$') === 0)) {
1408 31
                return;
1409
            }
1410
        }
1411
1412 2
        $query = ['$in' => [$query]];
1413 2
    }
1414
}
1415