Completed
Pull Request — master (#1846)
by Catalin
16:48
created

Expr::notEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
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 Doctrine\ODM\MongoDB\DocumentManager;
8
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
9
use Doctrine\ODM\MongoDB\Mapping\MappingException;
10
use GeoJson\Geometry\Geometry;
11
use GeoJson\Geometry\Point;
12
use MongoDB\BSON\Binary;
13
use MongoDB\BSON\Javascript;
14
use function array_key_exists;
15
use function array_map;
16
use function array_merge;
17
use function array_values;
18
use function explode;
19
use function func_get_args;
20
use function in_array;
21
use function is_array;
22
use function is_string;
23
use function key;
24
use function sprintf;
25
use function strpos;
26
use function strtolower;
27
28
/**
29
 * Query expression builder for ODM.
30
 *
31
 */
32
class Expr
33
{
34
    /**
35
     * The query criteria array.
36
     *
37
     * @var array
38
     */
39
    private $query = [];
40
41
    /**
42
     * The "new object" array containing either a full document or a number of
43
     * atomic update operators.
44
     *
45
     * @see docs.mongodb.org/manual/reference/method/db.collection.update/#update-parameter
46
     * @var array
47
     */
48
    private $newObj = [];
49
50
    /**
51
     * The current field we are operating on.
52
     *
53
     * @var string|null
54
     */
55
    private $currentField;
56
57
    /**
58
     * The DocumentManager instance for this query
59
     *
60
     * @var DocumentManager
61
     */
62
    private $dm;
63
64
    /**
65
     * The ClassMetadata instance for the document being queried
66
     *
67
     * @var ClassMetadata
68
     */
69
    private $class;
70
71 395
    public function __construct(DocumentManager $dm)
72
    {
73 395
        $this->dm = $dm;
74 395
    }
75
76
    /**
77
     * Add one or more $and clauses to the current query.
78
     *
79
     * @see Builder::addAnd()
80
     * @see http://docs.mongodb.org/manual/reference/operator/and/
81
     * @param array|Expr $expression
82
     * @param array|Expr ...$expressions
83
     */
84 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...
85
    {
86 3
        if (! isset($this->query['$and'])) {
87 3
            $this->query['$and'] = [];
88
        }
89
90 3
        $this->query['$and'] = array_merge(
91 3
            $this->query['$and'],
92 3
            array_map(
93
                function ($expression) {
94 3
                    return $expression instanceof Expr ? $expression->getQuery() : $expression;
95 3
                },
96 3
                func_get_args()
97
            )
98
        );
99
100 3
        return $this;
101
    }
102
103
    /**
104
     * Add one or more $nor clauses to the current query.
105
     *
106
     * @see Builder::addNor()
107
     * @see http://docs.mongodb.org/manual/reference/operator/nor/
108
     * @param array|Expr $expression
109
     * @param array|Expr ...$expressions
110
     */
111 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...
112
    {
113 1
        if (! isset($this->query['$nor'])) {
114 1
            $this->query['$nor'] = [];
115
        }
116
117 1
        $this->query['$nor'] = array_merge(
118 1
            $this->query['$nor'],
119
            array_map(function ($expression) {
120 1
                return $expression instanceof Expr ? $expression->getQuery() : $expression;
121 1
            }, func_get_args())
122
        );
123
124 1
        return $this;
125
    }
126
127
    /**
128
     * Add one or more $or clauses to the current query.
129
     *
130
     * @see Builder::addOr()
131
     * @see http://docs.mongodb.org/manual/reference/operator/or/
132
     * @param array|Expr $expression
133
     * @param array|Expr ...$expressions
134
     */
135 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...
136
    {
137 5
        if (! isset($this->query['$or'])) {
138 5
            $this->query['$or'] = [];
139
        }
140
141 5
        $this->query['$or'] = array_merge(
142 5
            $this->query['$or'],
143
            array_map(function ($expression) {
144 5
                return $expression instanceof Expr ? $expression->getQuery() : $expression;
145 5
            }, func_get_args())
146
        );
147
148 5
        return $this;
149
    }
150
151
    /**
152
     * Append one or more values to the current array field only if they do not
153
     * already exist in the array.
154
     *
155
     * If the field does not exist, it will be set to an array containing the
156
     * unique value(s) in the argument. If the field is not an array, the query
157
     * will yield an error.
158
     *
159
     * Multiple values may be specified by provided an Expr object and using
160
     * {@link Expr::each()}.
161
     *
162
     * @see Builder::addToSet()
163
     * @see http://docs.mongodb.org/manual/reference/operator/addToSet/
164
     * @see http://docs.mongodb.org/manual/reference/operator/each/
165
     * @param mixed|Expr $valueOrExpression
166
     */
167 5
    public function addToSet($valueOrExpression): self
168
    {
169 5
        if ($valueOrExpression instanceof Expr) {
170 1
            $valueOrExpression = $valueOrExpression->getQuery();
171
        }
172
173 5
        $this->requiresCurrentField();
174 5
        $this->newObj['$addToSet'][$this->currentField] = $valueOrExpression;
175 5
        return $this;
176
    }
177
178
    /**
179
     * Specify $all criteria for the current field.
180
     *
181
     * @see Builder::all()
182
     * @see http://docs.mongodb.org/manual/reference/operator/all/
183
     */
184 2
    public function all(array $values): self
185
    {
186 2
        return $this->operator('$all', $values);
187
    }
188
189
    /**
190
     * Apply a bitwise operation on the current field
191
     *
192
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
193
     */
194 3
    protected function bit(string $operator, int $value): self
195
    {
196 3
        $this->requiresCurrentField();
197 3
        $this->newObj['$bit'][$this->currentField][$operator] = $value;
198 3
        return $this;
199
    }
200
201
    /**
202
     * Apply a bitwise and operation on the current field.
203
     *
204
     * @see Builder::bitAnd()
205
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
206
     */
207 1
    public function bitAnd(int $value): self
208
    {
209 1
        return $this->bit('and', $value);
210
    }
211
212
    /**
213
     * Apply a bitwise or operation on the current field.
214
     *
215
     * @see Builder::bitOr()
216
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
217
     */
218 1
    public function bitOr(int $value): self
219
    {
220 1
        return $this->bit('or', $value);
221
    }
222
223
    /**
224
     * Matches documents where all of the bit positions given by the query are
225
     * clear.
226
     *
227
     * @see Builder::bitsAllClear()
228
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllClear/
229
     * @param int|array|Binary $value
230
     */
231
    public function bitsAllClear($value): self
232
    {
233
        $this->requiresCurrentField();
234
        return $this->operator('$bitsAllClear', $value);
235
    }
236
237
    /**
238
     * Matches documents where all of the bit positions given by the query are
239
     * set.
240
     *
241
     * @see Builder::bitsAllSet()
242
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllSet/
243
     * @param int|array|Binary $value
244
     */
245
    public function bitsAllSet($value): self
246
    {
247
        $this->requiresCurrentField();
248
        return $this->operator('$bitsAllSet', $value);
249
    }
250
251
    /**
252
     * Matches documents where any of the bit positions given by the query are
253
     * clear.
254
     *
255
     * @see Builder::bitsAnyClear()
256
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnyClear/
257
     * @param int|array|Binary $value
258
     */
259
    public function bitsAnyClear($value): self
260
    {
261
        $this->requiresCurrentField();
262
        return $this->operator('$bitsAnyClear', $value);
263
    }
264
265
    /**
266
     * Matches documents where any of the bit positions given by the query are
267
     * set.
268
     *
269
     * @see Builder::bitsAnySet()
270
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnySet/
271
     * @param int|array|Binary $value
272
     */
273
    public function bitsAnySet($value): self
274
    {
275
        $this->requiresCurrentField();
276
        return $this->operator('$bitsAnySet', $value);
277
    }
278
279
    /**
280
     * Apply a bitwise xor operation on the current field.
281
     *
282
     * @see Builder::bitXor()
283
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
284
     */
285 1
    public function bitXor(int $value): self
286
    {
287 1
        return $this->bit('xor', $value);
288
    }
289
290
    /**
291
     * A boolean flag to enable or disable case sensitive search for $text
292
     * criteria.
293
     *
294
     * This method must be called after text().
295
     *
296
     * @see Builder::caseSensitive()
297
     * @see http://docs.mongodb.org/manual/reference/operator/text/
298
     * @throws \BadMethodCallException If the query does not already have $text criteria.
299
     *
300
     */
301 3
    public function caseSensitive(bool $caseSensitive): self
302
    {
303 3
        if (! isset($this->query['$text'])) {
304 1
            throw new \BadMethodCallException('This method requires a $text operator (call text() first)');
305
        }
306
307
        // Remove caseSensitive option to keep support for older database versions
308 2
        if ($caseSensitive) {
309 2
            $this->query['$text']['$caseSensitive'] = true;
310 1
        } elseif (isset($this->query['$text']['$caseSensitive'])) {
311 1
            unset($this->query['$text']['$caseSensitive']);
312
        }
313
314 2
        return $this;
315
    }
316
317
    /**
318
     * Associates a comment to any expression taking a query predicate.
319
     *
320
     * @see Builder::comment()
321
     * @see http://docs.mongodb.org/manual/reference/operator/query/comment/
322
     */
323
    public function comment(string $comment): self
324
    {
325
        $this->query['$comment'] = $comment;
326
        return $this;
327
    }
328
329
    /**
330
     * Sets the value of the current field to the current date, either as a date or a timestamp.
331
     *
332
     * @see Builder::currentDate()
333
     * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
334
     * @throws \InvalidArgumentException If an invalid type is given.
335
     */
336 3
    public function currentDate(string $type = 'date'): self
337
    {
338 3
        if (! in_array($type, ['date', 'timestamp'])) {
339 1
            throw new \InvalidArgumentException('Type for currentDate operator must be date or timestamp.');
340
        }
341
342 2
        $this->requiresCurrentField();
343 2
        $this->newObj['$currentDate'][$this->currentField]['$type'] = $type;
344 2
        return $this;
345
    }
346
347
    /**
348
     * A boolean flag to enable or disable diacritic sensitive search for $text
349
     * criteria.
350
     *
351
     * This method must be called after text().
352
     *
353
     * @see Builder::diacriticSensitive()
354
     * @see http://docs.mongodb.org/manual/reference/operator/text/
355
     * @throws \BadMethodCallException If the query does not already have $text criteria.
356
     *
357
     */
358 3
    public function diacriticSensitive(bool $diacriticSensitive): self
359
    {
360 3
        if (! isset($this->query['$text'])) {
361 1
            throw new \BadMethodCallException('This method requires a $text operator (call text() first)');
362
        }
363
364
        // Remove diacriticSensitive option to keep support for older database versions
365 2
        if ($diacriticSensitive) {
366 2
            $this->query['$text']['$diacriticSensitive'] = true;
367 1
        } elseif (isset($this->query['$text']['$diacriticSensitive'])) {
368 1
            unset($this->query['$text']['$diacriticSensitive']);
369
        }
370
371 2
        return $this;
372
    }
373
374
    /**
375
     * Add $each criteria to the expression for a $push operation.
376
     *
377
     * @see Expr::push()
378
     * @see http://docs.mongodb.org/manual/reference/operator/each/
379
     */
380 4
    public function each(array $values): self
381
    {
382 4
        return $this->operator('$each', $values);
383
    }
384
385
    /**
386
     * Specify $elemMatch criteria for the current field.
387
     *
388
     * @see Builder::elemMatch()
389
     * @see http://docs.mongodb.org/manual/reference/operator/elemMatch/
390
     * @param array|Expr $expression
391
     */
392 4
    public function elemMatch($expression): self
393
    {
394 4
        return $this->operator('$elemMatch', $expression instanceof Expr ? $expression->getQuery() : $expression);
395
    }
396
397
    /**
398
     * Specify an equality match for the current field.
399
     *
400
     * @see Builder::equals()
401
     * @param mixed $value
402
     */
403 104
    public function equals($value): self
404
    {
405 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...
406 103
            $this->query[$this->currentField] = $value;
407
        } else {
408 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...
409
        }
410 104
        return $this;
411
    }
412
413
    /**
414
     * Specify $exists criteria for the current field.
415
     *
416
     * @see Builder::exists()
417
     * @see http://docs.mongodb.org/manual/reference/operator/exists/
418
     * @return $this
419
     */
420 5
    public function exists(bool $bool): self
421
    {
422 5
        return $this->operator('$exists', (bool) $bool);
423
    }
424
425
    /**
426
     * Set the current field for building the expression.
427
     *
428
     * @see Builder::field()
429
     */
430 195
    public function field(string $field): self
431
    {
432 195
        $this->currentField = $field;
433 195
        return $this;
434
    }
435
436
    /**
437
     * Add $geoIntersects criteria with a GeoJSON geometry to the expression.
438
     *
439
     * The geometry parameter GeoJSON object or an array corresponding to the
440
     * geometry's JSON representation.
441
     *
442
     * @see Builder::geoIntersects()
443
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
444
     * @param array|Geometry $geometry
445
     */
446 2
    public function geoIntersects($geometry): self
447
    {
448 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...
449 1
            $geometry = $geometry->jsonSerialize();
450
        }
451
452 2
        return $this->operator('$geoIntersects', ['$geometry' => $geometry]);
453
    }
