GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 1e1b58...a9ac0a )
by De
02:31
created

Collection::aggregate()   F

Complexity

Conditions 21
Paths 614

Size

Total Lines 103
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 103
rs 2.3015
cc 21
eloc 52
nc 614
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Document\InvalidDocumentException;
15
use Sokil\Mongo\Exception\FeatureNotSupportedException;
16
use Sokil\Mongo\Collection\Definition;
17
use Sokil\Mongo\Enum\Language;
18
19
/**
20
 * Instance of this class is a representation of mongo collection.
21
 * It aggregates \MongoCollection instance.
22
 *
23
 * @link https://github.com/sokil/php-mongo#selecting-database-and-collection Selecting collection
24
 * @link https://github.com/sokil/php-mongo#querying-documents Querying documents
25
 * @link https://github.com/sokil/php-mongo#update-few-documents Update few documents
26
 * @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting collection
27
 *
28
 * @author Dmytro Sokil <[email protected]>
29
 */
30
class Collection implements \Countable
31
{
32
    /**
33
     * @var string expression class. This class may be overloaded to define
34
     *  own query methods (whereUserAgeGreatedThan(), etc.)
35
     * @deprecated since 1.13 Use 'expressionClass' declaration in mapping
36
     */
37
    protected $_queryExpressionClass;
38
39
    /**
40
     * @deprecated since 1.13 Use 'documentClass' declaration in mapping
41
     * @var string Default class for document
42
     */
43
    private $documentClass;
44
45
    /**
46
     * List of arrays, where each item array is an index definition.
47
     * Every index definition must contain key 'keys' with list of fields and orders,
48
     * and optional options from @link http://php.net/manual/en/mongocollection.createindex.php:
49
     *
50
     * Example:
51
     * array(
52
     *     array(
53
     *         'keys' => array('field1' => 1, 'field2' => -1),
54
     *         'unique' => true
55
     *     ),
56
     *     ...
57
     * )
58
     * @var array list of indexes
59
     * @deprecated since 1.13 Use 'index' declaration in mapping
60
     */
61
    protected $_index;
62
63
    /**
64
     *
65
     * @var \Sokil\Mongo\Database
66
     */
67
    protected $_database;
68
69
    /**
70
     *
71
     * @var \MongoCollection
72
     */
73
    protected $_mongoCollection;
74
75
    /**
76
     * Implementation of identity map pattern
77
     *
78
     * @var array list of cached documents
79
     */
80
    private $documentPool = array();
81
82
    /**
83
     *
84
     * @var bool cache or not documents
85
     */
86
    private $isDocumentPoolEnabled = true;
87
88
    /**
89
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
90
     * @var bool default value of versioning
91
     */
92
    protected $versioning;
93
94
    /**
95
     * @var \Sokil\Mongo\Collection\Definition collection options
96
     */
97
    private $definition;
98
99
    public function __construct(Database $database, $collection, Definition $definition = null)
100
    {
101
        // define db
102
        $this->_database = $database;
103
104
        $this->initCollection($collection);
105
106
        // init definition
107
        $this->definition = $definition ? $definition : new Definition();
108
109
        if($this->documentClass) {
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$documentClass has been deprecated with message: since 1.13 Use 'documentClass' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
110
            $this->definition->setOption('documentClass', $this->documentClass);
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$documentClass has been deprecated with message: since 1.13 Use 'documentClass' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
111
        }
112
        if($this->versioning !== null) {
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$versioning has been deprecated with message: since 1.13 Use 'versioning' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
113
            $this->definition->setOption('versioning', $this->versioning);
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$versioning has been deprecated with message: since 1.13 Use 'versioning' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
114
        }
115
        if($this->_index) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_index of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$_index has been deprecated with message: since 1.13 Use 'index' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
116
            $this->definition->setOption('index', $this->_index);
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$_index has been deprecated with message: since 1.13 Use 'index' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
117
        }
118
        if($this->_queryExpressionClass) {
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$_queryExpressionClass has been deprecated with message: since 1.13 Use 'expressionClass' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
119
            $this->definition->setOption('expressionClass', $this->_queryExpressionClass);
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Collection::$_queryExpressionClass has been deprecated with message: since 1.13 Use 'expressionClass' declaration in mapping

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
120
        }
121
    }
122
123
    protected function initCollection($collection)
124
    {
125
        // init mongo collection
126
        if($collection instanceof \MongoCollection) {
127
            $this->_mongoCollection = $collection;
128
        } else {
129
            $this->_mongoCollection = $this->_database->getMongoDB()->selectCollection($collection);
130
        }
131
    }
132
133
    /**
134
     * Start versioning documents on modify
135
     *
136
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
137
     * @return \Sokil\Mongo\Collection
138
     */
139
    public function enableVersioning()
140
    {
141
        $this->definition->setOption('versioning', true);
142
        return $this;
143
    }
144
145
    /**
146
     * Check if versioning enabled
147
     *
148
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
149
     * @return bool
150
     */
151
    public function isVersioningEnabled()
152
    {
153
        return $this->definition->getOption('versioning');
154
    }
155
156
    /**
157
     * Get option
158
     *
159
     * @param string|int $name
160
     * @return mixed
161
     */
162
    public function getOption($name)
163
    {
164
        return $this->definition->getOption($name);
165
    }
166
167
    public function getOptions()
168
    {
169
        return $this->definition->getOptions();
170
    }
171
172
    public function __get($name)
