1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the PHPMongo package. |
5
|
|
|
* |
6
|
|
|
* (c) Dmytro Sokil <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Sokil\Mongo; |
13
|
|
|
|
14
|
|
|
use Sokil\Mongo\Exception\CursorException; |
15
|
|
|
use Sokil\Mongo\Exception\FeatureNotSupportedException; |
16
|
|
|
use Sokil\Mongo\Exception\WriteException; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @mixin Expression |
20
|
|
|
*/ |
21
|
|
|
class Cursor implements |
22
|
|
|
\Iterator, |
23
|
|
|
\Countable |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* |
27
|
|
|
* @var Client |
28
|
|
|
*/ |
29
|
|
|
private $client; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* |
33
|
|
|
* @var Collection |
34
|
|
|
*/ |
35
|
|
|
private $collection; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
private $fields = array(); |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* |
45
|
|
|
* @var \MongoCursor |
46
|
|
|
*/ |
47
|
|
|
private $cursor; |
48
|
|
|
/** |
49
|
|
|
* |
50
|
|
|
* @var Expression |
51
|
|
|
*/ |
52
|
|
|
private $expression; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Offset |
56
|
|
|
* @var int |
57
|
|
|
*/ |
58
|
|
|
private $skip = 0; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Limit |
62
|
|
|
* @var int |
63
|
|
|
*/ |
64
|
|
|
private $limit = 0; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Definition of sort |
68
|
|
|
* @var array |
69
|
|
|
*/ |
70
|
|
|
private $sort = array(); |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Definition of read preference |
74
|
|
|
* @var array |
75
|
|
|
*/ |
76
|
|
|
private $readPreference = array(); |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Return result as array or as Document instance |
80
|
|
|
* @var boolean |
81
|
|
|
*/ |
82
|
|
|
private $isResultAsArray = false; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Cursor options |
86
|
|
|
* @var array |
87
|
|
|
*/ |
88
|
|
|
private $options = array( |
89
|
|
|
'expressionClass' => '\Sokil\Mongo\Expression', |
90
|
|
|
/** |
91
|
|
|
* @link http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ |
92
|
|
|
* @var int number of documents to return in each batch of the response from the MongoDB instance |
93
|
|
|
*/ |
94
|
|
|
'batchSize' => null, |
95
|
|
|
// client timeout |
96
|
|
|
'clientTimeout' => null, |
97
|
|
|
// Specifies a cumulative time limit in milliseconds to |
98
|
|
|
// be allowed by the server for processing operations on the cursor. |
99
|
|
|
'serverTimeout' => null, |
100
|
|
|
); |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Use document pool to create Document object from array |
104
|
|
|
* @var bool |
105
|
|
|
*/ |
106
|
|
|
private $isDocumentPoolUsed = true; |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Index hinting |
110
|
|
|
* @param \Sokil\Mongo\Collection $collection |
111
|
|
|
* @param array $options |
112
|
|
|
*/ |
113
|
|
|
private $hint; |
114
|
|
|
|
115
|
|
|
public function __construct(Collection $collection, array $options = null) |
116
|
|
|
{ |
117
|
|
|
$this->collection = $collection; |
118
|
|
|
$this->client = $this->collection->getDatabase()->getClient(); |
119
|
|
|
|
120
|
|
|
if (!empty($options)) { |
121
|
|
|
$this->options = $options + $this->options; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// expression |
125
|
|
|
$this->expression = $this->expression(); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
public function __call($name, $arguments) |
129
|
|
|
{ |
130
|
|
|
call_user_func_array( |
131
|
|
|
array($this->expression, $name), |
132
|
|
|
$arguments |
133
|
|
|
); |
134
|
|
|
|
135
|
|
|
return $this; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Get option |
140
|
|
|
* |
141
|
|
|
* @param string|int $name |
142
|
|
|
* @param mixed $default |
143
|
|
|
* |
144
|
|
|
* @return mixed |
145
|
|
|
*/ |
146
|
|
|
public function getOption($name, $default = null) |
147
|
|
|
{ |
148
|
|
|
return isset($this->options[$name]) ? $this->options[$name] : $default; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Get result as array |
153
|
|
|
* |
154
|
|
|
* @return Cursor |
155
|
|
|
*/ |
156
|
|
|
public function asArray() |
157
|
|
|
{ |
158
|
|
|
$this->isResultAsArray = true; |
159
|
|
|
return $this; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Get result as object |
164
|
|
|
* @return Cursor |
165
|
|
|
*/ |
166
|
|
|
public function asObject() |
167
|
|
|
{ |
168
|
|
|
$this->isResultAsArray = false; |
169
|
|
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Check if result returned as array |
174
|
|
|
* @return bool |
175
|
|
|
*/ |
176
|
|
|
public function isResultAsArray() |
177
|
|
|
{ |
178
|
|
|
return $this->isResultAsArray; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Return only specified fields |
183
|
|
|
* |
184
|
|
|
* @param array $fields |
185
|
|
|
* @return Cursor |
186
|
|
|
*/ |
187
|
|
|
public function fields(array $fields) |
188
|
|
|
{ |
189
|
|
|
$this->fields = array_fill_keys($fields, 1); |
190
|
|
|
$this->skipDocumentPool(); |
191
|
|
|
return $this; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Append field to accept list |
196
|
|
|
* |
197
|
|
|
* @param string $field field name |
198
|
|
|
* |
199
|
|
|
* @return Cursor |
200
|
|
|
*/ |
201
|
|
|
public function field($field) |
202
|
|
|
{ |
203
|
|
|
$this->fields[$field] = 1; |
204
|
|
|
$this->skipDocumentPool(); |
205
|
|
|
return $this; |
206
|
|
|
} |
207
|
|
|
/** |
208
|
|
|
* Return all fields except specified |
209
|
|
|
* |
210
|
|
|
* @param array $fields |
211
|
|
|
* @return \Sokil\Mongo\Cursor |
212
|
|
|
*/ |
213
|
|
|
public function skipFields(array $fields) |
214
|
|
|
{ |
215
|
|
|
$this->fields = array_fill_keys($fields, 0); |
216
|
|
|
$this->skipDocumentPool(); |
217
|
|
|
return $this; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Append field to skip list |
222
|
|
|
* |
223
|
|
|
* @param string $field field name |
224
|
|
|
* @return Cursor |
225
|
|
|
*/ |
226
|
|
|
public function skipField($field) |
227
|
|
|
{ |
228
|
|
|
$this->fields[$field] = 0; |
229
|
|
|
$this->skipDocumentPool(); |
230
|
|
|
return $this; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Paginate list of sub-documents |
235
|
|
|
* |
236
|
|
|
* @param string $field |
237
|
|
|
* @param integer $limit |
238
|
|
|
* @param integer $skip |
239
|
|
|
* @return \Sokil\Mongo\Cursor |
240
|
|
|
* @throws Exception |
241
|
|
|
*/ |
242
|
|
|
public function slice($field, $limit, $skip = null) |
243
|
|
|
{ |
244
|
|
|
$limit = (int) $limit; |
245
|
|
|
$skip = (int) $skip; |
246
|
|
|
|
247
|
|
|
if ($skip) { |
248
|
|
|
$this->fields[$field] = array('$slice' => array($skip, $limit)); |
249
|
|
|
} else { |
250
|
|
|
$this->fields[$field] = array('$slice' => $limit); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$this->skipDocumentPool(); |
254
|
|
|
|
255
|
|
|
return $this; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Merge expression |
260
|
|
|
* @param \Sokil\Mongo\Expression $expression |
261
|
|
|
* @return \Sokil\Mongo\Cursor |
262
|
|
|
*/ |
263
|
|
|
public function query(Expression $expression) |
264
|
|
|
{ |
265
|
|
|
$this->expression->merge($expression); |
266
|
|
|
return $this; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Helper to create new expression |
271
|
|
|
* |
272
|
|
|
* @return \Sokil\Mongo\Expression |
273
|
|
|
*/ |
274
|
|
|
public function expression() |
275
|
|
|
{ |
276
|
|
|
return new $this->options['expressionClass']; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Filter by list of \MongoId |
281
|
|
|
* |
282
|
|
|
* @param array $idList list of ids |
283
|
|
|
* @return \Sokil\Mongo\Cursor |
284
|
|
|
*/ |
285
|
|
|
public function byIdList(array $idList) |
286
|
|
|
{ |
287
|
|
|
$this->expression->whereIn('_id', self::mixedToMongoIdList($idList)); |
288
|
|
|
return $this; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Filter by id |
293
|
|
|
* |
294
|
|
|
* @param string|\MongoId $id id of document |
295
|
|
|
* @return \Sokil\Mongo\Cursor |
296
|
|
|
*/ |
297
|
|
|
public function byId($id) |
298
|
|
|
{ |
299
|
|
|
if ($id instanceof \MongoId) { |
300
|
|
|
$this->expression->where('_id', $id); |
301
|
|
|
} else { |
302
|
|
|
try { |
303
|
|
|
$this->expression->where('_id', new \MongoId($id)); |
304
|
|
|
} catch (\MongoException $e) { |
305
|
|
|
$this->expression->where('_id', $id); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
return $this; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Skip defined number of documents |
314
|
|
|
* |
315
|
|
|
* @param int $skip number of documents to skip |
316
|
|
|
* @return \Sokil\Mongo\Cursor |
317
|
|
|
*/ |
318
|
|
|
public function skip($skip) |
319
|
|
|
{ |
320
|
|
|
$this->skip = (int) $skip; |
321
|
|
|
|
322
|
|
|
return $this; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Limit result set to specified number of elements |
327
|
|
|
* |
328
|
|
|
* @param int $limit number of elements in result set |
329
|
|
|
* @param int|null $offset number of elements to skip |
330
|
|
|
* @return \Sokil\Mongo\Cursor |
331
|
|
|
*/ |
332
|
|
|
public function limit($limit, $offset = null) |
333
|
|
|
{ |
334
|
|
|
$this->limit = (int) $limit; |
335
|
|
|
|
336
|
|
|
if (null !== $offset) { |
337
|
|
|
$this->skip($offset); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
return $this; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Specifies the number of documents to return in each batch of the response from the MongoDB instance. |
345
|
|
|
* |
346
|
|
|
* @param int $size number of documents |
347
|
|
|
* @link http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ |
348
|
|
|
* @return \Sokil\Mongo\Cursor |
349
|
|
|
*/ |
350
|
|
|
public function setBatchSize($size) |
351
|
|
|
{ |
352
|
|
|
$this->options['batchSize'] = (int) $size; |
353
|
|
|
|
354
|
|
|
return $this; |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* Instructs the driver to stop waiting for a response and throw a |
359
|
|
|
* MongoCursorTimeoutException after a set time. |
360
|
|
|
* A timeout can be set at any time and will affect subsequent queries on |
361
|
|
|
* the cursor, including fetching more results from the database. |
362
|
|
|
* |
363
|
|
|
* @param int $ms |
364
|
|
|
* @return Cursor |
365
|
|
|
*/ |
366
|
|
|
public function setClientTimeout($ms) |
367
|
|
|
{ |
368
|
|
|
$this->options['clientTimeout'] = (int) $ms; |
369
|
|
|
return $this; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Server-side timeout for a query, |
374
|
|
|
* Specifies a cumulative time limit in milliseconds to be allowed |
375
|
|
|
* by the server for processing operations on the cursor. |
376
|
|
|
* |
377
|
|
|
* @param int $ms |
378
|
|
|
* @return \Sokil\Mongo\Cursor |
379
|
|
|
*/ |
380
|
|
|
public function setServerTimeout($ms) |
381
|
|
|
{ |
382
|
|
|
$this->options['serverTimeout'] = (int) $ms; |
383
|
|
|
return $this; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Sort result by specified keys and directions |
388
|
|
|
* |
389
|
|
|
* An array of fields by which to sort. Each element in the array has as key the field name, and as value either |
390
|
|
|
* 1 for ascending sort, or -1 for descending sort. Each result is first sorted on the first field in the array, |
391
|
|
|
* then (if it exists) on the second field in the array, etc. This means that the order of the fields in the |
392
|
|
|
* fields array is important. See also the examples section. |
393
|
|
|
* |
394
|
|
|
* @param array $sort |
395
|
|
|
* @return Cursor |
396
|
|
|
*/ |
397
|
|
|
public function sort(array $sort) |
398
|
|
|
{ |
399
|
|
|
$this->sort = $sort; |
400
|
|
|
return $this; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Count documents in result without applying limit and offset |
405
|
|
|
* @return int count |
406
|
|
|
*/ |
407
|
|
|
public function count() |
408
|
|
|
{ |
409
|
|
|
return (int) $this->collection |
410
|
|
|
->getMongoCollection() |
411
|
|
|
->count($this->expression->toArray()); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Explain expression |
416
|
|
|
* |
417
|
|
|
* @return array |
418
|
|
|
* |
419
|
|
|
* @throws FeatureNotSupportedException |
420
|
|
|
*/ |
421
|
|
|
public function explain() |
422
|
|
|
{ |
423
|
|
|
if (Client::isEmulationMode()) { |
424
|
|
|
throw new FeatureNotSupportedException('Feature not implemented in emulation mode'); |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
$this->rewind(); |
428
|
|
|
|
429
|
|
|
return $this->cursor->explain(); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Count documents in result with applying limit and offset |
434
|
|
|
* |
435
|
|
|
* ext-mongo:1.0.7 Added limit and skip as second and third parameters, respectively. |
436
|
|
|
* ext-mongo:1.6.0 The second parameter is now an options array. Passing limit and skip as the second and third |
437
|
|
|
* parameters, respectively, is deprecated. |
438
|
|
|
* |
439
|
|
|
* @return int |
440
|
|
|
* |
441
|
|
|
* @throws FeatureNotSupportedException |
442
|
|
|
*/ |
443
|
|
|
public function limitedCount() |
444
|
|
|
{ |
445
|
|
|
if (version_compare(\MongoClient::VERSION, '1.0.7', '<')) { |
446
|
|
|
throw new FeatureNotSupportedException('Limit and skip not supported in ext-mongo versions prior to 1.0.7'); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
return (int) $this->collection |
450
|
|
|
->getMongoCollection() |
451
|
|
|
->count( |
452
|
|
|
$this->expression->toArray(), |
453
|
|
|
$this->limit, |
454
|
|
|
$this->skip |
455
|
|
|
); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Gte list of \MongoId of current search query |
461
|
|
|
* @return array |
462
|
|
|
*/ |
463
|
|
|
public function getIdList() |
464
|
|
|
{ |
465
|
|
|
return self::mixedToMongoIdList($this->findAll()); |
|
|
|
|
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Find one document which correspond to expression |
470
|
|
|
* |
471
|
|
|
* @deprecated since v.1.22.2. Use ::one instead |
472
|
|
|
* |
473
|
|
|
* @return Document|array|null |
474
|
|
|
* |
475
|
|
|
* @throws CursorException |
476
|
|
|
*/ |
477
|
|
|
public function findOne() |
478
|
|
|
{ |
479
|
|
|
return $this->one(); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* Find one document which correspond to expression |
484
|
|
|
* |
485
|
|
|
* @return Document|array|null |
486
|
|
|
* |
487
|
|
|
* @throws CursorException |
488
|
|
|
*/ |
489
|
|
|
public function one() |
490
|
|
|
{ |
491
|
|
|
try { |
492
|
|
|
$mongoDocument = $this->collection |
493
|
|
|
->getMongoCollection() |
494
|
|
|
->findOne( |
495
|
|
|
$this->expression->toArray(), |
496
|
|
|
$this->fields |
497
|
|
|
); |
498
|
|
|
} catch (\Exception $e) { |
499
|
|
|
throw new CursorException( |
500
|
|
|
$e->getMessage(), |
501
|
|
|
$e->getCode(), |
502
|
|
|
$e |
503
|
|
|
); |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
if (empty($mongoDocument)) { |
507
|
|
|
return null; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
if (true === $this->isResultAsArray) { |
511
|
|
|
return $mongoDocument; |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
return $this->collection->hydrate( |
515
|
|
|
$mongoDocument, |
516
|
|
|
$this->isDocumentPoolUsed() |
517
|
|
|
); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* Get result of searching |
522
|
|
|
* |
523
|
|
|
* @deprecated since v.1.22.2. Use ::one instead |
524
|
|
|
* |
525
|
|
|
* @return Document[]|array[] |
526
|
|
|
*/ |
527
|
|
|
public function findAll() |
528
|
|
|
{ |
529
|
|
|
return $this->all(); |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
/** |
533
|
|
|
* Get result of searching |
534
|
|
|
* |
535
|
|
|
* @return array |
536
|
|
|
*/ |
537
|
|
|
public function all() |
538
|
|
|
{ |
539
|
|
|
return iterator_to_array($this); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Get random document |
544
|
|
|
* |
545
|
|
|
* @deprecated since v.1.22.2. Use ::one instead |
546
|
|
|
* |
547
|
|
|
* @return Document|null |
548
|
|
|
*/ |
549
|
|
|
public function findRandom() |
550
|
|
|
{ |
551
|
|
|
return $this->random(); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* Get random document |
556
|
|
|
* |
557
|
|
|
* @return Document|null |
558
|
|
|
*/ |
559
|
|
|
public function random() |
560
|
|
|
{ |
561
|
|
|
$count = $this->count(); |
562
|
|
|
switch ($count) { |
563
|
|
|
case 0: |
564
|
|
|
return null; |
565
|
|
|
case 1: |
566
|
|
|
return $this->one(); |
567
|
|
|
default: |
568
|
|
|
$cursor = $this->skip(mt_rand(0, $count - 1))->limit(1); |
569
|
|
|
$cursor->rewind(); |
570
|
|
|
return $cursor->current(); |
571
|
|
|
} |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* Get query builder's expression |
576
|
|
|
* |
577
|
|
|
* @return Expression |
578
|
|
|
*/ |
579
|
|
|
public function getExpression() |
580
|
|
|
{ |
581
|
|
|
return $this->expression; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* Get MongoDB query array |
586
|
|
|
* |
587
|
|
|
* @return array |
588
|
|
|
*/ |
589
|
|
|
public function getMongoQuery() |
590
|
|
|
{ |
591
|
|
|
return $this->expression->toArray(); |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* Return the values from a single field in the result set of documents |
596
|
|
|
* |
597
|
|
|
* @param string $fieldName |
598
|
|
|
* @return array |
599
|
|
|
*/ |
600
|
|
|
public function pluck($fieldName) |
601
|
|
|
{ |
602
|
|
|
$isEmbeddedDocumentField = false !== strpos($fieldName, '.'); |
603
|
|
|
|
604
|
|
|
$valueList = array(); |
605
|
|
|
|
606
|
|
|
if ($isEmbeddedDocumentField) { |
607
|
|
|
// get result |
608
|
|
|
if ($this->isResultAsArray) { |
609
|
|
|
$cursor = clone $this; |
610
|
|
|
$documentObjectList = $cursor->asObject()->findAll(); |
|
|
|
|
611
|
|
|
unset($cursor); |
612
|
|
|
} else { |
613
|
|
|
$documentObjectList = $this->findAll(); |
|
|
|
|
614
|
|
|
} |
615
|
|
|
// get value of field |
616
|
|
|
foreach ($documentObjectList as $key => $documentObject) { |
617
|
|
|
$valueList[$key] = $documentObject->get($fieldName); |
618
|
|
|
} |
619
|
|
|
} else { |
620
|
|
|
// get result |
621
|
|
|
if ($this->isResultAsArray) { |
622
|
|
|
$documentArrayList = $this->findAll(); |
|
|
|
|
623
|
|
|
} else { |
624
|
|
|
$cursor = clone $this; |
625
|
|
|
$documentArrayList = $cursor->asArray()->findAll(); |
|
|
|
|
626
|
|
|
unset($cursor); |
627
|
|
|
} |
628
|
|
|
// get values of field |
629
|
|
|
$valueList = array_column($documentArrayList, $fieldName, '_id'); |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
return $valueList; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
/** |
636
|
|
|
* Get document instance and remove it from collection |
637
|
|
|
* |
638
|
|
|
* @return \Sokil\Mongo\Document |
639
|
|
|
*/ |
640
|
|
|
public function findAndRemove() |
641
|
|
|
{ |
642
|
|
|
$mongoDocument = $this->collection->getMongoCollection()->findAndModify( |
643
|
|
|
$this->expression->toArray(), |
644
|
|
|
null, |
645
|
|
|
$this->fields, |
646
|
|
|
array( |
647
|
|
|
'remove' => true, |
648
|
|
|
'sort' => $this->sort, |
649
|
|
|
) |
650
|
|
|
); |
651
|
|
|
|
652
|
|
|
if (empty($mongoDocument)) { |
653
|
|
|
return null; |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
return $this->collection->hydrate( |
657
|
|
|
$mongoDocument, |
658
|
|
|
$this->isDocumentPoolUsed() |
659
|
|
|
); |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Find first document and update it |
664
|
|
|
* |
665
|
|
|
* @param Operator $operator operations with document to update |
666
|
|
|
* @param bool $upsert if document not found - create |
667
|
|
|
* @param bool $returnUpdated if true - return updated document |
668
|
|
|
* |
669
|
|
|
* @return null|Document |
670
|
|
|
*/ |
671
|
|
|
public function findAndUpdate(Operator $operator, $upsert = false, $returnUpdated = true) |
672
|
|
|
{ |
673
|
|
|
$mongoDocument = $this->collection |
674
|
|
|
->getMongoCollection() |
675
|
|
|
->findAndModify( |
676
|
|
|
$this->expression->toArray(), |
677
|
|
|
$operator ? $operator->toArray() : null, |
678
|
|
|
$this->fields, |
679
|
|
|
array( |
680
|
|
|
'new' => $returnUpdated, |
681
|
|
|
'sort' => $this->sort, |
682
|
|
|
'upsert' => $upsert, |
683
|
|
|
) |
684
|
|
|
); |
685
|
|
|
|
686
|
|
|
if (empty($mongoDocument)) { |
687
|
|
|
return null; |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
return $this->collection->hydrate($mongoDocument, $this->isDocumentPoolUsed()); |
691
|
|
|
} |
692
|
|
|
|
693
|
|
|
/** |
694
|
|
|
* Apply callable to all documents in cursor |
695
|
|
|
* |
696
|
|
|
* @param callable $handler |
697
|
|
|
* @return array |
698
|
|
|
*/ |
699
|
|
View Code Duplication |
public function map($handler) |
|
|
|
|
700
|
|
|
{ |
701
|
|
|
$result = array(); |
702
|
|
|
|
703
|
|
|
foreach ($this as $id => $document) { |
704
|
|
|
$result[$id] = $handler($document); |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
return $result; |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Filter documents in cursor by condition in callable |
712
|
|
|
* |
713
|
|
|
* @param callable $handler |
714
|
|
|
* @return array |
715
|
|
|
*/ |
716
|
|
View Code Duplication |
public function filter($handler) |
|
|
|
|
717
|
|
|
{ |
718
|
|
|
$result = array(); |
719
|
|
|
|
720
|
|
|
foreach ($this as $id => $document) { |
721
|
|
|
if (!$handler($document)) { |
722
|
|
|
continue; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
$result[$id] = $document; |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
return $result; |
729
|
|
|
} |
730
|
|
|
|
731
|
|
|
/** |
732
|
|
|
* Get result set of documents. |
733
|
|
|
* |
734
|
|
|
* @return \Sokil\Mongo\ResultSet |
735
|
|
|
*/ |
736
|
|
|
public function getResultSet() |
737
|
|
|
{ |
738
|
|
|
return new ResultSet($this->findAll()); |
|
|
|
|
739
|
|
|
} |
740
|
|
|
|
741
|
|
|
/** |
742
|
|
|
* Get paginator |
743
|
|
|
* |
744
|
|
|
* @param int $page page number |
745
|
|
|
* @param int $itemsOnPage number of items on page |
746
|
|
|
* @return Paginator |
747
|
|
|
*/ |
748
|
|
|
public function paginate($page, $itemsOnPage = 30) |
749
|
|
|
{ |
750
|
|
|
$paginator = new Paginator($this); |
751
|
|
|
return $paginator |
752
|
|
|
->setCurrentPage($page) |
753
|
|
|
->setItemsOnPage($itemsOnPage); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Clears the cursor |
758
|
|
|
* Mongo's rewind is reset+next |
759
|
|
|
*/ |
760
|
|
|
public function reset() |
761
|
|
|
{ |
762
|
|
|
if ($this->cursor !== null) { |
763
|
|
|
$this->cursor->reset(); |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
return $this; |
767
|
|
|
} |
768
|
|
|
|
769
|
|
|
/** |
770
|
|
|
* Returns the cursor to the beginning of the result set. |
771
|
|
|
* @return void |
772
|
|
|
*/ |
773
|
|
|
public function rewind() |
774
|
|
|
{ |
775
|
|
|
if ($this->cursor !== null) { |
776
|
|
|
$this->cursor->rewind(); |
777
|
|
|
return; |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
$this->cursor = $this->collection |
781
|
|
|
->getMongoCollection() |
782
|
|
|
->find( |
783
|
|
|
$this->expression->toArray(), |
784
|
|
|
$this->fields |
785
|
|
|
); |
786
|
|
|
|
787
|
|
|
if ($this->skip) { |
788
|
|
|
$this->cursor->skip($this->skip); |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
if ($this->limit) { |
792
|
|
|
$this->cursor->limit($this->limit); |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
if ($this->options['batchSize']) { |
796
|
|
|
$this->cursor->batchSize($this->options['batchSize']); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
if ($this->options['clientTimeout']) { |
800
|
|
|
$this->cursor->timeout($this->options['clientTimeout']); |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
if ($this->options['serverTimeout']) { |
804
|
|
|
$this->cursor->maxTimeMS($this->options['clientTimeout']); |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
if (!empty($this->sort)) { |
808
|
|
|
$this->cursor->sort($this->sort); |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
if ($this->hint) { |
812
|
|
|
$this->cursor->hint($this->hint); |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
// define read preferences |
816
|
|
|
if (!empty($this->readPreference)) { |
817
|
|
|
$this->cursor->setReadPreference( |
818
|
|
|
$this->readPreference['type'], |
819
|
|
|
$this->readPreference['tagsets'] |
820
|
|
|
); |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
// init cursor state |
824
|
|
|
$this->cursor->rewind(); |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
/** |
828
|
|
|
* @return bool |
829
|
|
|
*/ |
830
|
|
|
public function valid() |
831
|
|
|
{ |
832
|
|
|
return $this->cursor->valid(); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* @return Document|array|null |
837
|
|
|
*/ |
838
|
|
|
public function current() |
839
|
|
|
{ |
840
|
|
|
$mongoDocument = $this->cursor->current(); |
841
|
|
|
if (empty($mongoDocument)) { |
842
|
|
|
return null; |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
if ($this->isResultAsArray) { |
846
|
|
|
return $mongoDocument; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
return $this->collection->hydrate( |
850
|
|
|
$mongoDocument, |
851
|
|
|
$this->isDocumentPoolUsed |
852
|
|
|
); |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* @return string |
857
|
|
|
*/ |
858
|
|
|
public function key() |
859
|
|
|
{ |
860
|
|
|
return $this->cursor->key(); |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
/** |
864
|
|
|
* @return void |
865
|
|
|
*/ |
866
|
|
|
public function next() |
867
|
|
|
{ |
868
|
|
|
$this->cursor->next(); |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
public function readPrimaryOnly() |
872
|
|
|
{ |
873
|
|
|
$this->readPreference = array( |
874
|
|
|
'type' => \MongoClient::RP_PRIMARY, |
875
|
|
|
'tagsets' => array(), |
876
|
|
|
); |
877
|
|
|
|
878
|
|
|
return $this; |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
public function readPrimaryPreferred(array $tags = null) |
882
|
|
|
{ |
883
|
|
|
$this->readPreference = array( |
884
|
|
|
'type' => \MongoClient::RP_PRIMARY_PREFERRED, |
885
|
|
|
'tagsets' => $tags, |
886
|
|
|
); |
887
|
|
|
|
888
|
|
|
return $this; |
889
|
|
|
} |
890
|
|
|
|
891
|
|
|
public function readSecondaryOnly(array $tags = null) |
892
|
|
|
{ |
893
|
|
|
$this->readPreference = array( |
894
|
|
|
'type' => \MongoClient::RP_SECONDARY, |
895
|
|
|
'tagsets' => $tags, |
896
|
|
|
); |
897
|
|
|
|
898
|
|
|
return $this; |
899
|
|
|
} |
900
|
|
|
|
901
|
|
|
public function readSecondaryPreferred(array $tags = null) |
902
|
|
|
{ |
903
|
|
|
$this->readPreference = array( |
904
|
|
|
'type' => \MongoClient::RP_SECONDARY_PREFERRED, |
905
|
|
|
'tagsets' => $tags, |
906
|
|
|
); |
907
|
|
|
|
908
|
|
|
return $this; |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
public function readNearest(array $tags = null) |
912
|
|
|
{ |
913
|
|
|
$this->readPreference = array( |
914
|
|
|
'type' => \MongoClient::RP_NEAREST, |
915
|
|
|
'tagsets' => $tags, |
916
|
|
|
); |
917
|
|
|
|
918
|
|
|
return $this; |
919
|
|
|
} |
920
|
|
|
|
921
|
|
|
/** |
922
|
|
|
* @return array |
923
|
|
|
*/ |
924
|
|
|
public function getReadPreference() |
925
|
|
|
{ |
926
|
|
|
if ($this->cursor) { |
927
|
|
|
return $this->cursor->getReadPreference(); |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
return $this->readPreference; |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
public function isDocumentPoolUsed() |
934
|
|
|
{ |
935
|
|
|
return $this->isDocumentPoolUsed; |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
public function useDocumentPool() |
939
|
|
|
{ |
940
|
|
|
$this->isDocumentPoolUsed = true; |
941
|
|
|
return $this; |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
public function skipDocumentPool() |
945
|
|
|
{ |
946
|
|
|
$this->isDocumentPoolUsed = false; |
947
|
|
|
return $this; |
948
|
|
|
} |
949
|
|
|
|
950
|
|
|
/** |
951
|
|
|
* Specify index to use |
952
|
|
|
* |
953
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/meta/hint/ |
954
|
|
|
* @param array|string $specification Specify the index either by the index name or by document |
955
|
|
|
* @return \Sokil\Mongo\Cursor |
956
|
|
|
*/ |
957
|
|
|
public function hint($specification) |
958
|
|
|
{ |
959
|
|
|
$this->hint = $specification; |
960
|
|
|
return $this; |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
/** |
964
|
|
|
* Copy selected documents to another collection |
965
|
|
|
* |
966
|
|
|
* @param string $targetCollectionName |
967
|
|
|
* @param string|null $targetDatabaseName Target database name. If not specified - use current |
968
|
|
|
* @param int $batchLimit count of documents to get from old and insert to new collection per time |
969
|
|
|
* |
970
|
|
|
* @return Cursor |
971
|
|
|
* |
972
|
|
|
* @throws WriteException |
973
|
|
|
*/ |
974
|
|
|
public function copyToCollection( |
975
|
|
|
$targetCollectionName, |
976
|
|
|
$targetDatabaseName = null, |
977
|
|
|
$batchLimit = 100 |
978
|
|
|
) { |
979
|
|
|
// target database |
980
|
|
|
if (empty($targetDatabaseName)) { |
981
|
|
|
$database = $this->collection->getDatabase(); |
982
|
|
|
} else { |
983
|
|
|
$database = $this->client->getDatabase($targetDatabaseName); |
984
|
|
|
} |
985
|
|
|
|
986
|
|
|
// target collection |
987
|
|
|
$targetMongoCollection = $database |
988
|
|
|
->getCollection($targetCollectionName) |
989
|
|
|
->getMongoCollection(); |
990
|
|
|
|
991
|
|
|
// cursor |
992
|
|
|
$this->rewind(); |
993
|
|
|
|
994
|
|
|
// copy data |
995
|
|
|
$inProgress = true; |
996
|
|
|
while ($inProgress) { |
997
|
|
|
// get next pack of documents |
998
|
|
|
$documentList = array(); |
999
|
|
|
for ($i = 0; $i < $batchLimit; $i++) { |
1000
|
|
|
if (!$this->cursor->valid()) { |
1001
|
|
|
$inProgress = false; |
1002
|
|
|
|
1003
|
|
|
if (!empty($documentList)) { |
1004
|
|
|
// still need batch insert |
1005
|
|
|
break; |
1006
|
|
|
} else { |
1007
|
|
|
// no documents to insert - just exit |
1008
|
|
|
break(2); |
1009
|
|
|
} |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
$documentList[] = $this->cursor->current(); |
1013
|
|
|
$this->cursor->next(); |
1014
|
|
|
} |
1015
|
|
|
|
1016
|
|
|
// insert |
1017
|
|
|
$result = $targetMongoCollection->batchInsert($documentList); |
1018
|
|
|
|
1019
|
|
|
// With passed write concern, returns an associative array with the status of the inserts ("ok") |
1020
|
|
|
// and any error that may have occurred ("err"). |
1021
|
|
|
// Otherwise, returns TRUE if the batch insert was successfully sent, FALSE otherwise. |
1022
|
|
|
if (is_array($result)) { |
1023
|
|
|
if ($result['ok'] != 1) { |
1024
|
|
|
throw new WriteException('Batch insert error: ' . $result['err']); |
1025
|
|
|
} |
1026
|
|
|
} elseif (false === $result) { |
1027
|
|
|
throw new WriteException('Batch insert error'); |
1028
|
|
|
} |
1029
|
|
|
} |
1030
|
|
|
|
1031
|
|
|
return $this; |
1032
|
|
|
} |
1033
|
|
|
|
1034
|
|
|
/** |
1035
|
|
|
* Move selected documents to another collection. |
1036
|
|
|
* Documents will be removed from source collection only after |
1037
|
|
|
* copying them to target collection. |
1038
|
|
|
* |
1039
|
|
|
* @param string $targetCollectionName |
1040
|
|
|
* @param string|null $targetDatabaseName Target database name. If not specified - use current |
1041
|
|
|
* @param int $batchLimit count of documents to get from old and insert to new collection per time |
1042
|
|
|
*/ |
1043
|
|
|
public function moveToCollection( |
1044
|
|
|
$targetCollectionName, |
1045
|
|
|
$targetDatabaseName = null, |
1046
|
|
|
$batchLimit = 100 |
1047
|
|
|
) { |
1048
|
|
|
// copy to target |
1049
|
|
|
$this->copyToCollection($targetCollectionName, $targetDatabaseName, $batchLimit); |
1050
|
|
|
|
1051
|
|
|
// remove from source |
1052
|
|
|
$this->collection->batchDelete($this->expression); |
|
|
|
|
1053
|
|
|
} |
1054
|
|
|
|
1055
|
|
|
/** |
1056
|
|
|
* Used to get hash that uniquely identifies current query |
1057
|
|
|
* |
1058
|
|
|
* @return string |
1059
|
|
|
*/ |
1060
|
|
|
public function getHash() |
1061
|
|
|
{ |
1062
|
|
|
$hash = array(); |
1063
|
|
|
|
1064
|
|
|
// expression |
1065
|
|
|
$hash[] = json_encode($this->expression->toArray()); |
1066
|
|
|
|
1067
|
|
|
// sorts |
1068
|
|
View Code Duplication |
if (!empty($this->sort)) { |
|
|
|
|
1069
|
|
|
$sort = $this->sort; |
1070
|
|
|
ksort($sort); |
1071
|
|
|
$hash[] = implode('', array_merge(array_keys($sort), array_values($sort))); |
1072
|
|
|
} |
1073
|
|
|
|
1074
|
|
|
// fields |
1075
|
|
View Code Duplication |
if (!empty($this->fields)) { |
|
|
|
|
1076
|
|
|
$fields = $this->fields; |
1077
|
|
|
ksort($fields); |
1078
|
|
|
$hash[] = implode('', array_merge(array_keys($fields), array_values($fields))); |
1079
|
|
|
} |
1080
|
|
|
|
1081
|
|
|
// skip and limit |
1082
|
|
|
$hash[] = $this->skip; |
1083
|
|
|
$hash[] = $this->limit; |
1084
|
|
|
|
1085
|
|
|
// get hash |
1086
|
|
|
return md5(implode(':', $hash)); |
1087
|
|
|
} |
1088
|
|
|
|
1089
|
|
|
/** |
1090
|
|
|
* Get list of MongoId objects from array of strings, MongoId's and Document's |
1091
|
|
|
* |
1092
|
|
|
* @param array $list |
1093
|
|
|
* @return array list of \MongoId |
1094
|
|
|
*/ |
1095
|
|
|
public static function mixedToMongoIdList(array $list) |
1096
|
|
|
{ |
1097
|
|
|
return array_map(function ($element) { |
1098
|
|
|
// MongoId |
1099
|
|
|
if ($element instanceof \MongoId) { |
1100
|
|
|
return $element; |
1101
|
|
|
} |
1102
|
|
|
|
1103
|
|
|
// \Sokil\Mongo\Document |
1104
|
|
|
if ($element instanceof Document) { |
1105
|
|
|
return $element->getId(); |
1106
|
|
|
} |
1107
|
|
|
|
1108
|
|
|
// array with id key |
1109
|
|
|
if (is_array($element)) { |
1110
|
|
|
if (!isset($element['_id'])) { |
1111
|
|
|
throw new \InvalidArgumentException('Array must have _id key'); |
1112
|
|
|
} |
1113
|
|
|
return $element['_id']; |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
// string |
1117
|
|
|
if (is_string($element)) { |
1118
|
|
|
try { |
1119
|
|
|
return new \MongoId($element); |
1120
|
|
|
} catch (\MongoException $e) { |
1121
|
|
|
return $element; |
1122
|
|
|
} |
1123
|
|
|
} |
1124
|
|
|
|
1125
|
|
|
// int |
1126
|
|
|
if (is_int($element)) { |
1127
|
|
|
return $element; |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
throw new \InvalidArgumentException( |
1131
|
|
|
'Must be \MongoId, \Sokil\Mongo\Document, array with _id key, string or integer' |
1132
|
|
|
); |
1133
|
|
|
}, array_values($list)); |
1134
|
|
|
} |
1135
|
|
|
} |
1136
|
|
|
|
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.