454
455
    /**
456
     * Add $geoWithin criteria with a GeoJSON geometry to the expression.
457
     *
458
     * The geometry parameter GeoJSON object or an array corresponding to the
459
     * geometry's JSON representation.
460
     *
461
     * @see Builder::geoWithin()
462
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
463
     * @param array|Geometry $geometry
464
     */
465 2
    public function geoWithin($geometry): self
466
    {
467 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...
468 1
            $geometry = $geometry->jsonSerialize();
469
        }
470
471 2
        return $this->operator('$geoWithin', ['$geometry' => $geometry]);
472
    }
473
474
    /**
475
     * Add $geoWithin criteria with a $box shape to the expression.
476
     *
477
     * A rectangular polygon will be constructed from a pair of coordinates
478
     * corresponding to the bottom left and top right corners.
479
     *
480
     * Note: the $box operator only supports legacy coordinate pairs and 2d
481
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
482
     *
483
     * @see Builder::geoWithinBox()
484
     * @see http://docs.mongodb.org/manual/reference/operator/box/
485
     */
486 1
    public function geoWithinBox(float $x1, float $y1, float $x2, float $y2): self
487
    {
488 1
        $shape = ['$box' => [[$x1, $y1], [$x2, $y2]]];
489
490 1
        return $this->operator('$geoWithin', $shape);
491
    }
