Completed
Pull Request — master (#1726)
by Andreas
12:19
created

Expr   D

Complexity

Total Complexity 140

Size/Duplication

Total Lines 1446
Duplicated Lines 15.7 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 84.1%

Importance

Changes 0
Metric Value
wmc 140
lcom 1
cbo 7
dl 227
loc 1446
ccs 291
cts 346
cp 0.841
rs 4.4102
c 0
b 0
f 0

75 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A rename() 0 6 1
B set() 0 23 4
A setClassMetadata() 0 4 1
A setNewObj() 0 5 1
A setOnInsert() 0 7 1
A setQuery() 0 5 1
A size() 0 4 1
A slice() 0 4 1
A sort() 0 6 2
A text() 0 5 1
B type() 0 30 2
A unsetField() 0 6 1
A where() 0 5 1
C getReferenceMapping() 0 26 8
A normalizeSortOrder() 0 8 3
A requiresCurrentField() 0 6 2
C wrapEqualityCriteria() 0 26 8
A addAnd() 18 18 3
A addNor() 13 13 3
A addOr() 13 13 3
A addToSet() 10 10 2
A all() 0 4 1
A bit() 0 6 1
A bitAnd() 0 4 1
A bitOr() 0 4 1
A bitsAllClear() 0 5 1
A bitsAllSet() 0 5 1
A bitsAnyClear() 0 5 1
A bitsAnySet() 0 5 1
A bitXor() 0 4 1
A caseSensitive() 15 15 4
A comment() 0 5 1
A currentDate() 0 10 2
A diacriticSensitive() 15 15 4
A each() 0 4 1
A elemMatch() 0 4 2
A equals() 0 9 2
A exists() 0 4 1
A field() 0 5 1
A geoIntersects() 8 8 2
A geoWithin() 8 8 2
A geoWithinBox() 0 6 1
A geoWithinCenter() 0 6 1
A geoWithinCenterSphere() 0 6 1
A geoWithinPolygon() 0 6 1
A getCurrentField() 0 4 1
A getNewObj() 0 6 1
A getQuery() 0 6 1
A gt() 0 4 1
A gte() 0 4 1
A in() 0 4 1
A inc() 0 6 1
D includesReferenceTo() 40 40 9
A language() 0 10 2
A lt() 0 4 1
A lte() 0 4 1
A max() 0 6 1
A min() 0 6 1
A mod() 0 4 1
A mul() 0 6 1
A near() 12 12 3
A nearSphere() 12 12 3
A not() 0 4 2
A notEqual() 0 4 1
A notIn() 0 4 1
A operator() 0 11 2
A popFirst() 0 6 1
A popLast() 0 6 1
A position() 0 4 1
A pull() 10 10 2
A pullAll() 0 6 1
A push() 13 13 2
A range() 0 4 1
D references() 40 40 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Expr often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Expr, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Doctrine\ODM\MongoDB\Query;
4
5
use Doctrine\ODM\MongoDB\DocumentManager;
6
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
7
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
8
use Doctrine\ODM\MongoDB\Mapping\MappingException;
9
use GeoJson\Geometry\Geometry;
10
use GeoJson\Geometry\Point;
11
12
/**
13
 * Query expression builder for ODM.
14
 *
15
 * @since       1.0
16
 */
17
class Expr
18
{
19
    /**
20
     * The query criteria array.
21
     *
22
     * @var array
23
     */
24
    private $query = [];
25
26
    /**
27
     * The "new object" array containing either a full document or a number of
28
     * atomic update operators.
29
     *
30
     * @see docs.mongodb.org/manual/reference/method/db.collection.update/#update-parameter
31
     * @var array
32
     */
33
    private $newObj = [];
34
35
    /**
36
     * The current field we are operating on.
37
     *
38
     * @var string
39
     */
40
    private $currentField;
41
42
    /**
43
     * The DocumentManager instance for this query
44
     *
45
     * @var DocumentManager
46
     */
47
    private $dm;
48
49
    /**
50
     * The ClassMetadata instance for the document being queried
51
     *
52
     * @var ClassMetadata
53
     */
54
    private $class;
55
56
    /**
57
     * @param DocumentManager $dm
58
     */
59 391
    public function __construct(DocumentManager $dm)
60
    {
61 391
        $this->dm = $dm;
62 391
    }
63
64
    /**
65
     * Add one or more $and clauses to the current query.
66
     *
67
     * @see Builder::addAnd()
68
     * @see http://docs.mongodb.org/manual/reference/operator/and/
69
     * @param array|Expr $expression
70
     * @param array|Expr ...$expressions
71
     * @return $this
72
     */
73 3 View Code Duplication
    public function addAnd($expression, ...$expressions)
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...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
74
    {
75 3
        if (! isset($this->query['$and'])) {
76 3
            $this->query['$and'] = [];
77
        }
78
79 3
        $this->query['$and'] = array_merge(
80 3
            $this->query['$and'],
81 3
            array_map(
82 3
                function ($expression) {
83 3
                    return $expression instanceof Expr ? $expression->getQuery() : $expression;
84 3
                },
85 3
                func_get_args()
86
            )
87
        );
88
89 3
        return $this;
90
    }
91
92
    /**
93
     * Add one or more $nor clauses to the current query.
94
     *
95
     * @see Builder::addNor()
96
     * @see http://docs.mongodb.org/manual/reference/operator/nor/
97
     * @param array|Expr $expression
98
     * @param array|Expr ...$expressions
99
     * @return $this
100
     */
101 1 View Code Duplication
    public function addNor($expression, ...$expressions)
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...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
    {
103 1
        if (! isset($this->query['$nor'])) {
104 1
            $this->query['$nor'] = [];
105
        }
106
107 1
        $this->query['$nor'] = array_merge(
108 1
            $this->query['$nor'],
109
            array_map(function ($expression) { return $expression instanceof Expr ? $expression->getQuery() : $expression; }, func_get_args())
110
        );
111
112 1
        return $this;
113
    }
114
115
    /**
116
     * Add one or more $or clauses to the current query.
117
     *
118
     * @see Builder::addOr()
119
     * @see http://docs.mongodb.org/manual/reference/operator/or/
120
     * @param array|Expr $expression
121
     * @param array|Expr ...$expressions
122
     * @return $this
123
     */
124 5 View Code Duplication
    public function addOr($expression, ...$expressions)
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...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126 5
        if (! isset($this->query['$or'])) {
127 5
            $this->query['$or'] = [];
128
        }
129
130 5
        $this->query['$or'] = array_merge(
131 5
            $this->query['$or'],
132
            array_map(function ($expression) { return $expression instanceof Expr ? $expression->getQuery() : $expression; }, func_get_args())
133
        );
134
135 5
        return $this;
136
    }
137
138
    /**
139
     * Append one or more values to the current array field only if they do not
140
     * already exist in the array.
141
     *
142
     * If the field does not exist, it will be set to an array containing the
143
     * unique value(s) in the argument. If the field is not an array, the query
144
     * will yield an error.
145
     *
146
     * Multiple values may be specified by provided an Expr object and using
147
     * {@link Expr::each()}.
148
     *
149
     * @see Builder::addToSet()
150
     * @see http://docs.mongodb.org/manual/reference/operator/addToSet/
151
     * @see http://docs.mongodb.org/manual/reference/operator/each/
152
     * @param mixed|Expr $valueOrExpression
153
     * @return $this
154
     */
155 5 View Code Duplication
    public function addToSet($valueOrExpression)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
    {
157 5
        if ($valueOrExpression instanceof Expr) {
158 1
            $valueOrExpression = $valueOrExpression->getQuery();
159
        }
160
161 5
        $this->requiresCurrentField();
162 5
        $this->newObj['$addToSet'][$this->currentField] = $valueOrExpression;
163 5
        return $this;
164
    }
165
166
    /**
167
     * Specify $all criteria for the current field.
168
     *
169
     * @see Builder::all()
170
     * @see http://docs.mongodb.org/manual/reference/operator/all/
171
     * @param array $values
172
     * @return $this
173
     */
174 2
    public function all(array $values)
175
    {
176 2
        return $this->operator('$all', (array) $values);
177
    }
178
179
    /**
180
     * Apply a bitwise operation on the current field
181
     *
182
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
183
     * @param string $operator
184
     * @param int $value
185
     * @return $this
186
     */
187 3
    protected function bit($operator, $value)
188
    {
189 3
        $this->requiresCurrentField();
190 3
        $this->newObj['$bit'][$this->currentField][$operator] = $value;
191 3
        return $this;
192
    }
193
194
    /**
195
     * Apply a bitwise and operation on the current field.
196
     *
197
     * @see Builder::bitAnd()
198
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
199
     * @param int $value
200
     * @return $this
201
     */
202 1
    public function bitAnd($value)
203
    {
204 1
        return $this->bit('and', $value);
205
    }
206
207
    /**
208
     * Apply a bitwise or operation on the current field.
209
     *
210
     * @see Builder::bitOr()
211
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
212
     * @param int $value
213
     * @return $this
214
     */
215 1
    public function bitOr($value)
216
    {
217 1
        return $this->bit('or', $value);
218
    }
219
220
    /**
221
     * Matches documents where all of the bit positions given by the query are
222
     * clear.
223
     *
224
     * @see Builder::bitsAllClear()
225
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllClear/
226
     * @param int|array|\MongoBinData $value
227
     * @return $this
228
     */
229
    public function bitsAllClear($value)
230
    {
231
        $this->requiresCurrentField();
232
        return $this->operator('$bitsAllClear', $value);
233
    }
234
235
    /**
236
     * Matches documents where all of the bit positions given by the query are
237
     * set.
238
     *
239
     * @see Builder::bitsAllSet()
240
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAllSet/
241
     * @param int|array|\MongoBinData $value
242
     * @return $this
243
     */
244
    public function bitsAllSet($value)
245
    {
246
        $this->requiresCurrentField();
247
        return $this->operator('$bitsAllSet', $value);
248
    }
249
250
    /**
251
     * Matches documents where any of the bit positions given by the query are
252
     * clear.
253
     *
254
     * @see Builder::bitsAnyClear()
255
     * @see https://docs.mongodb.org/manual/reference/operator/query/bitsAnyClear/
256
     * @param int|array|\MongoBinData $value
257
     * @return $this
258
     */
259
    public function bitsAnyClear($value)
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|\MongoBinData $value
272
     * @return $this
273
     */
274
    public function bitsAnySet($value)
275
    {
276
        $this->requiresCurrentField();
277
        return $this->operator('$bitsAnySet', $value);
278
    }
279
280
    /**
281
     * Apply a bitwise xor operation on the current field.
282
     *
283
     * @see Builder::bitXor()
284
     * @see http://docs.mongodb.org/manual/reference/operator/update/bit/
285
     * @param int $value
286
     * @return $this
287
     */
288 1
    public function bitXor($value)
289
    {
290 1
        return $this->bit('xor', $value);
291
    }
292
293
    /**
294
     * A boolean flag to enable or disable case sensitive search for $text
295
     * criteria.
296
     *
297
     * This method must be called after text().
298
     *
299
     * @see Builder::caseSensitive()
300
     * @see http://docs.mongodb.org/manual/reference/operator/text/
301
     * @param bool $caseSensitive
302
     * @return $this
303
     * @throws \BadMethodCallException if the query does not already have $text criteria
304
     *
305
     * @since 1.3
306
     */
307 3 View Code Duplication
    public function caseSensitive($caseSensitive)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
308
    {
309 3
        if ( ! isset($this->query['$text'])) {
310 1
            throw new \BadMethodCallException('This method requires a $text operator (call text() first)');
311
        }
312
313
        // Remove caseSensitive option to keep support for older database versions
314 2
        if ($caseSensitive) {
315 2
            $this->query['$text']['$caseSensitive'] = true;
316 1
        } elseif (isset($this->query['$text']['$caseSensitive'])) {
317 1
            unset($this->query['$text']['$caseSensitive']);
318
        }
319
320 2
        return $this;
321
    }
322
323
    /**
324
     * Associates a comment to any expression taking a query predicate.
325
     *
326
     * @see Builder::comment()
327
     * @see http://docs.mongodb.org/manual/reference/operator/query/comment/
328
     * @param string $comment
329
     * @return $this
330
     */
331
    public function comment($comment)
332
    {
333
        $this->query['$comment'] = $comment;
334
        return $this;
335
    }
336
337
    /**
338
     * Sets the value of the current field to the current date, either as a date or a timestamp.
339
     *
340
     * @see Builder::currentDate()
341
     * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/
342
     * @param string $type
343
     * @return $this
344
     * @throws \InvalidArgumentException if an invalid type is given
345
     */
346 3
    public function currentDate($type = 'date')
347
    {
348 3
        if (! in_array($type, ['date', 'timestamp'])) {
349 1
            throw new \InvalidArgumentException('Type for currentDate operator must be date or timestamp.');
350
        }
351
352 2
        $this->requiresCurrentField();
353 2
        $this->newObj['$currentDate'][$this->currentField]['$type'] = $type;
354 2
        return $this;
355
    }
356
357
    /**
358
     * A boolean flag to enable or disable diacritic sensitive search for $text
359
     * criteria.
360
     *
361
     * This method must be called after text().
362
     *
363
     * @see Builder::diacriticSensitive()
364
     * @see http://docs.mongodb.org/manual/reference/operator/text/
365
     * @param bool $diacriticSensitive
366
     * @return $this
367
     * @throws \BadMethodCallException if the query does not already have $text criteria
368
     *
369
     * @since 1.3
370
     */
371 3 View Code Duplication
    public function diacriticSensitive($diacriticSensitive)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373 3
        if ( ! isset($this->query['$text'])) {
374 1
            throw new \BadMethodCallException('This method requires a $text operator (call text() first)');
375
        }
376
377
        // Remove diacriticSensitive option to keep support for older database versions
378 2
        if ($diacriticSensitive) {
379 2
            $this->query['$text']['$diacriticSensitive'] = true;
380 1
        } elseif (isset($this->query['$text']['$diacriticSensitive'])) {
381 1
            unset($this->query['$text']['$diacriticSensitive']);
382
        }
383
384 2
        return $this;
385
    }