173
    {
174
        return $this->getDocument($name);
175
    }
176
177
    /**
178
     * Get name of collection
179
     * 
180
     * @return string name of collection
181
     */
182
    public function getName()
183
    {
184
        return $this->_mongoCollection->getName();
185
    }
186
187
    /**
188
     * Get native collection instance of mongo driver
189
     * 
190
     * @return \MongoCollection
191
     */
192
    public function getMongoCollection()
193
    {
194
        return $this->_mongoCollection;
195
    }
196
197
    /**
198
     *
199
     * @return \Sokil\Mongo\Database
200
     */
201
    public function getDatabase()
202
    {
203
        return $this->_database;
204
    }
205
206
    /**
207
     * Delete collection
208
     * 
209
     * @return \Sokil\Mongo\Collection
210
     * @throws \Sokil\Mongo\Exception
211
     */
212
    public function delete() {
213
        $status = $this->_mongoCollection->drop();
214
        if($status['ok'] != 1) {
215
            // check if collection exists
216
            if('ns not found' !== $status['errmsg']) {
217
                // collection exist
218
                throw new Exception('Error deleting collection ' . $this->getName() . ': ' . $status['errmsg']);
219
            }
220
        }
221
222
        return $this;
223
    }
224
225
    /**
226
     * Override to define class name of document by document data
227
     *
228
     * @param array $documentData
229
     * @return string Document class data
230
     */
231
    public function getDocumentClassName(array $documentData = null)
232
    {
233
        $documentClass = $this->definition->getOption('documentClass');
234
235
        if(is_callable($documentClass)) {
236
            return call_user_func($documentClass, $documentData);
237
        }
238
239
        if(class_exists($documentClass)) {
240
            return $documentClass;
241
        }
242
243
        throw new Exception('Property "documentClass" must be callable or valid name of class');
244
    }
245
246
    /**
247
     * Factory method to get not stored Document instance from array
248
     * @param array $data
249
     * @return \Sokil\Mongo\Document
250
     */
251
    public function createDocument(array $data = null)
252
    {
253
        $className = $this->getDocumentClassName($data);
254
255
        /* @var $document \Sokil\Mongo\Document */
256
        $document = new $className(
257
            $this,
258
            $data,
259
            array('stored' => false) + $this->definition->getOptions()
260
        );
261
262
        // store document to identity map
263
        if($this->isDocumentPoolEnabled()) {
264
            $collection = $this;
265
            $document->onAfterInsert(function(\Sokil\Mongo\Event $event) use($collection) {
0 ignored issues
show
Bug introduced by
The call to onAfterInsert() misses a required argument $priority.

This check looks for function calls that miss required arguments.

Loading history...
266
                $collection->addDocumentToDocumentPool($event->getTarget());
267
            });
268
        }
269
270
        return $document;
271
    }
272
273
    /**
274
     * Factory method to get document object from array of stored document
275
     *
276
     * @param array $data
277
     * @return \Sokil\Mongo\Document
278
     */
279
    public function hydrate($data, $useDocumentPool = true)
280
    {
281
        if(!is_array($data) || !isset($data['_id'])) {
282
            throw new Exception('Document must be stored and has _id key');
283
        }
284
285
        // if document already in pool - return it
286
        if($useDocumentPool && $this->isDocumentPoolEnabled() && $this->isDocumentInDocumentPool($data['_id'])) {
287
            return $this
288
                ->getDocumentFromDocumentPool($data['_id'])
289
                ->mergeUnmodified($data);
290
        }
291
292
        // init document instance
293
        $className = $this->getDocumentClassName($data);
294
        $document = new $className(
295
            $this,
296
            $data,
297
            array('stored' => true) + $this->definition->getOptions()
298
        );
299
300
        // store document in cache
301
        if($useDocumentPool && $this->isDocumentPoolEnabled()) {
302
            $this->addDocumentToDocumentPool($document);
303
        }
304
305
        return $document;
306
    }
307
308
    /**
309
     * Total count of documents in collection
310
     * 
311
     * @return int 
312
     */
313
    public function count()
314
    {
315
        return $this->find()->count();
316
    }
317
318
    /**
319
     * Retrieve a list of distinct values for the given key across a collection.
320
     *
321
     * @param string $selector field selector
322
     * @param array|callable|\Sokil\Mongo\Expression $expression expression to search documents
323
     * @return array distinct values
324
     */
325
    public function getDistinct($selector, $expression = null)
326
    {
327
        if($expression) {
328
            return $this->_mongoCollection->distinct(
329
                $selector,
330
                Expression::convertToArray($expression)
331
            );
332
        }
333
334
        return $this->_mongoCollection->distinct($selector);
335
    }
336
337
    /**
338
     * Create new Expression instance to use in query builder or update operations
339
     * 
340
     * @return \Sokil\Mongo\Expression
341
     */
342
    public function expression()
343
    {
344
        $className = $this->definition->getExpressionClass();
345
        return new $className;
346
    }
347
348
    /**
349
     * Create Operator instance to use in update operations
350
     * 
351
     * @return \Sokil\Mongo\Operator
352
     */
353
    public function operator()
354
    {
355
        return new Operator();
356
    }
357
358
    /**
359
     * Create document query builder
360
     *
361
     * @param $callable callable|null Function to configure query builder&
362
     * @return \Sokil\Mongo\Cursor|\Sokil\Mongo\Expression
363
     */
364
    public function find($callable = null)
