Completed
Push — master ( 87aba0...0e8729 )
by Andreas
25s queued 16s
created

lib/Doctrine/ODM/MongoDB/Query/Builder.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Query;
21
22
use Doctrine\ODM\MongoDB\DocumentManager;
23
use Doctrine\ODM\MongoDB\Hydrator;
24
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
25
26
/**
27
 * Query builder for ODM.
28
 *
29
 * @since       1.0
30
 */
31
class Builder extends \Doctrine\MongoDB\Query\Builder
32
{
33
    /**
34
     * The DocumentManager instance for this query
35
     *
36
     * @var DocumentManager
37
     */
38
    private $dm;
39
40
    /**
41
     * The ClassMetadata instance.
42
     *
43
     * @var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata
44
     */
45
    private $class;
46
47
    /**
48
     * The current field we are operating on.
49
     *
50
     * @todo Change this to private once ODM requires doctrine/mongodb 1.1+
51
     * @var string
52
     */
53
    protected $currentField;
54
55
    /**
56
     * Whether or not to hydrate the data to documents.
57
     *
58
     * @var boolean
59
     */
60
    private $hydrate = true;
61
62
    /**
63
     * Whether or not to refresh the data for documents that are already in the identity map.
64
     *
65
     * @var boolean
66
     */
67
    private $refresh = false;
68
69
    /**
70
     * Array of primer Closure instances.
71
     *
72
     * @var array
73
     */
74
    private $primers = array();
75
76
    /**
77
     * Whether or not to require indexes.
78
     *
79
     * @var bool
80
     */
81
    private $requireIndexes;
82
83
    /**
84
     * Whether or not to register documents in UnitOfWork.
85
     *
86
     * @var bool
87
     */
88
    private $readOnly;
89
90
    /**
91
     * Construct a Builder
92
     *
93
     * @param DocumentManager $dm
94
     * @param string[]|string|null $documentName (optional) an array of document names, the document name, or none
95
     */
96 232
    public function __construct(DocumentManager $dm, $documentName = null)
97
    {
98 232
        $this->dm = $dm;
99 232
        $this->expr = new Expr($dm);
100 232
        if ($documentName !== null) {
101 224
            $this->setDocumentName($documentName);
102
        }
103 231
    }
104
105
    /**
106
     * Set whether or not to require indexes.
107
     *
108
     * @param bool $requireIndexes
109
     * @return $this
110
     *
111
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
112
     */
113 2
    public function requireIndexes($requireIndexes = true)
114
    {
115 2
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
116 2
            'requireIndexes was deprecated in version 1.2 and will be removed altogether in 2.0.',
117 2
            E_USER_DEPRECATED
118
        );
119 2
        $this->requireIndexes = $requireIndexes;
120 2
        return $this;
121
    }
122
123
    /**
124
     * Set the current field to operate on.
125
     *
126
     * @param string $field
127
     * @return $this
128
     */
129 155
    public function field($field)
130
    {
131 155
        $this->currentField = $field;
132 155
        parent::field($field);
133
134 155
        return $this;
135
    }
136
137
    /**
138
     * Use a primer to eagerly load all references in the current field.
139
     *
140
     * If $primer is true or a callable is provided, referenced documents for
141
     * this field will loaded into UnitOfWork immediately after the query is
142
     * executed. This will avoid multiple queries due to lazy initialization of
143
     * Proxy objects.
144
     *
145
     * If $primer is false, no priming will take place. That is also the default
146
     * behavior.
147
     *
148
     * If a custom callable is used, its signature should conform to the default
149
     * Closure defined in {@link ReferencePrimer::__construct()}.
150
     *
151
     * @param boolean|callable $primer
152
     * @return $this
153
     * @throws \InvalidArgumentException If $primer is not boolean or callable
154
     */
155 27
    public function prime($primer = true)