492
493
    /**
494
     * Add $geoWithin criteria with a $center shape to the expression.
495
     *
496
     * Note: the $center operator only supports legacy coordinate pairs and 2d
497
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
498
     *
499
     * @see Builider::geoWithinCenter()
500
     * @see http://docs.mongodb.org/manual/reference/operator/center/
501
     */
502 1
    public function geoWithinCenter(float $x, float $y, float $radius): self
503
    {
504 1
        $shape = ['$center' => [[$x, $y], $radius]];
505
506 1
        return $this->operator('$geoWithin', $shape);
507
    }
508
509
    /**
510
     * Add $geoWithin criteria with a $centerSphere shape to the expression.
511
     *
512
     * Note: the $centerSphere operator supports both 2d and 2dsphere indexes.
513
     *
514
     * @see Builder::geoWithinCenterSphere()
515
     * @see http://docs.mongodb.org/manual/reference/operator/centerSphere/
516
     */
517 1
    public function geoWithinCenterSphere(float $x, float $y, float $radius): self
518
    {
519 1
        $shape = ['$centerSphere' => [[$x, $y], $radius]];
520
521 1
        return $this->operator('$geoWithin', $shape);
522
    }
523
524
    /**
525
     * Add $geoWithin criteria with a $polygon shape to the expression.
526
     *
527
     * Point coordinates are in x, y order (easting, northing for projected
528
     * coordinates, longitude, latitude for geographic coordinates).
529
     *
530
     * The last point coordinate is implicitly connected with the first.
531
     *
532
     * Note: the $polygon operator only supports legacy coordinate pairs and 2d
533
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
534
     *
535
     * @see Builder::geoWithinPolygon()
536
     * @see http://docs.mongodb.org/manual/reference/operator/polygon/
537
     * @param array $point1    First point of the polygon
538
     * @param array $point2    Second point of the polygon
539
     * @param array $point3    Third point of the polygon
540
     * @param array ...$points Additional points of the polygon
541
     * @return $this
542
     * @throws \InvalidArgumentException If less than three points are given.
543
     */