365
    {
366
        /** @var \Sokil\Mongo\Cursor $cursor */
367
        $cursor = new Cursor($this, array(
368
            'expressionClass'   => $this->definition->getExpressionClass(),
369
            'batchSize'         => $this->definition->getOption('batchSize'),
370
            'clientTimeout'     => $this->definition->getOption('cursorClientTimeout'),
371
            'serverTimeout'     => $this->definition->getOption('cursorServerTimeout'),
372
        ));
373
374
        if(is_callable($callable)) {
375
            $callable($cursor->getExpression());
376
        }
377
378
        return $cursor;
379
    }
380
381
    /**
382
     * Create document query builder
383
     *
384
     * @return \Sokil\Mongo\Cursor
385
     */
386
    public function findAsArray($callable = null)
387
    {
388
        return $this
389
            ->find($callable)
390
            ->asArray();
391
    }
392
393
    /**
394
     * Stop storing found documents to pool
395
     *
396
     * @return \Sokil\Mongo\Collection
397
     */
398
    public function disableDocumentPool()
399
    {
400
        $this->isDocumentPoolEnabled = false;
401
        return $this;
402
    }
403
404
    /**
405
     * Start storing found documents to pool
406
     *
407
     * @return \Sokil\Mongo\Collection
408
     */
409
    public function enableDocumentPool()
410
    {
411
        $this->isDocumentPoolEnabled = true;
412
        return $this;
413
    }
414
415
    /**
416
     * Check if document pool enabled and requested documents store to it
417
     *
418
     * @return bool
419
     */
420
    public function isDocumentPoolEnabled()
421
    {
422
        return $this->isDocumentPoolEnabled;
423
    }
424
425
    public function clearDocumentPool()
426
    {
427
        $this->documentPool = array();
428
        return $this;
429
    }
430
431
    /**
432
     * Check if documents are in pool
433
     *
434
     * @return bool
435
     */
436
    public function isDocumentPoolEmpty()
437
    {
438
        return !$this->documentPool;
439
    }
440
441
    /**
442
     * Store document to pool
443
     *
444
     * @param array $document
445
     * @return \Sokil\Mongo\Collection
446
     */
447
    public function addDocumentToDocumentPool(Document $document)
448
    {
449
        $documentId = (string) $document->getId();
450
451
        if(!isset($this->documentPool[$documentId])) {
452
            $this->documentPool[$documentId] = $document;
453
        } else {
454
            // merging because document after
455
            // load and before getting in second place may be changed
456
            // and this changes must be preserved:
457
            //
458
            // 1. Document loads and modifies in current session
459
            // 2. Document loads modified in another session
460
            // 3. Document loads once again in current session. Changes from stage 2 merges as unmodified
461
462
            $this->documentPool[$documentId]->mergeUnmodified($document->toArray());
463
        }
464
465
        return $this;
466
    }
467
468
    /**
469
     * Store documents to identity map
470
     *
471
     * @param array $documents list of Document instances
472
     * @return \Sokil\Mongo\Collection
473
     */
474
    public function addDocumentsToDocumentPool(array $documents)