386
387
    /**
388
     * Add $each criteria to the expression for a $push operation.
389
     *
390
     * @see Expr::push()
391
     * @see http://docs.mongodb.org/manual/reference/operator/each/
392
     * @param array $values
393
     * @return $this
394
     */
395 4
    public function each(array $values)
396
    {
397 4
        return $this->operator('$each', $values);
398
    }
399
400
    /**
401
     * Specify $elemMatch criteria for the current field.
402
     *
403
     * @see Builder::elemMatch()
404
     * @see http://docs.mongodb.org/manual/reference/operator/elemMatch/
405
     * @param array|Expr $expression
406
     * @return $this
407
     */
408 4
    public function elemMatch($expression)
409
    {
410 4
        return $this->operator('$elemMatch', $expression instanceof Expr ? $expression->getQuery() : $expression);
411
    }
412
413
    /**
414
     * Specify an equality match for the current field.
415
     *
416
     * @see Builder::equals()
417
     * @param mixed $value
418
     * @return $this
419
     */
420 100
    public function equals($value)
421
    {
422 100
        if ($this->currentField) {
423 99
            $this->query[$this->currentField] = $value;
424
        } else {
425 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...
426
        }
427 100
        return $this;
428
    }
429
430
    /**
431
     * Specify $exists criteria for the current field.
432
     *
433
     * @see Builder::exists()
434
     * @see http://docs.mongodb.org/manual/reference/operator/exists/
435
     * @param boolean $bool
436
     * @return $this
437
     */