544 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...
545
    {
546 1
        $shape = ['$polygon' => func_get_args()];
547
548 1
        return $this->operator('$geoWithin', $shape);
549
    }
550
551
    /**
552
     * Return the current field.
553
     */
554 2
    public function getCurrentField(): ?string
555
    {
556 2
        return $this->currentField;
557
    }
558
559
    /**
560
     * Gets prepared newObj part of expression.
561
     */
562 178
    public function getNewObj(): array
563
    {
564 178
        return $this->dm->getUnitOfWork()
565 178
            ->getDocumentPersister($this->class->name)
566 178
            ->prepareQueryOrNewObj($this->newObj, true);
567
    }
568
569
    /**
570
     * Gets prepared query part of expression.
571
     */
572 254
    public function getQuery(): array
573
    {
574 254
        return $this->dm->getUnitOfWork()
575 254
            ->getDocumentPersister($this->class->name)
576 254
            ->prepareQueryOrNewObj($this->query);
577
    }
578
579
    /**
580
     * Specify $gt criteria for the current field.
581
     *
582
     * @see Builder::gt()
583
     * @see http://docs.mongodb.org/manual/reference/operator/gt/
584
     * @param mixed $value
585
     */
586 2
    public function gt($value): self
587
    {
588 2
        return $this->operator('$gt', $value);
589
    }
