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 |
|
$this->requireIndexes = $requireIndexes; |
116
|
2 |
|
return $this; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Set the current field to operate on. |
121
|
|
|
* |
122
|
|
|
* @param string $field |
123
|
|
|
* @return $this |
124
|
|
|
*/ |
125
|
155 |
|
public function field($field) |
126
|
|
|
{ |
127
|
155 |
|
$this->currentField = $field; |
128
|
155 |
|
parent::field($field); |
129
|
|
|
|
130
|
155 |
|
return $this; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Use a primer to eagerly load all references in the current field. |
135
|
|
|
* |
136
|
|
|
* If $primer is true or a callable is provided, referenced documents for |
137
|
|
|
* this field will loaded into UnitOfWork immediately after the query is |
138
|
|
|
* executed. This will avoid multiple queries due to lazy initialization of |
139
|
|
|
* Proxy objects. |
140
|
|
|
* |
141
|
|
|
* If $primer is false, no priming will take place. That is also the default |
142
|
|
|
* behavior. |
143
|
|
|
* |
144
|
|
|
* If a custom callable is used, its signature should conform to the default |
145
|
|
|
* Closure defined in {@link ReferencePrimer::__construct()}. |
146
|
|
|
* |
147
|
|
|
* @param boolean|callable $primer |
148
|
|
|
* @return $this |
149
|
|
|
* @throws \InvalidArgumentException If $primer is not boolean or callable |
150
|
|
|
*/ |
151
|
27 |
|
public function prime($primer = true) |
152
|
|
|
{ |
153
|
27 |
|
if ( ! is_bool($primer) && ! is_callable($primer)) { |
154
|
1 |
|
throw new \InvalidArgumentException('$primer is not a boolean or callable'); |
155
|
|
|
} |
156
|
|
|
|
157
|
26 |
|
if ($primer === false) { |
158
|
1 |
|
unset($this->primers[$this->currentField]); |
159
|
|
|
|
160
|
1 |
|
return $this; |
161
|
|
|
} |
162
|
|
|
|
163
|
26 |
|
if (array_key_exists('eagerCursor', $this->query) && !$this->query['eagerCursor']) { |
164
|
1 |
|
throw new \BadMethodCallException("Can't call prime() when setting eagerCursor to false"); |
165
|
|
|
} |
166
|
|
|
|
167
|
25 |
|
$this->primers[$this->currentField] = $primer; |
168
|
25 |
|
return $this; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* {@inheritdoc} |
173
|
|
|
*/ |
174
|
6 |
|
public function eagerCursor($bool = true) |
175
|
|
|
{ |
176
|
6 |
|
if ( ! $bool && ! empty($this->primers)) { |
177
|
1 |
|
throw new \BadMethodCallException("Can't set eagerCursor to false when using reference primers"); |
178
|
|
|
} |
179
|
|
|
|
180
|
5 |
|
return parent::eagerCursor($bool); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @param bool $bool |
186
|
|
|
* @return $this |
187
|
|
|
*/ |
188
|
16 |
|
public function hydrate($bool = true) |
189
|
|
|
{ |
190
|
16 |
|
$this->hydrate = $bool; |
191
|
16 |
|
return $this; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param bool $bool |
196
|
|
|
* @return $this |
197
|
|
|
*/ |
198
|
2 |
|
public function readOnly($bool = true) |
199
|
|
|
{ |
200
|
2 |
|
$this->readOnly = $bool; |
201
|
2 |
|
return $this; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* @param bool $bool |
206
|
|
|
* @return $this |
207
|
|
|
*/ |
208
|
5 |
|
public function refresh($bool = true) |
209
|
|
|
{ |
210
|
5 |
|
$this->refresh = $bool; |
211
|
5 |
|
return $this; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Change the query type to find and optionally set and change the class being queried. |
216
|
|
|
* |
217
|
|
|
* @param string $documentName |
218
|
|
|
* @return $this |
219
|
|
|
*/ |
220
|
12 |
|
public function find($documentName = null) |
221
|
|
|
{ |
222
|
12 |
|
$this->setDocumentName($documentName); |
223
|
12 |
|
parent::find(); |
224
|
|
|
|
225
|
12 |
|
return $this; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @param string $documentName |
230
|
|
|
* @return $this |
231
|
|
|
*/ |
232
|
13 |
|
public function findAndUpdate($documentName = null) |
233
|
|
|
{ |
234
|
13 |
|
$this->setDocumentName($documentName); |
235
|
13 |
|
parent::findAndUpdate(); |
236
|
|
|
|
237
|
13 |
|
return $this; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @param bool $bool |
242
|
|
|
* @return $this |
243
|
|
|
*/ |
244
|
4 |
|
public function returnNew($bool = true) |
245
|
|
|
{ |
246
|
4 |
|
$this->refresh(true); |
247
|
4 |
|
parent::returnNew($bool); |
248
|
|
|
|
249
|
4 |
|
return $this; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @param string $documentName |
254
|
|
|
* @return $this |
255
|
|
|
*/ |
256
|
1 |
|
public function findAndRemove($documentName = null) |
257
|
|
|
{ |
258
|
1 |
|
$this->setDocumentName($documentName); |
259
|
1 |
|
parent::findAndRemove(); |
260
|
|
|
|
261
|
1 |
|
return $this; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param string $documentName |
266
|
|
|
* @return $this |
267
|
|
|
* |
268
|
|
|
* @deprecated Deprecated in version 1.2 - use updateOne or updateMany instead |
269
|
|
|
*/ |
270
|
12 |
|
public function update($documentName = null) |
271
|
|
|
{ |
272
|
12 |
|
$this->setDocumentName($documentName); |
273
|
12 |
|
parent::update(); |
|
|
|
|
274
|
|
|
|
275
|
12 |
|
return $this; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @param string $documentName |
280
|
|
|
* @return $this |
281
|
|
|
*/ |
282
|
|
|
public function updateOne($documentName = null) |
283
|
|
|
{ |
284
|
|
|
$this->setDocumentName($documentName); |
285
|
|
|
parent::updateOne(); |
286
|
|
|
|
287
|
|
|
return $this; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* @param string $documentName |
292
|
|
|
* @return $this |
293
|
|
|
*/ |
294
|
|
|
public function updateMany($documentName = null) |
295
|
|
|
{ |
296
|
|
|
$this->setDocumentName($documentName); |
297
|
|
|
parent::updateMany(); |
298
|
|
|
|
299
|
|
|
return $this; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* @param string $documentName |
304
|
|
|
* @return $this |
305
|
|
|
*/ |
306
|
1 |
|
public function insert($documentName = null) |
307
|
|
|
{ |
308
|
1 |
|
$this->setDocumentName($documentName); |
309
|
1 |
|
parent::insert(); |
310
|
|
|
|
311
|
1 |
|
return $this; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* @param string $documentName |
316
|
|
|
* @return $this |
317
|
|
|
*/ |
318
|
1 |
|
public function remove($documentName = null) |
319
|
|
|
{ |
320
|
1 |
|
$this->setDocumentName($documentName); |
321
|
1 |
|
parent::remove(); |
322
|
|
|
|
323
|
1 |
|
return $this; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* @param object $document |
328
|
|
|
* @return $this |
329
|
|
|
*/ |
330
|
11 |
|
public function references($document) |
331
|
|
|
{ |
332
|
11 |
|
$this->expr->references($document); |
|
|
|
|
333
|
9 |
|
return $this; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* @param object $document |
338
|
|
|
* @return $this |
339
|
|
|
*/ |
340
|
7 |
|
public function includesReferenceTo($document) |
341
|
|
|
{ |
342
|
7 |
|
$this->expr->includesReferenceTo($document); |
|
|
|
|
343
|
5 |
|
return $this; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* @inheritdoc |
348
|
|
|
* @deprecated Deprecated in version 1.2 - use Aggregation Builder's group stage instead. |
349
|
|
|
*/ |
350
|
1 |
|
public function group($keys, array $initial, $reduce = null, array $options = []) |
351
|
|
|
{ |
352
|
1 |
|
@trigger_error( |
|
|
|
|
353
|
1 |
|
sprintf('%s was deprecated in version 1.2 - use Aggregation Builder\'s group stage instead.', __METHOD__), |
354
|
1 |
|
E_USER_DEPRECATED |
355
|
|
|
); |
356
|
1 |
|
return parent::group($keys, $initial, $reduce, $options); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* Gets the Query executable. |
361
|
|
|
* |
362
|
|
|
* @param array $options |
363
|
|
|
* @return Query $query |
364
|
|
|
*/ |
365
|
201 |
|
public function getQuery(array $options = array()) |
366
|
|
|
{ |
367
|
201 |
|
if ($this->query['type'] === Query::TYPE_MAP_REDUCE) { |
368
|
3 |
|
$this->hydrate = false; |
369
|
|
|
} |
370
|
|
|
|
371
|
201 |
|
$documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name); |
372
|
|
|
|
373
|
201 |
|
$query = $this->query; |
374
|
|
|
|
375
|
201 |
|
$query['query'] = $this->expr->getQuery(); |
376
|
201 |
|
$query['query'] = $documentPersister->addDiscriminatorToPreparedQuery($query['query']); |
|
|
|
|
377
|
201 |
|
$query['query'] = $documentPersister->addFilterToPreparedQuery($query['query']); |
378
|
|
|
|
379
|
201 |
|
$query['newObj'] = $this->expr->getNewObj(); |
380
|
|
|
|
381
|
201 |
|
if (isset($query['distinct'])) { |
382
|
2 |
|
$query['distinct'] = $documentPersister->prepareFieldName($query['distinct']); |
|
|
|
|
383
|
|
|
} |
384
|
|
|
|
385
|
201 |
|
if ($this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION && ! empty($query['upsert']) && |
386
|
201 |
|
(empty($query['query'][$this->class->discriminatorField]) || is_array($query['query'][$this->class->discriminatorField]))) { |
387
|
1 |
|
throw new \InvalidArgumentException('Upsert query that is to be performed on discriminated document does not have single ' . |
388
|
1 |
|
'discriminator. Either not use base class or set \'' . $this->class->discriminatorField . '\' field manually.'); |
389
|
|
|
} |
390
|
|
|
|
391
|
200 |
|
if ( ! empty($query['select'])) { |
392
|
14 |
|
$query['select'] = $documentPersister->prepareSortOrProjection($query['select']); |
393
|
14 |
|
if ($this->hydrate && $this->class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION |
394
|
14 |
|
&& ! isset($query['select'][$this->class->discriminatorField])) { |
395
|
|
|
$includeMode = 0 < count(array_filter($query['select'], function($mode) { return $mode == 1; })); |
396
|
2 |
|
if ($includeMode && ! isset($query['select'][$this->class->discriminatorField])) { |
397
|
1 |
|
$query['select'][$this->class->discriminatorField] = 1; |
398
|
|
|
} |
399
|
|
|
} |
400
|
|
|
} |
401
|
|
|
|
402
|
200 |
|
if (isset($query['sort'])) { |
403
|
28 |
|
$query['sort'] = $documentPersister->prepareSortOrProjection($query['sort']); |
404
|
|
|
} |
405
|
|
|
|
406
|
200 |
|
if ($this->class->slaveOkay) { |
407
|
1 |
|
$query['slaveOkay'] = $this->class->slaveOkay; |
408
|
|
|
} |
409
|
|
|
|
410
|
200 |
|
return new Query( |
411
|
200 |
|
$this->dm, |
412
|
200 |
|
$this->class, |
413
|
200 |
|
$this->collection, |
414
|
|
|
$query, |
415
|
|
|
$options, |
416
|
200 |
|
$this->hydrate, |
417
|
200 |
|
$this->refresh, |
418
|
200 |
|
$this->primers, |
419
|
200 |
|
$this->requireIndexes, |
420
|
200 |
|
$this->readOnly |
421
|
|
|
); |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Create a new Expr instance that can be used as an expression with the Builder |
426
|
|
|
* |
427
|
|
|
* @return Expr $expr |
428
|
|
|
*/ |
429
|
25 |
|
public function expr() |
430
|
|
|
{ |
431
|
25 |
|
$expr = new Expr($this->dm); |
432
|
25 |
|
$expr->setClassMetadata($this->class); |
433
|
|
|
|
434
|
25 |
|
return $expr; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* @param string[]|string $documentName an array of document names or just one. |
439
|
|
|
*/ |
440
|
231 |
|
private function setDocumentName($documentName) |
441
|
|
|
{ |
442
|
231 |
|
if (is_array($documentName)) { |
443
|
2 |
|
$documentNames = $documentName; |
444
|
2 |
|
$documentName = $documentNames[0]; |
445
|
|
|
|
446
|
2 |
|
$metadata = $this->dm->getClassMetadata($documentName); |
447
|
2 |
|
$discriminatorField = $metadata->discriminatorField; |
448
|
2 |
|
$discriminatorValues = $this->getDiscriminatorValues($documentNames); |
449
|
|
|
|
450
|
|
|
// If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list |
451
|
1 |
View Code Duplication |
if ($metadata->defaultDiscriminatorValue && array_search($metadata->defaultDiscriminatorValue, $discriminatorValues) !== false) { |
|
|
|
|
452
|
1 |
|
$discriminatorValues[] = null; |
453
|
|
|
} |
454
|
|
|
|
455
|
1 |
|
$this->field($discriminatorField)->in($discriminatorValues); |
456
|
|
|
} |
457
|
|
|
|
458
|
230 |
|
if ($documentName !== null) { |
459
|
230 |
|
$this->collection = $this->dm->getDocumentCollection($documentName); |
460
|
230 |
|
$this->class = $this->dm->getClassMetadata($documentName); |
461
|
|
|
|
462
|
|
|
// Expr also needs to know |
463
|
230 |
|
$this->expr->setClassMetadata($this->class); |
|
|
|
|
464
|
|
|
} |
465
|
230 |
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Get Discriminator Values |
469
|
|
|
* |
470
|
|
|
* @param \Iterator|array $classNames |
471
|
|
|
* @return array an array of discriminatorValues (mixed type) |
472
|
|
|
* @throws \InvalidArgumentException if the number of found collections > 1 |
473
|
|
|
*/ |
474
|
2 |
|
private function getDiscriminatorValues($classNames) |
475
|
|
|
{ |
476
|
2 |
|
$discriminatorValues = array(); |
477
|
2 |
|
$collections = array(); |
478
|
2 |
|
foreach ($classNames as $className) { |
479
|
2 |
|
$class = $this->dm->getClassMetadata($className); |
480
|
2 |
|
$discriminatorValues[] = $class->discriminatorValue; |
481
|
2 |
|
$key = $this->dm->getDocumentDatabase($className)->getName() . '.' . $class->getCollection(); |
482
|
2 |
|
$collections[$key] = $key; |
483
|
|
|
} |
484
|
2 |
|
if (count($collections) > 1) { |
485
|
1 |
|
throw new \InvalidArgumentException('Documents involved are not all mapped to the same database collection.'); |
486
|
|
|
} |
487
|
1 |
|
return $discriminatorValues; |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.