438 5
    public function exists($bool)
439
    {
440 5
        return $this->operator('$exists', (boolean) $bool);
441
    }
442
443
    /**
444
     * Set the current field for building the expression.
445
     *
446
     * @see Builder::field()
447
     * @param string $field
448
     * @return $this
449
     */
450 191
    public function field($field)
451
    {
452 191
        $this->currentField = (string) $field;
453 191
        return $this;
454
    }
455
456
    /**
457
     * Add $geoIntersects criteria with a GeoJSON geometry to the expression.
458
     *
459
     * The geometry parameter GeoJSON object or an array corresponding to the
460
     * geometry's JSON representation.
461
     *
462
     * @see Builder::geoIntersects()
463
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
464
     * @param array|Geometry $geometry
465
     * @return $this
466
     */
467 2 View Code Duplication
    public function geoIntersects($geometry)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
    {
469 2
        if ($geometry instanceof Geometry) {
470 1
            $geometry = $geometry->jsonSerialize();
471
        }
472
473 2
        return $this->operator('$geoIntersects', ['$geometry' => $geometry]);
474
    }
475
476
    /**
477
     * Add $geoWithin criteria with a GeoJSON geometry to the expression.
478
     *
479
     * The geometry parameter GeoJSON object or an array corresponding to the
480
     * geometry's JSON representation.
481
     *
482
     * @see Builder::geoWithin()
483
     * @see http://docs.mongodb.org/manual/reference/operator/geoIntersects/
484
     * @param array|Geometry $geometry
485
     * @return $this
486
     */