590
591
    /**
592
     * Specify $gte criteria for the current field.
593
     *
594
     * @see Builder::gte()
595
     * @see http://docs.mongodb.org/manual/reference/operator/gte/
596
     * @param mixed $value
597
     */
598 2
    public function gte($value): self
599
    {
600 2
        return $this->operator('$gte', $value);
601
    }
602
603
    /**
604
     * Specify $in criteria for the current field.
605
     *
606
     * @see Builder::in()
607
     * @see http://docs.mongodb.org/manual/reference/operator/in/
608
     */
609 31
    public function in(array $values): self
610
    {
611 31
        return $this->operator('$in', array_values($values));
612
    }
613
614
    /**
615
     * Increment the current field.
616
     *
617
     * If the field does not exist, it will be set to this value.
618
     *
619
     * @see Builder::inc()
620
     * @see http://docs.mongodb.org/manual/reference/operator/inc/
621
     * @param float|int $value
622
     */
623 5
    public function inc($value): self
624
    {
625 5
        $this->requiresCurrentField();
626 5
        $this->newObj['$inc'][$this->currentField] = $value;
627 5
        return $this;
628
    }
629
630
    /**
631
     * Checks that the current field includes a reference to the supplied document.
632
     */
633 6
    public function includesReferenceTo(object $document): self
634
    {
635 6
        $this->requiresCurrentField();
636 6
        $mapping = $this->getReferenceMapping();
637 4
        $reference = $this->dm->createReference($document, $mapping);
638 4
        $storeAs = $mapping['storeAs'] ?? null;
639 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...
640
641
        switch ($storeAs) {
642 4
            case ClassMetadata::REFERENCE_STORE_AS_ID:
643 2
                $this->query[$mapping['name']] = $reference;
644 2
                return $this;
645
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

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

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

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