156
    {
157 27
        if ( ! is_bool($primer) && ! is_callable($primer)) {
158 1
            throw new \InvalidArgumentException('$primer is not a boolean or callable');
159
        }
160
161 26
        if ($primer === false) {
162 1
            unset($this->primers[$this->currentField]);
163
164 1
            return $this;
165
        }
166
167 26
        if (array_key_exists('eagerCursor', $this->query) && !$this->query['eagerCursor']) {
168 1
            throw new \BadMethodCallException("Can't call prime() when setting eagerCursor to false");
169
        }
170
171 25
        $this->primers[$this->currentField] = $primer;
172 25
        return $this;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 6
    public function eagerCursor($bool = true)
179
    {
180 6
        if ( ! $bool && ! empty($this->primers)) {
181 1
            throw new \BadMethodCallException("Can't set eagerCursor to false when using reference primers");
182
        }
183
184 5
        return parent::eagerCursor($bool);
185
    }
186
187
188
    /**
189
     * @param bool $bool
190
     * @return $this
191
     */
192 16
    public function hydrate($bool = true)
193
    {
194 16
        $this->hydrate = $bool;
195 16
        return $this;
196
    }
197
198
    /**
199
     * @param bool $bool
200
     * @return $this
201
     */
202 2
    public function readOnly($bool = true)
203
    {
204 2
        $this->readOnly = $bool;
205 2
        return $this;
206
    }
207
208
    /**
209
     * @param bool $bool
210
     * @return $this
211
     */
212 5
    public function refresh($bool = true)
213
    {
214 5
        $this->refresh = $bool;
215 5
        return $this;
216
    }
217
218
    /**
219
     * Change the query type to find and optionally set and change the class being queried.
220
     *
221
     * @param string $documentName
222
     * @return $this
223
     */
224 12
    public function find($documentName = null)
225
    {
226 12
        $this->setDocumentName($documentName);
227 12
        parent::find();
228
229 12
        return $this;
230
    }
231
232
    /**
233
     * @param string $documentName
234
     * @return $this
235
     */
236 13
    public function findAndUpdate($documentName = null)
237
    {
238 13
        $this->setDocumentName($documentName);
239 13
        parent::findAndUpdate();
240
241 13
        return $this;
242
    }
243
244
    /**
245
     * @param bool $bool
246
     * @return $this
247
     */
248 4
    public function returnNew($bool = true)
249
    {
250 4
        $this->refresh(true);
251 4
        parent::returnNew($bool);
252
253 4
        return $this;
254
    }
255
256
    /**
257
     * @param string $documentName
258
     * @return $this
259
     */
260 1
    public function findAndRemove($documentName = null)
261
    {
262 1
        $this->setDocumentName($documentName);
263 1
        parent::findAndRemove();
264
265 1
        return $this;
266
    }
267
268
    /**
269
     * @param string $documentName
270
     * @return $this
271
     *
272
     * @deprecated Deprecated in version 1.2 - use updateOne or updateMany instead
273
     */
274 12
    public function update($documentName = null)
275
    {
276 12
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
277 12
            sprintf('%s was deprecated in version 1.2 - use updateOne or updateMany instead.', __METHOD__),
278 12
            E_USER_DEPRECATED
279
        );
280 12
        $this->setDocumentName($documentName);
281 12
        parent::update();
282
283 12
        return $this;
284
    }
285
286
    /**
287
     * @param string $documentName
288
     * @return $this
289
     */
290
    public function updateOne($documentName = null)
291
    {
292
        $this->setDocumentName($documentName);
293
        parent::updateOne();
294
295
        return $this;
296
    }
297
298
    /**
299
     * @param string $documentName
300
     * @return $this
301
     */
302
    public function updateMany($documentName = null)
303
    {
304
        $this->setDocumentName($documentName);
305
        parent::updateMany();
306
307
        return $this;
308
    }
309
310
    /**
311
     * @param string $documentName
312
     * @return $this
313
     */
314 1
    public function insert($documentName = null)
315
    {
316 1
        $this->setDocumentName($documentName);
317 1
        parent::insert();
318
319 1
        return $this;
320
    }
321
322
    /**
323
     * @param string $documentName
324
     * @return $this
325
     */
326 1
    public function remove($documentName = null)
327
    {
328 1
        $this->setDocumentName($documentName);
329 1
        parent::remove();
330
331 1
        return $this;
332
    }
333
334
    /**
335
     * @param object $document
336
     * @return $this
337
     */
338 11
    public function references($document)
339
    {
340 11
        $this->expr->references($document);
341 9
        return $this;
342
    }
343
344
    /**
345
     * @param object $document
346
     * @return $this
347
     */
348 7
    public function includesReferenceTo($document)
349
    {
350 7
        $this->expr->includesReferenceTo($document);
351 5
        return $this;
352
    }
353
354
    /**
355
     * @inheritdoc
356
     * @deprecated Deprecated in version 1.2 - use Aggregation Builder's group stage instead.
357
     */
358 1
    public function group($keys, array $initial, $reduce = null, array $options = [])