475
    {
476
        foreach($documents as $document) {
477
            $this->addDocumentToDocumentPool($document);
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * Remove document instance from identity map
485
     *
486
     * @param \Sokil\Mongo\Document $document
487
     * @return \Sokil\Mongo\Collection
488
     */
489
    public function removeDocumentFromDocumentPool(Document $document)
490
    {
491
        unset($this->documentPool[(string) $document]);
492
        return $this;
493
    }
494
495
    /**
496
     * Get document from identity map by it's id
497
     *
498
     * @param string|int|\MongoId $id
499
     * @return \Sokil\Mongo\Document
500
     */
501
    public function getDocumentFromDocumentPool($id)
502
    {
503
        return $this->documentPool[(string) $id];
504
    }
505
506
    /**
507
     * Get documents from pool if they stored
508
     *
509
     * @param array $ids
510
     */
511
    public function getDocumentsFromDocumentPool(array $ids = null)
512
    {
513
        if(!$ids) {
514
            return $this->documentPool;
515
        }
516
517
        return array_intersect_key(
518
            $this->documentPool,
519
            array_flip(array_map('strval', $ids))
520
        );
521
    }
522
523
    /**
524
     * Get number of documents in document pool
525
     *
526
     * @return int
527
     */
528
    public function documentPoolCount()
529
    {
530
        return count($this->documentPool);
531
    }
532
533
    /**
534
     * Check if document exists in identity map
535
     *
536
     * @param \Sokil\Mongo\Document|\MongoId|int|string $document Document instance or it's id
537
     * @return boolean
538
     */
539
    public function isDocumentInDocumentPool($document)
540
    {
541
        if($document instanceof Document) {
542
            $document = $document->getId();
543
        }
544
545
        return isset($this->documentPool[(string) $document]);
546
    }
547
548
    /**
549
     * Get document by id
550
     * If callable specified, document always loaded directly omitting document pool.
551
     * Method may return document as array if cursor configured through Cursor::asArray()
552
     *
553
     * @param string|\MongoId $id
554
     * @param callable $callable cursor callable used to configure cursor
555
     * @return \Sokil\Mongo\Document|array|null
556
     */
557
    public function getDocument($id, $callable = null)
558
    {
559
        if(!$this->isDocumentPoolEnabled) {
560
            return $this->getDocumentDirectly($id, $callable);
561
        }
562
563
        if(!$callable && $this->isDocumentInDocumentPool($id)) {
564
            return $this->getDocumentFromDocumentPool($id);
565
        }
566
567
        $document = $this->getDocumentDirectly($id, $callable);
568
569
        // if callable configure cursor to return document as array,
570
        // than it can't be stored to document pool
571
        if($document instanceof Document) {
572
            $this->addDocumentToDocumentPool($document);
573
        }
574
575
        return $document;
576
    }
577
578
579
    /**
580
     * Get document by id directly omitting cache
581
     * Method may return document as array if cursor configured through Cursor::asArray()
582
     * 
583
     * @param string|\MongoId $id
584
     * @param callable $callable cursor callable used to configure cursor
585
     * @return \Sokil\Mongo\Document|array|null
586
     */
587
    public function getDocumentDirectly($id, $callable = null)
588
    {
589
        $cursor = $this->find();
590
591
        if(is_callable($callable)) {
592
            call_user_func($callable, $cursor);
593
        }
594
595
        return $cursor
596
            ->byId($id)
597
            ->skipDocumentPool()
598
            ->findOne();
599
    }
600
601
    /**
602
     * Check if document belongs to collection
603
     *
604
     * @param \Sokil\Mongo\Document $document
605
     * @return type
606
     */
607
    public function hasDocument(Document $document)
608
    {
609
        // check connection
610
        if($document->getCollection()->getDatabase()->getClient()->getDsn() !== $this->getDatabase()->getClient()->getDsn()) {
611
            return false;
612
        }
613
614
        // check database
615
        if ($document->getCollection()->getDatabase()->getName() !== $this->getDatabase()->getName()) {
616
            return false;
617
        }
618
619
        // check collection
620
        return $document->getCollection()->getName() == $this->getName();
621
    }
622
623
    /**
624
     * Get documents by list of id
625
     *
626
     * @param array $idList list of ids
627
     * @param callable $callable cursor callable used to configure cursor
628
     * @return array|null
629
     */
630
    public function getDocuments(array $idList, $callable = null)
631
    {
632
        $idListToFindDirectly = $idList;
633
634
        // try to egt document from pool if enabled
635
        $documentsInDocumentPool = array();
636
        if ($this->isDocumentPoolEnabled && !$callable) {
637
            $documentsInDocumentPool = $this->getDocumentsFromDocumentPool($idList);
638
            if (count($documentsInDocumentPool) === count($idList)) {
639
                return $documentsInDocumentPool;
640
            }
641
642
            // skip ids already found in pool
643
            $idListToFindDirectly = array_diff_key(
644
                array_map('strval', $idList),
645
                array_keys($documentsInDocumentPool)
646
            );
647
        }
648
649
        // get documents directly
650
        $cursor = $this->find();
651
652
        if (is_callable($callable)) {
653
            call_user_func($callable, $cursor);
654
        }
655
656
        $documentsGettingDirectly = $cursor->byIdList($idListToFindDirectly)->findAll();
657
        if (!$documentsGettingDirectly) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $documentsGettingDirectly of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
658
            return $documentsInDocumentPool ? $documentsInDocumentPool : array();
659
        }
660
661
        if ($this->isDocumentPoolEnabled) {
662
            $this->addDocumentsToDocumentPool($documentsGettingDirectly);
663
        }
664
665
        return $documentsGettingDirectly + $documentsInDocumentPool;
666
    }
667
668
    /**
669
     * Creates batch insert operation handler
670
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
671
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
672
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
673
     *   one item at a time) or can rearrange it. Defaults to TRUE
674
     * @return BatchInsert
675
     */
676
    public function createBatchInsert($writeConcern = null, $timeout = null, $ordered = null)
677
    {
678
        return new BatchInsert(
679
            $this,
680
            $writeConcern,
681
            $timeout,
682
            $ordered
683
        );
684
    }
685
686
    /**
687
     * Creates batch update operation handler
688
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
689
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
690
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
691
     *   one item at a time) or can rearrange it. Defaults to TRUE
692
     * @return BatchUpdate
693
     */
694
    public function createBatchUpdate($writeConcern = null, $timeout = null, $ordered = null)
695
    {
696
        return new BatchUpdate(
697
            $this,
698
            $writeConcern,
699
            $timeout,
700
            $ordered
701
        );
702
    }
703
704
    /**
705
     * Creates batch delete operation handler
706
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
707
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
708
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
709
     *   one item at a time) or can rearrange it. Defaults to TRUE
710
     * @return BatchDelete
711
     */
712
    public function createBatchDelete($writeConcern = null, $timeout = null, $ordered = null)
713
    {
714
        return new BatchDelete(
715
            $this,
716
            $writeConcern,
717
            $timeout,
718
            $ordered
719
        );
720
    }
721
722
    /**
723
     * @deprecated since 1.13. Use Document::delete()
724
     * @param \Sokil\Mongo\Document $document
725
     * @return \Sokil\Mongo\Collection
726
     */
727
    public function deleteDocument(Document $document)
728
    {
729
        $document->delete();
730
        return $this;
731
    }
732
733
    /**
734
     * Delete documents by expression
735
     * 
736
     * @param callable|array|\Sokil\Mongo\Expression $expression
737
     * @return \Sokil\Mongo\Collection
738
     * @throws Exception
739
     */
740
    public function batchDelete($expression = array())
741
    {
742
        // remove
743
        $result = $this->_mongoCollection->remove(
744
            Expression::convertToArray($expression)
745
        );
746
747
        // check result
748
        if(true !== $result && $result['ok'] != 1) {
749
            throw new Exception('Error removing documents from collection: ' . $result['err']);
750
        }
751
752
        return $this;
753
    }
754
755
    /**
756
     * @deprecated since 1.13. Use Collection::batchDelete();
757
     */
758
    public function deleteDocuments($expression = array())
759
    {
760
        return $this->batchDelete($expression);
761
    }
762
763
    /**
764
     * Insert multiple documents defined as arrays
765
     *
766
     * Prior to version 1.5.0 of the driver it was possible to use MongoCollection::batchInsert(),
767
     * however, as of 1.5.0 that method is now discouraged.
768
     *
769
     * You can use Collection::createBatchInsert()
770
     *
771
     * @param array $rows list of documents to insert, defined as arrays
772
     * @return \Sokil\Mongo\Collection
773
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
774
     * @throws \Sokil\Mongo\Exception
775
     */
776
    public function batchInsert($rows, $validate = true)
777
    {
778
        if($validate) {
779
            $document = $this->createDocument();
780
            foreach($rows as $row) {
781
                $document->merge($row);
782
783
                if(!$document->isValid()) {
784
                    throw new InvalidDocumentException('Document is invalid on batch insert');
785
                }
786
787
                $document->reset();
788
            }
789
        }
790
791
        $result = $this->_mongoCollection->batchInsert($rows);
792
793
        // If the w parameter is set to acknowledge the write,
794
        // returns an associative array with the status of the inserts ("ok")
795
        // and any error that may have occurred ("err").
796 View Code Duplication
        if(is_array($result)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
797
            if($result['ok'] != 1) {
798
                throw new Exception('Batch insert error: ' . $result['err']);
799
            }
800
801
            return $this;
802
        }
803
804
        // Otherwise, returns TRUE if the batch insert was successfully sent,
805
        // FALSE otherwise.
806
        if(!$result) {
807
            throw new Exception('Batch insert error');
808
        }
809
810
        return $this;
811
    }
812
813
    /**
814
     * @deprecated since 1.13 Use Collection::batchInsert()
815
     */
816
    public function insertMultiple($rows, $validate = true)
817
    {
818
        return $this->batchInsert($rows, $validate);
819
    }
820
821
    /**
822
     * Direct insert of array to MongoDB without creating document object and validation
823
     *
824
     * @param array $document
825
     * @return \Sokil\Mongo\Collection
826
     * @throws Exception
827
     */
828
    public function insert(array $document)
829
    {
830
        $result = $this->_mongoCollection->insert($document);
831
832
        // if write concern acknowledged
833 View Code Duplication
        if(is_array($result)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
834
            if($result['ok'] != 1) {
835
                throw new Exception('Insert error: ' . $result['err'] . ': ' . $result['errmsg']);
836
            }
837
838
            return $this;
839
        }
840
841
        // if write concern unacknowledged
842
        if(!$result) {
843
            throw new Exception('Insert error');
844
        }
845
846
        return $this;
847
    }
848
849
    /**
850
     * Update multiple documents
851
     *
852
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
853
     *  which documents will change.
854
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators to update
855
     * @param array $options update options, see http://php.net/manual/ru/mongocollection.update.php
856
     * @return \Sokil\Mongo\Collection
857
     * @throws \Sokil\Mongo\Exception
858
     */
859
    public function update($expression, $updateData, array $options = array())
860
    {
861
        // execute update operator
862
        $result = $this->_mongoCollection->update(
863
            Expression::convertToArray($expression),
864
            Operator::convertToArray($updateData),
865
            $options
866
        );
867
868
        // if write concern acknowledged
869 View Code Duplication
        if(is_array($result)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
870
            if($result['ok'] != 1) {
871
                throw new Exception(sprintf('Update error: %s: %s', $result['err'], $result['errmsg']));
872
            }
873
            return $this;
874
        }
875
876
        // if write concern unacknowledged
877
        if(!$result) {
878
            throw new Exception('Update error');
879
        }
880
881
        return $this;
882
    }
883
884
    /**
885
     * Update multiple documents
886
     *
887
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
888
     *  which documents will change.
889
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
890
     *  to update
891
     * @return \Sokil\Mongo\Collection
892
     * @throws \Sokil\Mongo\Exception
893
     */
894
    public function batchUpdate($expression, $updateData)
895
    {
896
        return $this->update($expression, $updateData, array(
897
            'multiple'  => true,
898
        ));
899
    }
900
901
    /**
902
     * @deprecated since 1.13 Use Collection::batchUpdate()
903
     */
904
    public function updateMultiple($expression, $updateData)
905
    {
906
        return $this->batchUpdate($expression, $updateData);
907
    }
908
909
    /**
910
     * Update all documents
911
     *
912
     * @deprecated since 1.13. Use Collection::batchUpdate([])
913
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
914
     * @return \Sokil\Mongo\Collection
915
     * @throws \Sokil\Mongo\Exception
916
     */
917
    public function updateAll($updateData)
918
    {
919
        return $this->update(array(), $updateData, array(
920
            'multiple'  => true,
921
        ));
922
    }
923
924
    /**
925
     * Start aggregation
926
     *
927
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
928
     * @return \Sokil\Mongo\Pipeline
929
     */
930
    public function createAggregator()
931
    {
932
        return new Pipeline($this);
933
    }
934
935
    /**
936
     * Aggregate using pipeline
937
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
938
     *
939
     * @param callable|array|Pipeline $pipeline list of pipeline stages
940
     * @param array aggregate options
941
     * @param bool $asCursor return result as cursor
942
     *
943
     * @throws \Sokil\Mongo\Exception
944
     * @return array result of aggregation
945
     */
946
    public function aggregate(
947
        $pipeline,
948
        array $options = array(),
949
        $asCursor = false
950
    ) {
951
        // configure through callable
952
        if (is_callable($pipeline)) {
953
            $pipelineConfiguratorCallable = $pipeline;
954
            $pipeline = $this->createAggregator();
955
            call_user_func($pipelineConfiguratorCallable, $pipeline);
956
        }
957
958
        // get aggregation array
959
        if ($pipeline instanceof Pipeline) {
960
            if ($options && is_array($options)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
961
                $options = array_merge($pipeline->getOptions(), $options);
962
            } else {
963
                $options = $pipeline->getOptions();
964
            }
965
            $pipeline = $pipeline->toArray();
966
        } else if (!is_array($pipeline)) {
967
            throw new Exception('Wrong pipeline specified');
968
        }
969
970
        // log
971
        $client = $this->_database->getClient();
972
        if ($client->isDebugEnabled()) {
973
            // record pipeline
974
            if ($client->hasLogger()) {
975
                $client->getLogger()->debug(
976
                    get_called_class() . ':<br><b>Pipeline</b>:<br>' .
977
                    json_encode($pipeline)
978
                );
979
            }
980
981
            // Check options only in debug mode. In production common exception will raised
982
            if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
983
                // get db version
984
                $dbVersion = $client->getDbVersion();
985
986
                // check options for db < 2.6
987
                if (version_compare($dbVersion, '2.6.0', '<')) {
988
                    if (!empty($options['explain'])) {
989
                        throw new FeatureNotSupportedException('Explain of aggregation implemented only from 2.6.0');
990
                    }
991
992
                    if (!empty($options['allowDiskUse'])) {
993
                        throw new FeatureNotSupportedException('Option allowDiskUse of aggregation implemented only from 2.6.0');
994
                    }
995
996
                    if (!empty($options['cursor'])) {
997
                        throw new FeatureNotSupportedException('Option cursor of aggregation implemented only from 2.6.0');
998
                    }
999
                }
1000
1001
                // check options for db < 3.2
1002
                if (version_compare($dbVersion, '3.2.0', '<')) {
1003
                    if (!empty($options['bypassDocumentValidation'])) {
1004
                        throw new FeatureNotSupportedException('Option bypassDocumentValidation of aggregation implemented only from 3.2.0');
1005
                    }
1006
1007
                    if (!empty($options['readConcern'])) {
1008
                        throw new FeatureNotSupportedException('Option readConcern of aggregation implemented only from 3.2.0');
1009
                    }
1010
                }
1011
            }
1012
        }
1013
1014
        // return result as cursor
1015
        if ($asCursor) {
1016
            if (version_compare(\MongoClient::VERSION, '1.5.0', '<')) {
1017
                throw new FeatureNotSupportedException('Aggregate cursor supported from driver version 1.5');
1018
            }
1019
            $cursor = $this->_mongoCollection->aggregateCursor($pipeline, $options);
0 ignored issues
show
Bug introduced by
The method aggregateCursor() does not exist on MongoCollection. Did you maybe mean aggregate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1020
            return $cursor;
1021
        }
1022
1023
        // prepare command
1024
        $command = array(
1025
            'aggregate' => $this->getName(),
1026
            'pipeline'  => $pipeline,
1027
        );
1028
1029
        // add options
1030
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1031
            $command += $options;
1032
        }