487 2 View Code Duplication
    public function geoWithin($geometry)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
488
    {
489 2
        if ($geometry instanceof Geometry) {
490 1
            $geometry = $geometry->jsonSerialize();
491
        }
492
493 2
        return $this->operator('$geoWithin', ['$geometry' => $geometry]);
494
    }
495
496
    /**
497
     * Add $geoWithin criteria with a $box shape to the expression.
498
     *
499
     * A rectangular polygon will be constructed from a pair of coordinates
500
     * corresponding to the bottom left and top right corners.
501
     *
502
     * Note: the $box operator only supports legacy coordinate pairs and 2d
503
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
504
     *
505
     * @see Builder::geoWithinBox()
506
     * @see http://docs.mongodb.org/manual/reference/operator/box/
507
     * @param float $x1
508
     * @param float $y1
509
     * @param float $x2
510
     * @param float $y2
511
     * @return $this
512
     */
513 1
    public function geoWithinBox($x1, $y1, $x2, $y2)
514
    {
515 1
        $shape = ['$box' => [[$x1, $y1], [$x2, $y2]]];
516
517 1
        return $this->operator('$geoWithin', $shape);
518
    }
519
520
    /**
521
     * Add $geoWithin criteria with a $center shape to the expression.
522
     *
523
     * Note: the $center operator only supports legacy coordinate pairs and 2d
524
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
525
     *
526
     * @see Builider::geoWithinCenter()
527
     * @see http://docs.mongodb.org/manual/reference/operator/center/
528
     * @param float $x
529
     * @param float $y
530
     * @param float $radius
531
     * @return $this
532
     */