359
    {
360 1
        @trigger_error(
361 1
            sprintf('%s was deprecated in version 1.2 - use Aggregation Builder\'s group stage instead.', __METHOD__),
362 1
            E_USER_DEPRECATED
363
        );
364 1
        return parent::group($keys, $initial, $reduce, $options);
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     *
370
     * @deprecated in version 1.2 - use setReadPreference instead.
371
     */
372 3
    public function slaveOkay($bool = true)
373
    {
374 3
        @trigger_error(
375 3
            sprintf('%s was deprecated in version 1.2 - use setReadPreference instead.'),
376 3
            E_USER_DEPRECATED
377
        );
378 3
        return parent::slaveOkay($bool);
379
    }
380
381
    /**
382
     * Gets the Query executable.
383
     *
384
     * @param array $options
385
     * @return Query $query
386
     */
387 201
    public function getQuery(array $options = array())
388
    {
389 201
        if ($this->query['type'] === Query::TYPE_MAP_REDUCE) {
390 3
            $this->hydrate = false;
391
        }
392
393 201
        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
394
395 201
        $query = $this->query;
396
397 201
        $query['query'] = $this->expr->getQuery();
398 201
        $query['query'] = $documentPersister->addDiscriminatorToPreparedQuery($query['query']);
399 201
        $query['query'] = $documentPersister->addFilterToPreparedQuery($query['query']);
400
401 201
        $query['newObj'] = $this->expr->getNewObj();
402
403 201
        if (isset($query['distinct'])) {
404 2
            $query['distinct'] = $documentPersister->prepareFieldName($query['distinct']);
405
        }
406
407 201
        if ($this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && ! empty($query['upsert']) &&
408 3
            (empty($query['query'][$this->class->discriminatorField]) || is_array($query['query'][$this->class->discriminatorField]))) {
409 1
            throw new \InvalidArgumentException('Upsert query that is to be performed on discriminated document does not have single ' .
410 1
                'discriminator. Either not use base class or set \'' . $this->class->discriminatorField . '\' field manually.');
411
        }
412
413 200
        if ( ! empty($query['select'])) {
414 14
            $query['select'] = $documentPersister->prepareSortOrProjection($query['select']);
415 14
            if ($this->hydrate && $this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION
416 2
                && ! isset($query['select'][$this->class->discriminatorField])) {
417
                $includeMode = 0 < count(array_filter($query['select'], function($mode) { return $mode == 1; }));
418 2
                if ($includeMode && ! isset($query['select'][$this->class->discriminatorField])) {
419 1
                    $query['select'][$this->class->discriminatorField] = 1;
420
                }
421
            }
422
        }
423
424 200
        if (isset($query['sort'])) {
425 28
            $query['sort'] = $documentPersister->prepareSortOrProjection($query['sort']);
426
        }
427
428 200
        if ($this->class->slaveOkay) {
429 1
            $query['slaveOkay'] = $this->class->slaveOkay;
430
        }
431
432 200
        return new Query(
433 200
            $this->dm,
434 200
            $this->class,
435 200
            $this->collection,
436
            $query,
437
            $options,
438 200
            $this->hydrate,
439 200
            $this->refresh,
440 200
            $this->primers,
441 200
            $this->requireIndexes,
442 200
            $this->readOnly
443
        );
444
    }
445
446
    /**
447
     * Create a new Expr instance that can be used as an expression with the Builder
448
     *
449
     * @return Expr $expr
450
     */
451 25
    public function expr()
452
    {
453 25
        $expr = new Expr($this->dm);
454 25
        $expr->setClassMetadata($this->class);
455
456 25
        return $expr;
457
    }
458
459
    /**
460
     * @param string[]|string $documentName an array of document names or just one.
461
     */
462 231
    private function setDocumentName($documentName)
463
    {
464 231
        if (is_array($documentName)) {
465 2
            $documentNames = $documentName;
466 2
            $documentName = $documentNames[0];
467
468 2
            $metadata = $this->dm->getClassMetadata($documentName);
469 2
            $discriminatorField = $metadata->discriminatorField;
470 2
            $discriminatorValues = $this->getDiscriminatorValues($documentNames);
471
472
            // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
473 1 View Code Duplication
            if ($metadata->defaultDiscriminatorValue && array_search($metadata->defaultDiscriminatorValue, $discriminatorValues) !== false) {
474 1
                $discriminatorValues[] = null;
475
            }
476
477 1
            $this->field($discriminatorField)->in($discriminatorValues);
478
        }
479
480 230
        if ($documentName !== null) {
481 230
            $this->collection = $this->dm->getDocumentCollection($documentName);
482 230
            $this->class = $this->dm->getClassMetadata($documentName);
483
484
            // Expr also needs to know
485 230
            $this->expr->setClassMetadata($this->class);
486
        }
487 230
    }
488
489
    /**
490
     * Get Discriminator Values
491
     *
492
     * @param \Iterator|array $classNames
493
     * @return array an array of discriminatorValues (mixed type)
494
     * @throws \InvalidArgumentException if the number of found collections > 1
495
     */
496 2
    private function getDiscriminatorValues($classNames)
497
    {
498 2
        $discriminatorValues = array();
499 2
        $collections = array();
500 2
        foreach ($classNames as $className) {
501 2
            $class = $this->dm->getClassMetadata($className);
502 2
            $discriminatorValues[] = $class->discriminatorValue;
503 2
            $key = $this->dm->getDocumentDatabase($className)->getName() . '.' . $class->getCollection();
504 2
            $collections[$key] = $key;
505
        }
506 2
        if (count($collections) > 1) {
507 1
            throw new \InvalidArgumentException('Documents involved are not all mapped to the same database collection.');
508
        }
509 1
        return $discriminatorValues;
510
    }
511
}
512