1033
1034
        // aggregate
1035
        $status = $this->_database->executeCommand($command);
1036
1037
        if($status['ok'] != 1) {
1038
            throw new Exception('Aggregate error: ' . $status['errmsg']);
1039
        }
1040
1041
        // explain response
1042
        if (!empty($command['explain'])) {
1043
            return $status['stages'];
1044
        }
1045
1046
        // result response
1047
        return $status['result'];
1048
    }
1049
1050
    /**
1051
     * Explain aggregation
1052
     *
1053
     * @deprecated use pipeline option 'explain' in Collection::aggregate() or method Pipeline::explain()
1054
     * @param array|Pipeline $pipeline
1055
     * @return array result
1056
     * @throws Exception
1057
     */
1058
    public function explainAggregate($pipeline)
1059
    {
1060
        if (version_compare($this->getDatabase()->getClient()->getDbVersion(), '2.6.0', '<')) {
1061
            throw new Exception('Explain of aggregation implemented only from 2.6.0');
1062
        }
1063
1064
        if ($pipeline instanceof Pipeline) {
1065
            $pipeline = $pipeline->toArray();
1066
        } else if (!is_array($pipeline)) {
1067
            throw new Exception('Wrong pipeline specified');
1068
        }
1069
1070
        // aggregate
1071
        return $this->_database->executeCommand(array(
1072
            'aggregate' => $this->getName(),
1073
            'pipeline'  => $pipeline,
1074
            'explain'   => true
1075
        ));
1076
    }