533 1
    public function geoWithinCenter($x, $y, $radius)
534
    {
535 1
        $shape = ['$center' => [[$x, $y], $radius]];
536
537 1
        return $this->operator('$geoWithin', $shape);
538
    }
539
540
    /**
541
     * Add $geoWithin criteria with a $centerSphere shape to the expression.
542
     *
543
     * Note: the $centerSphere operator supports both 2d and 2dsphere indexes.
544
     *
545
     * @see Builder::geoWithinCenterSphere()
546
     * @see http://docs.mongodb.org/manual/reference/operator/centerSphere/
547
     * @param float $x
548
     * @param float $y
549
     * @param float $radius
550
     * @return $this
551
     */
552 1
    public function geoWithinCenterSphere($x, $y, $radius)
553
    {
554 1
        $shape = ['$centerSphere' => [[$x, $y], $radius]];
555
556 1
        return $this->operator('$geoWithin', $shape);
557
    }
558
559
    /**
560
     * Add $geoWithin criteria with a $polygon shape to the expression.
561
     *
562
     * Point coordinates are in x, y order (easting, northing for projected
563
     * coordinates, longitude, latitude for geographic coordinates).
564
     *
565
     * The last point coordinate is implicitly connected with the first.
566
     *
567
     * Note: the $polygon operator only supports legacy coordinate pairs and 2d
568
     * indexes. This cannot be used with 2dsphere indexes and GeoJSON shapes.
569
     *
570
     * @see Builder::geoWithinPolygon()
571
     * @see http://docs.mongodb.org/manual/reference/operator/polygon/
572
     * @param array $point1 First point of the polygon
573
     * @param array $point2 Second point of the polygon
574
     * @param array $point3 Third point of the polygon
575
     * @param array ...$points Additional points of the polygon
576
     * @return $this
577
     * @throws \InvalidArgumentException if less than three points are given
578
     */
579 1
    public function geoWithinPolygon($point1, $point2, $point3, ...$points)
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...
580
    {
581 1
        $shape = ['$polygon' => func_get_args()];
582
583 1
        return $this->operator('$geoWithin', $shape);
584
    }
585
586
    /**
587
     * Return the current field.
588
     *
589
     * @return string
590
     */
591 2
    public function getCurrentField()
592
    {
593 2
        return $this->currentField;
594
    }
595
596
    /**
597
     * Gets prepared newObj part of expression.
598
     *
599
     * @return array
600
     */
601 174
    public function getNewObj()
602
    {
603 174
        return $this->dm->getUnitOfWork()
604 174
            ->getDocumentPersister($this->class->name)
605 174
            ->prepareQueryOrNewObj($this->newObj, true);
606
    }
607
608
    /**
609
     * Gets prepared query part of expression.
610
     *
611
     * @return array
612
     */
613 250
    public function getQuery()
614
    {
615 250
        return $this->dm->getUnitOfWork()
616 250
            ->getDocumentPersister($this->class->name)
617 250
            ->prepareQueryOrNewObj($this->query);
618
    }
619
620
    /**
621
     * Specify $gt criteria for the current field.
622
     *
623
     * @see Builder::gt()
624
     * @see http://docs.mongodb.org/manual/reference/operator/gt/
625
     * @param mixed $value
626
     * @return $this
627
     */
628 2
    public function gt($value)
629
    {
630 2
        return $this->operator('$gt', $value);
631
    }
632
633
    /**
634
     * Specify $gte criteria for the current field.
635
     *
636
     * @see Builder::gte()
637
     * @see http://docs.mongodb.org/manual/reference/operator/gte/
638
     * @param mixed $value
639
     * @return $this
640
     */
641 2
    public function gte($value)
642
    {
643 2
        return $this->operator('$gte', $value);
644
    }
