These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Doctrine\ODM\MongoDB\Query; |
||
4 | |||
5 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
6 | use Doctrine\ODM\MongoDB\Iterator\CachingIterator; |
||
7 | use Doctrine\ODM\MongoDB\Iterator\HydratingIterator; |
||
8 | use Doctrine\ODM\MongoDB\Iterator\Iterator; |
||
9 | use Doctrine\ODM\MongoDB\Iterator\PrimingIterator; |
||
10 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
11 | use MongoDB\Collection; |
||
12 | use MongoDB\Driver\Cursor; |
||
13 | use MongoDB\Operation\FindOneAndUpdate; |
||
14 | |||
15 | |||
16 | /** |
||
17 | * ODM Query wraps the raw Doctrine MongoDB queries to add additional functionality |
||
18 | * and to hydrate the raw arrays of data to Doctrine document objects. |
||
19 | * |
||
20 | * @since 1.0 |
||
21 | */ |
||
22 | class Query implements \IteratorAggregate |
||
23 | { |
||
24 | const TYPE_FIND = 1; |
||
25 | const TYPE_FIND_AND_UPDATE = 2; |
||
26 | const TYPE_FIND_AND_REMOVE = 3; |
||
27 | const TYPE_INSERT = 4; |
||
28 | const TYPE_UPDATE = 5; |
||
29 | const TYPE_REMOVE = 6; |
||
30 | const TYPE_GROUP = 7; |
||
31 | const TYPE_MAP_REDUCE = 8; |
||
32 | const TYPE_DISTINCT = 9; |
||
33 | const TYPE_COUNT = 11; |
||
34 | |||
35 | /** |
||
36 | * @deprecated 1.1 Will be removed for 2.0 |
||
37 | */ |
||
38 | const TYPE_GEO_LOCATION = 10; |
||
39 | |||
40 | const HINT_REFRESH = 1; |
||
41 | /** @deprecated */ |
||
42 | const HINT_SLAVE_OKAY = 2; |
||
43 | const HINT_READ_PREFERENCE = 3; |
||
44 | const HINT_READ_ONLY = 5; |
||
45 | |||
46 | /** |
||
47 | * The DocumentManager instance. |
||
48 | * |
||
49 | * @var DocumentManager |
||
50 | */ |
||
51 | private $dm; |
||
52 | |||
53 | /** |
||
54 | * The ClassMetadata instance. |
||
55 | * |
||
56 | * @var ClassMetadata |
||
57 | */ |
||
58 | private $class; |
||
59 | |||
60 | /** |
||
61 | * Whether to hydrate results as document class instances. |
||
62 | * |
||
63 | * @var boolean |
||
64 | */ |
||
65 | private $hydrate = true; |
||
66 | |||
67 | /** |
||
68 | * Array of primer Closure instances. |
||
69 | * |
||
70 | * @var array |
||
71 | */ |
||
72 | private $primers = array(); |
||
73 | |||
74 | /** |
||
75 | * Hints for UnitOfWork behavior. |
||
76 | * |
||
77 | * @var array |
||
78 | */ |
||
79 | private $unitOfWorkHints = array(); |
||
80 | |||
81 | /** |
||
82 | * The Collection instance. |
||
83 | * |
||
84 | * @var Collection |
||
85 | */ |
||
86 | protected $collection; |
||
87 | |||
88 | /** |
||
89 | * Query structure generated by the Builder class. |
||
90 | * |
||
91 | * @var array |
||
92 | */ |
||
93 | private $query; |
||
94 | |||
95 | /** |
||
96 | * @var Iterator |
||
97 | */ |
||
98 | private $iterator; |
||
99 | |||
100 | /** |
||
101 | * Query options |
||
102 | * |
||
103 | * @var array |
||
104 | */ |
||
105 | private $options; |
||
106 | |||
107 | /** |
||
108 | * Constructor. |
||
109 | * |
||
110 | * Please note that $requireIndexes was deprecated in 1.2 and will be removed in 2.0 |
||
111 | * |
||
112 | * @param DocumentManager $dm |
||
113 | * @param ClassMetadata $class |
||
114 | * @param Collection $collection |
||
115 | * @param array $query |
||
116 | * @param array $options |
||
117 | * @param boolean $hydrate |
||
118 | * @param boolean $refresh |
||
119 | * @param array $primers |
||
120 | * @param boolean $readOnly |
||
121 | */ |
||
122 | 159 | public function __construct(DocumentManager $dm, ClassMetadata $class, Collection $collection, array $query = array(), array $options = array(), $hydrate = true, $refresh = false, array $primers = array(), $readOnly = false) |
|
123 | { |
||
124 | 159 | $primers = array_filter($primers); |
|
125 | |||
126 | 159 | if ( ! empty($primers)) { |
|
127 | 22 | $query['eagerCursor'] = true; |
|
128 | } |
||
129 | |||
130 | 159 | if ( ! empty($query['eagerCursor'])) { |
|
131 | 23 | $query['useIdentifierKeys'] = false; |
|
132 | } |
||
133 | |||
134 | 159 | switch ($query['type']) { |
|
135 | 159 | case self::TYPE_FIND: |
|
136 | 36 | case self::TYPE_FIND_AND_UPDATE: |
|
137 | 24 | case self::TYPE_FIND_AND_REMOVE: |
|
138 | 21 | case self::TYPE_INSERT: |
|
139 | 20 | case self::TYPE_UPDATE: |
|
140 | 8 | case self::TYPE_REMOVE: |
|
141 | 6 | case self::TYPE_GROUP: |
|
142 | 6 | case self::TYPE_MAP_REDUCE: |
|
143 | 6 | case self::TYPE_DISTINCT: |
|
144 | 4 | case self::TYPE_COUNT: |
|
145 | 158 | break; |
|
146 | |||
147 | default: |
||
148 | 1 | throw new \InvalidArgumentException('Invalid query type: ' . $query['type']); |
|
149 | } |
||
150 | |||
151 | 158 | $this->collection = $collection; |
|
152 | 158 | $this->query = $query; |
|
153 | 158 | $this->options = $options; |
|
154 | 158 | $this->dm = $dm; |
|
155 | 158 | $this->class = $class; |
|
156 | 158 | $this->hydrate = $hydrate; |
|
157 | 158 | $this->primers = $primers; |
|
158 | |||
159 | 158 | $this->setReadOnly($readOnly); |
|
160 | 158 | $this->setRefresh($refresh); |
|
161 | |||
162 | 158 | if (isset($query['slaveOkay'])) { |
|
163 | $this->unitOfWorkHints[self::HINT_SLAVE_OKAY] = $query['slaveOkay']; |
||
0 ignored issues
–
show
|
|||
164 | } |
||
165 | |||
166 | 158 | if (isset($query['readPreference'])) { |
|
167 | 6 | $this->unitOfWorkHints[self::HINT_READ_PREFERENCE] = $query['readPreference']; |
|
168 | } |
||
169 | 158 | } |
|
170 | |||
171 | 64 | public function __clone() |
|
172 | { |
||
173 | 64 | $this->iterator = null; |
|
174 | 64 | } |
|
175 | |||
176 | /** |
||
177 | * Return an array of information about the query structure for debugging. |
||
178 | * |
||
179 | * The $name parameter may be used to return a specific key from the |
||
180 | * internal $query array property. If omitted, the entire array will be |
||
181 | * returned. |
||
182 | * |
||
183 | * @param string $name |
||
184 | * @return mixed |
||
185 | */ |
||
186 | 26 | public function debug($name = null) |
|
187 | { |
||
188 | 26 | return $name !== null ? $this->query[$name] : $this->query; |
|
189 | } |
||
190 | |||
191 | /** |
||
192 | * Execute the query and returns the results. |
||
193 | * |
||
194 | * @throws \Doctrine\ODM\MongoDB\MongoDBException |
||
195 | * @return Iterator|int|string|array |
||
196 | */ |
||
197 | 119 | public function execute() |
|
198 | { |
||
199 | 119 | $results = $this->runQuery(); |
|
200 | |||
201 | 119 | if ( ! $this->hydrate) { |
|
202 | 9 | return $results; |
|
203 | } |
||
204 | |||
205 | 113 | if ($results instanceof Cursor) { |
|
206 | $results = $this->makeIterator($results); |
||
207 | } |
||
208 | |||
209 | 113 | $uow = $this->dm->getUnitOfWork(); |
|
210 | |||
211 | /* If a single document is returned from a findAndModify command and it |
||
212 | * includes the identifier field, attempt hydration. |
||
213 | */ |
||
214 | 113 | if (($this->query['type'] === self::TYPE_FIND_AND_UPDATE || |
|
215 | 113 | $this->query['type'] === self::TYPE_FIND_AND_REMOVE) && |
|
216 | 113 | is_array($results) && isset($results['_id'])) { |
|
217 | |||
218 | 5 | $results = $uow->getOrCreateDocument($this->class->name, $results, $this->unitOfWorkHints); |
|
219 | |||
220 | 5 | if ( ! empty($this->primers)) { |
|
221 | 1 | $referencePrimer = new ReferencePrimer($this->dm, $uow); |
|
222 | |||
223 | 1 | foreach ($this->primers as $fieldName => $primer) { |
|
224 | 1 | $primer = is_callable($primer) ? $primer : null; |
|
225 | 1 | $referencePrimer->primeReferences($this->class, array($results), $fieldName, $this->unitOfWorkHints, $primer); |
|
226 | } |
||
227 | } |
||
228 | } |
||
229 | |||
230 | 113 | return $results; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * Gets the ClassMetadata instance. |
||
235 | * |
||
236 | * @return ClassMetadata $class |
||
237 | */ |
||
238 | public function getClass() |
||
239 | { |
||
240 | return $this->class; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Gets the DocumentManager instance. |
||
245 | * |
||
246 | * @return DocumentManager $dm |
||
247 | */ |
||
248 | public function getDocumentManager() |
||
249 | { |
||
250 | return $this->dm; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * Execute the query and return its result, which must be an Iterator. |
||
255 | * |
||
256 | * If the query type is not expected to return an Iterator, |
||
257 | * BadMethodCallException will be thrown before executing the query. |
||
258 | * Otherwise, the query will be executed and UnexpectedValueException will |
||
259 | * be thrown if {@link Query::execute()} does not return an Iterator. |
||
260 | * |
||
261 | * @see http://php.net/manual/en/iteratoraggregate.getiterator.php |
||
262 | * @return Iterator |
||
263 | * @throws \BadMethodCallException if the query type would not return an Iterator |
||
264 | * @throws \UnexpectedValueException if the query did not return an Iterator |
||
265 | */ |
||
266 | 84 | public function getIterator() |
|
267 | { |
||
268 | 84 | switch ($this->query['type']) { |
|
269 | 84 | case self::TYPE_FIND: |
|
270 | 6 | case self::TYPE_GROUP: |
|
271 | 6 | case self::TYPE_MAP_REDUCE: |
|
272 | 6 | case self::TYPE_DISTINCT: |
|
273 | 78 | break; |
|
274 | |||
275 | default: |
||
276 | 6 | throw new \BadMethodCallException('Iterator would not be returned for query type: ' . $this->query['type']); |
|
277 | } |
||
278 | |||
279 | 78 | if ($this->iterator === null) { |
|
280 | 78 | $this->iterator = $this->execute(); |
|
281 | } |
||
282 | |||
283 | 78 | return $this->iterator; |
|
284 | } |
||
285 | |||
286 | /** |
||
287 | * Return the query structure. |
||
288 | * |
||
289 | * @return array |
||
290 | */ |
||
291 | 14 | public function getQuery() |
|
292 | { |
||
293 | 14 | return $this->query; |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * Execute the query and return the first result. |
||
298 | * |
||
299 | * @return array|object|null |
||
300 | */ |
||
301 | 64 | public function getSingleResult() |
|
302 | { |
||
303 | 64 | $clonedQuery = clone $this; |
|
304 | 64 | $clonedQuery->query['limit'] = 1; |
|
305 | 64 | return $clonedQuery->getIterator()->current() ?: null; |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * Return the query type. |
||
310 | * |
||
311 | * @return integer |
||
312 | */ |
||
313 | public function getType() |
||
314 | { |
||
315 | return $this->query['type']; |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Sets whether or not to hydrate the documents to objects. |
||
320 | * |
||
321 | * @param boolean $hydrate |
||
322 | */ |
||
323 | public function setHydrate($hydrate) |
||
324 | { |
||
325 | $this->hydrate = (boolean) $hydrate; |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Set whether documents should be registered in UnitOfWork. If document would |
||
330 | * already be managed it will be left intact and new instance returned. |
||
331 | * |
||
332 | * This option has no effect if hydration is disabled. |
||
333 | * |
||
334 | * @param boolean $readOnly |
||
335 | */ |
||
336 | 158 | public function setReadOnly($readOnly) |
|
337 | { |
||
338 | 158 | $this->unitOfWorkHints[Query::HINT_READ_ONLY] = (boolean) $readOnly; |
|
339 | 158 | } |
|
340 | |||
341 | /** |
||
342 | * Set whether to refresh hydrated documents that are already in the |
||
343 | * identity map. |
||
344 | * |
||
345 | * This option has no effect if hydration is disabled. |
||
346 | * |
||
347 | * @param boolean $refresh |
||
348 | */ |
||
349 | 158 | public function setRefresh($refresh) |
|
350 | { |
||
351 | 158 | $this->unitOfWorkHints[Query::HINT_REFRESH] = (boolean) $refresh; |
|
352 | 158 | } |
|
353 | |||
354 | /** |
||
355 | * Execute the query and return its results as an array. |
||
356 | * |
||
357 | * @see IteratorAggregate::toArray() |
||
358 | * @return array |
||
359 | */ |
||
360 | 11 | public function toArray() |
|
361 | { |
||
362 | 11 | return $this->getIterator()->toArray(); |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * Returns an array containing the specified keys and their values from the |
||
367 | * query array, provided they exist and are not null. |
||
368 | * |
||
369 | * @param string $key,... One or more option keys to be read |
||
370 | * @return array |
||
371 | */ |
||
372 | 118 | private function getQueryOptions(/* $key, ... */) |
|
373 | { |
||
374 | 118 | return array_filter( |
|
375 | 118 | array_intersect_key($this->query, array_flip(func_get_args())), |
|
376 | function($value) { return $value !== null; } |
||
377 | ); |
||
378 | } |
||
379 | |||
380 | 105 | private function makeIterator(Cursor $cursor): Iterator |
|
381 | { |
||
382 | 105 | if ($this->hydrate && $this->class) { |
|
383 | 97 | $cursor = new HydratingIterator($cursor, $this->dm->getUnitOfWork(), $this->class, $this->unitOfWorkHints); |
|
384 | } |
||
385 | |||
386 | 105 | $cursor = new CachingIterator($cursor); |
|
387 | |||
388 | 105 | if ( ! empty($this->primers)) { |
|
389 | 20 | $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork()); |
|
390 | 20 | $cursor = new PrimingIterator($cursor, $this->class, $referencePrimer, $this->primers, $this->unitOfWorkHints); |
|
391 | } |
||
392 | |||
393 | 105 | return $cursor; |
|
394 | } |
||
395 | |||
396 | /** |
||
397 | * Returns an array with its keys renamed based on the translation map. |
||
398 | * |
||
399 | * @param array $options Query options |
||
400 | * @return array $rename Translation map (from => to) for renaming keys |
||
401 | */ |
||
402 | 110 | private function renameQueryOptions(array $options, array $rename) |
|
403 | { |
||
404 | 110 | if (empty($options)) { |
|
405 | 41 | return $options; |
|
406 | } |
||
407 | |||
408 | 86 | return array_combine( |
|
409 | 86 | array_map( |
|
410 | function($key) use ($rename) { return $rename[$key] ?? $key; }, |
||
411 | 86 | array_keys($options) |
|
412 | ), |
||
413 | 86 | array_values($options) |
|
414 | ); |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Execute the query and return its result. |
||
419 | * |
||
420 | * The return value will vary based on the query type. Commands with results |
||
421 | * (e.g. aggregate, inline mapReduce) may return an ArrayIterator. Other |
||
422 | * commands and operations may return a status array or a boolean, depending |
||
423 | * on the driver's write concern. Queries and some mapReduce commands will |
||
424 | * return an Iterator. |
||
425 | * |
||
426 | * @return Iterator|string|int|array |
||
427 | */ |
||
428 | 119 | public function runQuery() |
|
429 | { |
||
430 | 119 | $options = $this->options; |
|
431 | |||
432 | 119 | switch ($this->query['type']) { |
|
433 | 119 | case self::TYPE_FIND: |
|
434 | 105 | $queryOptions = $this->getQueryOptions('select', 'sort', 'skip', 'limit', 'readPreference'); |
|
435 | 105 | $queryOptions = $this->renameQueryOptions($queryOptions, ['select' => 'projection']); |
|
436 | |||
437 | 105 | $cursor = $this->collection->find( |
|
438 | 105 | $this->query['query'], |
|
439 | 105 | $queryOptions |
|
440 | ); |
||
441 | |||
442 | 105 | return $this->makeIterator($cursor); |
|
443 | |||
444 | 22 | case self::TYPE_FIND_AND_UPDATE: |
|
445 | 6 | $queryOptions = $this->getQueryOptions('select', 'sort', 'upsert'); |
|
446 | 6 | $queryOptions = $this->renameQueryOptions($queryOptions, ['select' => 'projection']); |
|
447 | 6 | $queryOptions['returnDocument'] = ($this->query['new'] ?? false) ? FindOneAndUpdate::RETURN_DOCUMENT_AFTER : FindOneAndUpdate::RETURN_DOCUMENT_BEFORE; |
|
448 | |||
449 | 6 | return $this->collection->findOneAndUpdate( |
|
450 | 6 | $this->query['query'], |
|
451 | 6 | $this->query['newObj'], |
|
452 | 6 | array_merge($options, $queryOptions) |
|
453 | ); |
||
454 | |||
455 | 17 | case self::TYPE_FIND_AND_REMOVE: |
|
456 | 2 | $queryOptions = $this->getQueryOptions('select', 'sort'); |
|
457 | 2 | $queryOptions = $this->renameQueryOptions($queryOptions, ['select' => 'projection']); |
|
458 | |||
459 | 2 | return $this->collection->findOneAndDelete( |
|
460 | 2 | $this->query['query'], |
|
461 | 2 | array_merge($options, $queryOptions) |
|
462 | ); |
||
463 | |||
464 | 15 | case self::TYPE_INSERT: |
|
465 | return $this->collection->insertOne($this->query['newObj'], $options); |
||
466 | |||
467 | 15 | case self::TYPE_UPDATE: |
|
468 | 10 | if ($this->query['multiple'] ?? false) { |
|
469 | 2 | return $this->collection->updateMany( |
|
470 | 2 | $this->query['query'], |
|
471 | 2 | $this->query['newObj'], |
|
472 | 2 | array_merge($options, $this->getQueryOptions('upsert')) |
|
473 | ); |
||
474 | } |
||
475 | |||
476 | 8 | return $this->collection->updateOne( |
|
477 | 8 | $this->query['query'], |
|
478 | 8 | $this->query['newObj'], |
|
479 | 8 | array_merge($options, $this->getQueryOptions('upsert')) |
|
480 | ); |
||
481 | |||
482 | 5 | case self::TYPE_REMOVE: |
|
483 | 1 | return $this->collection->deleteMany($this->query['query'], $options); |
|
484 | |||
485 | 4 | View Code Duplication | case self::TYPE_DISTINCT: |
486 | 2 | $collection = $this->collection; |
|
487 | 2 | $query = $this->query; |
|
488 | |||
489 | 2 | return $collection->distinct( |
|
490 | 2 | $query['distinct'], |
|
491 | 2 | $query['query'], |
|
492 | 2 | array_merge($options, $this->getQueryOptions('readPreference')) |
|
493 | ); |
||
494 | |||
495 | 2 | View Code Duplication | case self::TYPE_COUNT: |
496 | 2 | $collection = $this->collection; |
|
497 | 2 | $query = $this->query; |
|
498 | |||
499 | 2 | return $collection->count( |
|
500 | 2 | $query['query'], |
|
501 | 2 | array_merge($options, $this->getQueryOptions('hint', 'limit', 'skip', 'readPreference')) |
|
502 | ); |
||
503 | } |
||
504 | } |
||
505 | } |
||
506 |
This class constant has been deprecated.