1077
1078
    /**
1079
     * Validates a collection. The method scans a collection’s data structures
1080
     * for correctness and returns a single document that describes the
1081
     * relationship between the logical collection and the physical
1082
     * representation of the data.
1083
     *
1084
     * @link http://docs.mongodb.org/manual/reference/method/db.collection.validate/
1085
     * @param bool $full Specify true to enable a full validation and to return
1086
     *      full statistics. MongoDB disables full validation by default because it
1087
     *      is a potentially resource-intensive operation.
1088
     * @return array
1089
     * @throws Exception
1090
     */
1091
    public function validate($full = false)
1092
    {
1093
        $response = $this->_mongoCollection->validate($full);
1094
        if(!$response || $response['ok'] != 1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1095
            throw new Exception($response['errmsg']);
1096
        }
1097
1098
        return $response;
1099
    }
1100
1101
    /**
1102
     * Create index
1103
     *
1104
     * @param array $key
1105
     * @param array $options see @link http://php.net/manual/en/mongocollection.ensureindex.php
1106
     * @return \Sokil\Mongo\Collection
1107
     */
1108
    public function ensureIndex(array $key, array $options = array())
1109
    {
1110
        $this->_mongoCollection->ensureIndex($key, $options);
1111
        return $this;
1112
    }