645
646
    /**
647
     * Specify $in criteria for the current field.
648
     *
649
     * @see Builder::in()
650
     * @see http://docs.mongodb.org/manual/reference/operator/in/
651
     * @param array $values
652
     * @return $this
653
     */
654 31
    public function in(array $values)
655
    {
656 31
        return $this->operator('$in', array_values($values));
657
    }
658
659
    /**
660
     * Increment the current field.
661
     *
662
     * If the field does not exist, it will be set to this value.
663
     *
664
     * @see Builder::inc()
665
     * @see http://docs.mongodb.org/manual/reference/operator/inc/
666
     * @param float|integer $value
667
     * @return $this
668
     */
669 5
    public function inc($value)
670
    {
671 5
        $this->requiresCurrentField();
672 5
        $this->newObj['$inc'][$this->currentField] = $value;
673 5
        return $this;
674
    }
675
676
    /**
677
     * Checks that the current field includes a reference to the supplied document.
678
     *
679
     * @param object $document
680
     * @return Expr
681
     */
682 6 View Code Duplication
    public function includesReferenceTo($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
683
    {
684 6
        $this->requiresCurrentField();
685 6
        $mapping = $this->getReferenceMapping();
686 4
        $reference = $this->dm->createReference($document, $mapping);
687 4
        $storeAs = array_key_exists('storeAs', $mapping) ? $mapping['storeAs'] : null;
688
689
        switch ($storeAs) {
690 4
            case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
691 2
                $this->query[$mapping['name']] = $reference;
692 2
                return $this;
693
                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...
694
695 3
            case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
696
                $keys = ['id' => true];
697
                break;
698
699 3
            case ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF:
700 1
            case ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB:
701 3
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
702
703 3
                if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF) {
704 2
                    unset($keys['$db']);
705
                }
706
707 3
                if (isset($mapping['targetDocument'])) {
708 1
                    unset($keys['$ref'], $keys['$db']);
709
                }
710 3
                break;
711
712
            default:
713
                throw new \InvalidArgumentException("Reference type {$storeAs} is invalid.");
714
        }
715
716 3
        foreach ($keys as $key => $value) {
717 3
            $this->query[$mapping['name']]['$elemMatch'][$key] = $reference[$key];
718
        }
719
720 3
        return $this;
721
    }
722
723
    /**
724
     * Set the $language option for $text criteria.
725
     *
726
     * This method must be called after text().
727
     *
728
     * @see Builder::language()
729
     * @see http://docs.mongodb.org/manual/reference/operator/text/
730
     * @param string $language
731
     * @return $this
732
     * @throws \BadMethodCallException if the query does not already have $text criteria
733
     */
734 2
    public function language($language)
735
    {
736 2
        if ( ! isset($this->query['$text'])) {
737 1
            throw new \BadMethodCallException('This method requires a $text operator (call text() first)');
738
        }
739
740 1
        $this->query['$text']['$language'] = (string) $language;
741
742 1
        return $this;
743
    }
744
745
    /**
746
     * Specify $lt criteria for the current field.
747
     *
748
     * @see Builder::lte()
749
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
750
     * @param mixed $value
751
     * @return $this
752
     */
753 4
    public function lt($value)
754
    {
755 4
        return $this->operator('$lt', $value);
756
    }
757
758
    /**
759
     * Specify $lte criteria for the current field.
760
     *
761
     * @see Builder::lte()
762
     * @see http://docs.mongodb.org/manual/reference/operator/lte/
763
     * @param mixed $value
764
     * @return $this
765
     */
766 2
    public function lte($value)
767
    {
768 2
        return $this->operator('$lte', $value);
769
    }
770
771
    /**
772
     * Updates the value of the field to a specified value if the specified value is greater than the current value of the field.
773
     *
774
     * @see Builder::max()
775
     * @see http://docs.mongodb.org/manual/reference/operator/update/max/
776
     * @param mixed $value
777
     * @return $this
778
     */
779
    public function max($value)
780
    {
781
        $this->requiresCurrentField();
782
        $this->newObj['$max'][$this->currentField] = $value;
783
        return $this;
784
    }
785
786
    /**
787
     * Updates the value of the field to a specified value if the specified value is less than the current value of the field.
788
     *
789
     * @see Builder::min()
790
     * @see http://docs.mongodb.org/manual/reference/operator/update/min/
791
     * @param mixed $value
792
     * @return $this
793
     */
794
    public function min($value)
795
    {
796
        $this->requiresCurrentField();
797
        $this->newObj['$min'][$this->currentField] = $value;
798
        return $this;
799
    }
800
801
    /**
802
     * Specify $mod criteria for the current field.
803
     *
804
     * @see Builder::mod()
805
     * @see http://docs.mongodb.org/manual/reference/operator/mod/
806
     * @param float|integer $divisor
807
     * @param float|integer $remainder
808
     * @return $this
809
     */
810
    public function mod($divisor, $remainder = 0)
811
    {
812
        return $this->operator('$mod', [$divisor, $remainder]);
813
    }
814
815
    /**
816
     * Multiply the current field.
817
     *
818
     * If the field does not exist, it will be set to 0.
819
     *
820
     * @see Builder::mul()
821
     * @see http://docs.mongodb.org/manual/reference/operator/mul/
822
     * @param float|integer $value
823
     * @return $this
824
     */
825
    public function mul($value)
826
    {
827
        $this->requiresCurrentField();
828
        $this->newObj['$mul'][$this->currentField] = $value;
829
        return $this;
830
    }
831
832
    /**
833
     * Add $near criteria to the expression.
834
     *
835
     * A GeoJSON point may be provided as the first and only argument for
836
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
837
     * an array corresponding to the point's JSON representation.
838
     *
839
     * @see Builder::near()
840
     * @see http://docs.mongodb.org/manual/reference/operator/near/
841
     * @param float|array|Point $x
842
     * @param float $y
843
     * @return $this
844
     */