1113
1114
    /**
1115
     * Create unique index
1116
     *
1117
     * @param array $key
1118
     * @param boolean $dropDups
1119
     * @return \Sokil\Mongo\Collection
1120
     */
1121
    public function ensureUniqueIndex(array $key, $dropDups = false)
1122
    {
1123
        $this->_mongoCollection->ensureIndex($key, array(
1124
            'unique'    => true,
1125
            'dropDups'  => (bool) $dropDups,
1126
        ));
1127
1128
        return $this;
1129
    }
1130
1131
    /**
1132
     * Create sparse index.
1133
     *
1134
     * Sparse indexes only contain entries for documents that have the indexed
1135
     * field, even if the index field contains a null value. The index skips
1136
     * over any document that is missing the indexed field.
1137
     *
1138
     * @link http://docs.mongodb.org/manual/core/index-sparse/
1139
     *
1140
     * @param string|array $key An array specifying the index's fields as its
1141
     *  keys. For each field, the value is either the index direction or index
1142
     *  type. If specifying direction, specify 1 for ascending or -1
1143
     *  for descending.
1144
     *
1145
     * @return \Sokil\Mongo\Collection
1146
     */
1147
    public function ensureSparseIndex(array $key)
1148
    {
1149
        $this->_mongoCollection->ensureIndex($key, array(
1150
            'sparse'    => true,
1151
        ));
1152
1153
        return $this;
1154
    }
1155
1156
    /**
1157
     * Create TTL index
1158
     *
1159
     * @link http://docs.mongodb.org/manual/tutorial/expire-data/
1160
     *
1161
     * If seconds not specified then document expired at specified time, as
1162
     * described at @link http://docs.mongodb.org/manual/tutorial/expire-data/#expire-documents-at-a-certain-clock-time
1163
     *
1164
     * @param string|array $key key must be date to use TTL
1165
     * @param int $seconds
1166
     * @return \Sokil\Mongo\Collection
1167
     */
1168
    public function ensureTTLIndex(array $key, $seconds = 0)
1169
    {
1170
        $this->_mongoCollection->ensureIndex($key, array(
1171
            'expireAfterSeconds' => $seconds,
1172
        ));
1173
1174
        return $this;
1175
    }
1176
1177
    /**
1178
     * Create geo index 2dsphere
1179
     *
1180
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2dsphere-index/
1181
     *
1182
     * @param string $field
1183
     * @return \Sokil\Mongo\Collection
1184
     */
1185 View Code Duplication
    public function ensure2dSphereIndex($field)
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...
1186
    {
1187
        if (is_array($field)) {
1188
            $keys = array_fill_keys($field, '2dsphere');
1189
        } else {
1190
            $keys = array(
1191
                $field => '2dsphere',
1192
            );
1193
        }
1194
1195
        $this->_mongoCollection->ensureIndex($keys);
1196
1197
        return $this;
1198
    }
1199
1200
    /**
1201
     * Create geo index 2dsphere
1202
     *
1203
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2d-index/
1204
     *
1205
     * @param string $field
1206
     * @return \Sokil\Mongo\Collection
1207
     */
1208 View Code Duplication
    public function ensure2dIndex($field)
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...
1209
    {
1210
        if (is_array($field)) {
1211
            $keys = array_fill_keys($field, '2d');
1212
        } else {
1213
            $keys = array(
1214
                $field => '2d',
1215
            );
1216
        }
1217
1218
        $this->_mongoCollection->ensureIndex($keys);
1219
1220
        return $this;
1221
    }
1222
1223
    /**
1224
     * Create fulltext index
1225
     *
1226
     * @link https://docs.mongodb.org/manual/core/index-text/
1227
     * @link https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/
1228
     *
1229
     * If a collection contains documents or embedded documents that are in different languages,
1230
     * include a field named language in the documents or embedded documents and specify as its value the language
1231
     * for that document or embedded document.
1232
     *
1233
     * The specified language in the document overrides the default language for the text index.
1234
     * The specified language in an embedded document override the language specified in an enclosing document or
1235
     * the default language for the index.
1236
     *
1237
     * @param array|string $field definition of fields where full text index ensured.
1238
     *  May be string to ensure index on one field, array of fields to create full text index on few fields, and
1239
     *   widdcard '$**'  to create index on all fields of collection. Default value is '$**'
1240
     * @param $weights For a text index, the weight of an indexed field denotes the significance of the field
1241
     *   relative to the other indexed fields in terms of the text search score.
1242
     * @param $defaultLanguage The default language associated with the indexed data determines the rules to parse
1243
     *  word roots (i.e. stemming) and ignore stop words. The default language for the indexed data is english.
1244
     * @param $languageOverride To use a field with a name other than language, include the
1245
     *   language_override option when creating the index.
1246
     *
1247
     * @return Collection
1248
     */