845 3 View Code Duplication
    public function near($x, $y = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
846
    {
847 3
        if ($x instanceof Point) {
848 1
            $x = $x->jsonSerialize();
849
        }
850
851 3
        if (is_array($x)) {
852 2
            return $this->operator('$near', ['$geometry' => $x]);
853
        }
854
855 1
        return $this->operator('$near', [$x, $y]);
856
    }
857
858
    /**
859
     * Add $nearSphere criteria to the expression.
860
     *
861
     * A GeoJSON point may be provided as the first and only argument for
862
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
863
     * an array corresponding to the point's JSON representation.
864
     *
865
     * @see Builder::nearSphere()
866
     * @see http://docs.mongodb.org/manual/reference/operator/nearSphere/
867
     * @param float|array|Point $x
868
     * @param float $y
869
     * @return $this
870
     */
871 3 View Code Duplication
    public function nearSphere($x, $y = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
872
    {
873 3
        if ($x instanceof Point) {
874 1
            $x = $x->jsonSerialize();
875
        }
876
877 3
        if (is_array($x)) {
878 2
            return $this->operator('$nearSphere', ['$geometry' => $x]);
879
        }
880
881 1
        return $this->operator('$nearSphere', [$x, $y]);
882
    }
883
884
    /**
885
     * Negates an expression for the current field.
886
     *
887
     * @see Builder::not()
888
     * @see http://docs.mongodb.org/manual/reference/operator/not/
889
     * @param array|Expr $expression
890
     * @return $this
891
     */
892 2
    public function not($expression)
893
    {
894 2
        return $this->operator('$not', $expression instanceof Expr ? $expression->getQuery() : $expression);
895
    }
896
897
    /**
898
     * Specify $ne criteria for the current field.
899
     *
900
     * @see Builder::notEqual()
901
     * @see http://docs.mongodb.org/manual/reference/operator/ne/
902
     * @param mixed $value
903
     * @return $this
904
     */
905 6
    public function notEqual($value)
906
    {
907 6
        return $this->operator('$ne', $value);
908
    }
909
910
    /**
911
     * Specify $nin criteria for the current field.
912
     *
913
     * @see Builder::notIn()
914
     * @see http://docs.mongodb.org/manual/reference/operator/nin/
915
     * @param array $values
916
     * @return $this
917
     */
918 6
    public function notIn(array $values)
919
    {
920 6
        return $this->operator('$nin', array_values($values));
921
    }
922
923
    /**
924
     * Defines an operator and value on the expression.
925
     *
926
     * If there is a current field, the operator will be set on it; otherwise,
927
     * the operator is set at the top level of the query.
928
     *
929
     * @param string $operator
930
     * @param mixed $value
931
     * @return $this
932
     */
933 81
    public function operator($operator, $value)
934
    {
935 81
        $this->wrapEqualityCriteria();
936
937 81
        if ($this->currentField) {
938 56
            $this->query[$this->currentField][$operator] = $value;
939
        } else {
940 27
            $this->query[$operator] = $value;
941
        }
942 81
        return $this;
943
    }
944
945
    /**
946
     * Remove the first element from the current array field.
947
     *
948
     * @see Builder::popFirst()
949
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
950
     * @return $this
951
     */
952 1
    public function popFirst()
953
    {
954 1
        $this->requiresCurrentField();
955 1
        $this->newObj['$pop'][$this->currentField] = 1;
956 1
        return $this;
957
    }
958
959
    /**
960
     * Remove the last element from the current array field.
961
     *
962
     * @see Builder::popLast()
963
     * @see http://docs.mongodb.org/manual/reference/operator/pop/
964
     * @return $this
965
     */
966
    public function popLast()
967
    {
968
        $this->requiresCurrentField();
969
        $this->newObj['$pop'][$this->currentField] = -1;
970
        return $this;
971
    }
972
973
    /**
974
     * Add $position criteria to the expression for a $push operation.
975
     *
976
     * This is useful in conjunction with {@link Expr::each()} for a
977
     * {@link Expr::push()} operation.
978
     *
979
     * @see http://docs.mongodb.org/manual/reference/operator/update/position/
980
     * @param integer $position
981
     * @return $this
982
     */
983 1
    public function position($position)
984
    {
985 1
        return $this->operator('$position', $position);
986
    }
987
988
    /**
989
     * Remove all elements matching the given value or expression from the
990
     * current array field.
991
     *
992
     * @see Builder::pull()
993
     * @see http://docs.mongodb.org/manual/reference/operator/pull/
994
     * @param mixed|Expr $valueOrExpression
995
     * @return $this
996
     */
997 2 View Code Duplication
    public function pull($valueOrExpression)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
998
    {
999 2
        if ($valueOrExpression instanceof Expr) {
1000 1
            $valueOrExpression = $valueOrExpression->getQuery();
1001
        }
1002
1003 2
        $this->requiresCurrentField();
1004 2
        $this->newObj['$pull'][$this->currentField] = $valueOrExpression;
1005 2
        return $this;
1006
    }
1007
1008
    /**
1009
     * Remove all elements matching any of the given values from the current
1010
     * array field.
1011
     *
1012
     * @see Builder::pullAll()
1013
     * @see http://docs.mongodb.org/manual/reference/operator/pullAll/
1014
     * @param array $values
1015
     * @return $this
1016
     */
1017
    public function pullAll(array $values)
1018
    {
1019
        $this->requiresCurrentField();
1020
        $this->newObj['$pullAll'][$this->currentField] = $values;
1021
        return $this;
1022
    }
1023
1024
    /**
1025
     * Append one or more values to the current array field.
1026
     *
1027
     * If the field does not exist, it will be set to an array containing the
1028
     * value(s) in the argument. If the field is not an array, the query
1029
     * will yield an error.
1030
     *
1031
     * Multiple values may be specified by providing an Expr object and using
1032
     * {@link Expr::each()}. {@link Expr::slice()} and {@link Expr::sort()} may
1033
     * also be used to limit and order array elements, respectively.
1034
     *
1035
     * @see Builder::push()
1036
     * @see http://docs.mongodb.org/manual/reference/operator/push/
1037
     * @see http://docs.mongodb.org/manual/reference/operator/each/
1038
     * @see http://docs.mongodb.org/manual/reference/operator/slice/
1039
     * @see http://docs.mongodb.org/manual/reference/operator/sort/
1040
     * @param mixed|Expr $valueOrExpression
1041
     * @return $this
1042
     */
1043 8 View Code Duplication
    public function push($valueOrExpression)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1044
    {
1045 8
        if ($valueOrExpression instanceof Expr) {
1046 3
            $valueOrExpression = array_merge(
1047 3
                ['$each' => []],
1048 3
                $valueOrExpression->getQuery()
1049
            );
1050
        }
1051
1052 8
        $this->requiresCurrentField();
1053 8
        $this->newObj['$push'][$this->currentField] = $valueOrExpression;
1054 8
        return $this;
1055
    }
1056
1057
    /**
1058
     * Specify $gte and $lt criteria for the current field.
1059
     *
1060
     * This method is shorthand for specifying $gte criteria on the lower bound
1061
     * and $lt criteria on the upper bound. The upper bound is not inclusive.
1062
     *
1063
     * @see Builder::range()
1064
     * @param mixed $start
1065
     * @param mixed $end
1066
     * @return $this
1067
     */
1068 2
    public function range($start, $end)
1069
    {
1070 2
        return $this->operator('$gte', $start)->operator('$lt', $end);
1071
    }
1072
1073
    /**
1074
     * Checks that the value of the current field is a reference to the supplied document.
1075
     *
1076
     * @param object $document
1077
     * @return Expr
1078
     */
1079 13 View Code Duplication
    public function references($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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