1249
    public function ensureFulltextIndex(
1250
        $field = '$**',
1251
        array $weights = null,
1252
        $defaultLanguage = Language::ENGLISH,
1253
        $languageOverride = null
1254
    ) {
1255
        // keys
1256
        if (is_array($field)) {
1257
            $keys = array_fill_keys($field, 'text');
1258
        } else {
1259
            $keys = array(
1260
                $field => 'text',
1261
            );
1262
        }
1263
1264
        // options
1265
        $options = array(
1266
            'default_language' => $defaultLanguage,
1267
        );
1268
1269
        if (is_array($weights) && $weights) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $weights of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1270
            $options['weights'] = $weights;
1271
        }
1272
1273
        if ($languageOverride) {
1274
            $options['language_override'] = $languageOverride;
1275
        }
1276
1277
        // create index
1278
        $this->_mongoCollection->ensureIndex($keys, $options);
1279
1280
        return $this;
1281
    }
1282
1283
1284
1285
    /**
1286
     * Create indexes based on self::$_index metadata
1287
     *
1288
     * @return \Sokil\Mongo\Collection
1289
     * @throws \Exception
1290
     */
1291
    public function initIndexes()
1292
    {
1293
        // read index definition from collection options
1294
        // if not specified - use defined in property
1295
        $indexDefinition = $this->definition->getOption('index');
1296
1297
        // ensure indexes
1298
        foreach($indexDefinition as $options) {
1299
1300
            if(empty($options['keys'])) {
1301
                throw new Exception('Keys not specified');
1302
            }
1303
1304
            $keys = $options['keys'];
1305
            unset($options['keys']);
1306
1307
            $this->_mongoCollection->ensureIndex($keys, $options);
1308
        }
1309
1310
        return $this;
1311
    }
1312
1313
    /**
1314
     * Get index info
1315
     * @return array
1316
     */
1317
    public function getIndexes()
1318
    {
1319
        return $this->_mongoCollection->getIndexInfo();
1320
    }
1321
1322
    public function readPrimaryOnly()
1323
    {
1324
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY);
1325
        return $this;
1326
    }
1327
1328
    public function readPrimaryPreferred(array $tags = null)
1329
    {
1330
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY_PREFERRED, $tags);
1331
        return $this;
1332
    }
1333
1334
    public function readSecondaryOnly(array $tags = null)
1335
    {
1336
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY, $tags);
1337
        return $this;
1338
    }
1339
1340
    public function readSecondaryPreferred(array $tags = null)
1341
    {
1342
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, $tags);
1343
        return $this;
1344
    }
1345
1346
    public function readNearest(array $tags = null)
1347
    {
1348
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_NEAREST, $tags);
1349
        return $this;
1350
    }
1351
1352
    public function getReadPreference()
1353
    {
1354
        return $this->_mongoCollection->getReadPreference();
1355
    }
1356
1357
    /**
1358
     * Define write concern for all requests to current collection
1359
     *
1360
     * @param string|integer $w write concern
1361
     * @param int $timeout timeout in milliseconds
1362
     * @throws \Sokil\Mongo\Exception
1363
     * @return \Sokil\Mongo\Collection
1364
     */
1365
    public function setWriteConcern($w, $timeout = 10000)
1366
    {
1367
        if(!$this->_mongoCollection->setWriteConcern($w, (int) $timeout)) {
1368
            throw new Exception('Error setting write concern');
1369
        }
1370
1371
        return $this;
1372
    }
1373
1374
    /**
1375
     * Define unacknowledged write concern for all requests to current collection
1376
     *
1377
     * @param int $timeout timeout in milliseconds
1378
     * @throws \Sokil\Mongo\Exception
1379
     * @return \Sokil\Mongo\Collection
1380
     */
1381
    public function setUnacknowledgedWriteConcern($timeout = 10000)
1382
    {
1383
        $this->setWriteConcern(0, (int) $timeout);
1384
        return $this;
1385
    }
1386
1387
    /**
1388
     * Define majority write concern for all requests to current collection
1389
     *
1390
     * @param int $timeout timeout in milliseconds
1391
     * @throws \Sokil\Mongo\Exception
1392
     * @return \Sokil\Mongo\Collection
1393
     */
1394
    public function setMajorityWriteConcern($timeout = 10000)
1395
    {
1396
        $this->setWriteConcern('majority', (int) $timeout);
1397
        return $this;
1398
    }
1399
1400
    /**
1401
     * Get currently active write concern on all requests to collection
1402
     *
1403
     * @return int|string write concern
1404
     */
1405
    public function getWriteConcern()
1406
    {
1407
        return $this->_mongoCollection->getWriteConcern();
1408
    }
1409
1410
    /**
1411
     * Get collection stat
1412
     *
1413
     * @return array collection stat
1414
     */
1415
    public function stats()
1416
    {
1417
        return $this->getDatabase()->executeCommand(array(
1418
            'collstats' => $this->getName(),
1419
        ));
1420
    }
1421
}
1422