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 ( 2435fd...bdbde4 )
by De
06:26
created

Collection::hydrate()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 28
rs 5.3846
cc 8
eloc 15
nc 4
nop 2
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\Collection\Definition;
16
17
/**
18
 * Instance of this class is a representation of mongo collection.
19
 * It aggregates \MongoCollection instance.
20
 *
21
 * @link https://github.com/sokil/php-mongo#selecting-database-and-collection Selecting collection
22
 * @link https://github.com/sokil/php-mongo#querying-documents Querying documents
23
 * @link https://github.com/sokil/php-mongo#update-few-documents Update few documents
24
 * @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting collection
25
 *
26
 * @author Dmytro Sokil <[email protected]>
27
 */
28
class Collection implements \Countable
29
{
30
    /**
31
     * @var string expression class. This class may be overloaded to define
32
     *  own query methods (whereUserAgeGreatedThan(), etc.)
33
     * @deprecated since 1.13 Use 'expressionClass' declaration in mapping
34
     */
35
    protected $_queryExpressionClass;
36
37
    /**
38
     * @deprecated since 1.13 Use 'documentClass' declaration in mapping
39
     * @var string Default class for document
40
     */
41
    private $documentClass;
42
43
    /**
44
     * List of arrays, where each item array is an index definition.
45
     * Every index definition must contain key 'keys' with list of fields and orders,
46
     * and optional options from @link http://php.net/manual/en/mongocollection.createindex.php:
47
     *
48
     * Example:
49
     * array(
50
     *     array(
51
     *         'keys' => array('field1' => 1, 'field2' => -1),
52
     *         'unique' => true
53
     *     ),
54
     *     ...
55
     * )
56
     * @var array list of indexes
57
     * @deprecated since 1.13 Use 'index' declaration in mapping
58
     */
59
    protected $_index;
60
61
    /**
62
     *
63
     * @var \Sokil\Mongo\Database
64
     */
65
    protected $_database;
66
67
    /**
68
     *
69
     * @var \MongoCollection
70
     */
71
    protected $_mongoCollection;
72
73
    /**
74
     * Implementation of identity map pattern
75
     *
76
     * @var array list of cached documents
77
     */
78
    private $documentPool = array();
79
80
    /**
81
     *
82
     * @var bool cache or not documents
83
     */
84
    private $isDocumentPoolEnabled = true;
85
86
    /**
87
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
88
     * @var bool default value of versioning
89
     */
90
    protected $versioning;
91
92
    /**
93
     * @var \Sokil\Mongo\Collection\Definition collection options
94
     */
95
    private $definition;
96
97
    public function __construct(Database $database, $collection, Definition $definition = null)
98
    {
99
        // define db
100
        $this->_database = $database;
101
102
        $this->initCollection($collection);
103
104
        // init definition
105
        $this->definition = $definition ? $definition : new Definition();
106
107
        if($this->documentClass) {
108
            $this->definition->setOption('documentClass', $this->documentClass);
109
        }
110
        if($this->versioning !== null) {
111
            $this->definition->setOption('versioning', $this->versioning);
112
        }
113
        if($this->_index) {
114
            $this->definition->setOption('index', $this->_index);
115
        }
116
        if($this->_queryExpressionClass) {
117
            $this->definition->setOption('expressionClass', $this->_queryExpressionClass);
118
        }
119
    }
120
121
    protected function initCollection($collection)
122
    {
123
        // init mongo collection
124
        if($collection instanceof \MongoCollection) {
125
            $this->_mongoCollection = $collection;
126
        } else {
127
            $this->_mongoCollection = $this->_database->getMongoDB()->selectCollection($collection);
128
        }
129
    }
130
131
    /**
132
     * Start versioning documents on modify
133
     *
134
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
135
     * @return \Sokil\Mongo\Collection
136
     */
137
    public function enableVersioning()
138
    {
139
        $this->definition->setOption('versioning', true);
140
        return $this;
141
    }
142
143
    /**
144
     * Check if versioning enabled
145
     *
146
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
147
     * @return bool
148
     */
149
    public function isVersioningEnabled()
150
    {
151
        return $this->definition->getOption('versioning');
152
    }
153
154
    /**
155
     * Get option
156
     *
157
     * @param string|int $name
158
     * @return mixed
159
     */
160
    public function getOption($name)
161
    {
162
        return $this->definition->getOption($name);
163
    }
164
165
    public function getOptions()
166
    {
167
        return $this->definition->getOptions();
168
    }
169
170
    public function __get($name)
171
    {
172
        return $this->getDocument($name);
173
    }
174
175
    /**
176
     * Get name of collection
177
     * 
178
     * @return string name of collection
179
     */
180
    public function getName()
181
    {
182
        return $this->_mongoCollection->getName();
183
    }
184
185
    /**
186
     * Get native collection instance of mongo driver
187
     * 
188
     * @return \MongoCollection
189
     */
190
    public function getMongoCollection()
191
    {
192
        return $this->_mongoCollection;
193
    }
194
195
    /**
196
     *
197
     * @return \Sokil\Mongo\Database
198
     */
199
    public function getDatabase()
200
    {
201
        return $this->_database;
202
    }
203
204
    /**
205
     * Delete collection
206
     * 
207
     * @return \Sokil\Mongo\Collection
208
     * @throws \Sokil\Mongo\Exception
209
     */
210
    public function delete() {
211
        $status = $this->_mongoCollection->drop();
212
        if($status['ok'] != 1) {
213
            // check if collection exists
214
            if('ns not found' !== $status['errmsg']) {
215
                // collection exist
216
                throw new Exception('Error deleting collection ' . $this->getName() . ': ' . $status['errmsg']);
217
            }
218
        }
219
220
        return $this;
221
    }
222
223
    /**
224
     * Override to define class name of document by document data
225
     *
226
     * @param array $documentData
227
     * @return string Document class data
228
     */
229
    public function getDocumentClassName(array $documentData = null)
230
    {
231
        $documentClass = $this->definition->getOption('documentClass');
232
233
        if(is_callable($documentClass)) {
234
            return call_user_func($documentClass, $documentData);
235
        }
236
237
        if(class_exists($documentClass)) {
238
            return $documentClass;
239
        }
240
241
        throw new Exception('Property "documentClass" must be callable or valid name of class');
242
    }
243
244
    /**
245
     * Factory method to get not stored Document instance from array
246
     * @param array $data
247
     * @return \Sokil\Mongo\Document
248
     */
249
    public function createDocument(array $data = null)
250
    {
251
        $className = $this->getDocumentClassName($data);
252
253
        /* @var $document \Sokil\Mongo\Document */
254
        $document = new $className(
255
            $this,
256
            $data,
257
            array('stored' => false) + $this->definition->getOptions()
258
        );
259
260
        // store document to identity map
261
        if($this->isDocumentPoolEnabled()) {
262
            $collection = $this;
263
            $document->onAfterInsert(function(\Sokil\Mongo\Event $event) use($collection) {
264
                $collection->addDocumentToDocumentPool($event->getTarget());
265
            });
266
        }
267
268
        return $document;
269
    }
270
271
    /**
272
     * Factory method to get document object from array of stored document
273
     *
274
     * @param array $data
275
     * @return \Sokil\Mongo\Document
276
     */
277
    public function hydrate($data, $useDocumentPool = true)
278
    {
279
        if(!is_array($data) || !isset($data['_id'])) {
280
            throw new Exception('Document must be stored and has _id key');
281
        }
282
283
        // if document already in pool - return it
284
        if($useDocumentPool && $this->isDocumentPoolEnabled() && $this->isDocumentInDocumentPool($data['_id'])) {
285
            return $this
286
                ->getDocumentFromDocumentPool($data['_id'])
287
                ->mergeUnmodified($data);
288
        }
289
290
        // init document instance
291
        $className = $this->getDocumentClassName($data);
292
        $document = new $className(
293
            $this,
294
            $data,
295
            array('stored' => true) + $this->definition->getOptions()
296
        );
297
298
        // store document in cache
299
        if($useDocumentPool && $this->isDocumentPoolEnabled()) {
300
            $this->addDocumentToDocumentPool($document);
301
        }
302
303
        return $document;
304
    }
305
306
    /**
307
     * Total count of documents in collection
308
     * 
309
     * @return int 
310
     */
311
    public function count()
312
    {
313
        return $this->find()->count();
314
    }
315
316
    /**
317
     * Retrieve a list of distinct values for the given key across a collection.
318
     *
319
     * @param string $selector field selector
320
     * @param array|callable|\Sokil\Mongo\Expression $expression expression to search documents
321
     * @return array distinct values
322
     */
323
    public function getDistinct($selector, $expression = null)
324
    {
325
        if($expression) {
326
            return $this->_mongoCollection->distinct(
327
                $selector,
328
                Expression::convertToArray($expression)
329
            );
330
        }
331
332
        return $this->_mongoCollection->distinct($selector);
333
    }
334
335
    /**
336
     * Create new Expression instance to use in query builder or update operations
337
     * 
338
     * @return \Sokil\Mongo\Expression
339
     */
340
    public function expression()
341
    {
342
        $className = $this->definition->getExpressionClass();
343
        return new $className;
344
    }
345
346
    /**
347
     * Create Operator instance to use in update operations
348
     * 
349
     * @return \Sokil\Mongo\Operator
350
     */
351
    public function operator()
352
    {
353
        return new Operator();
354
    }
355
356
    /**
357
     * Create document query builder
358
     *
359
     * @param $callable callable|null Function to configure query builder&
360
     * @return \Sokil\Mongo\Cursor|\Sokil\Mongo\Expression
361
     */
362
    public function find($callable = null)
363
    {
364
        /** @var \Sokil\Mongo\Cursor $cursor */
365
        $cursor = new Cursor($this, array(
366
            'expressionClass'   => $this->definition->getExpressionClass(),
367
            'batchSize'         => $this->definition->getOption('batchSize'),
368
            'clientTimeout'     => $this->definition->getOption('cursorClientTimeout'),
369
            'serverTimeout'     => $this->definition->getOption('cursorServerTimeout'),
370
        ));
371
372
        if(is_callable($callable)) {
373
            $callable($cursor->getExpression());
374
        }
375
376
        return $cursor;
377
    }
378
379
    /**
380
     * Create document query builder
381
     *
382
     * @return \Sokil\Mongo\Cursor
383
     */
384
    public function findAsArray($callable = null)
385
    {
386
        return $this
387
            ->find($callable)
388
            ->asArray();
389
    }
390
391
    /**
392
     * Stop storing found documents to pool
393
     *
394
     * @return \Sokil\Mongo\Collection
395
     */
396
    public function disableDocumentPool()
397
    {
398
        $this->isDocumentPoolEnabled = false;
399
        return $this;
400
    }
401
402
    /**
403
     * Start storing found documents to pool
404
     *
405
     * @return \Sokil\Mongo\Collection
406
     */
407
    public function enableDocumentPool()
408
    {
409
        $this->isDocumentPoolEnabled = true;
410
        return $this;
411
    }
412
413
    /**
414
     * Check if document pool enabled and requested documents store to it
415
     *
416
     * @return bool
417
     */
418
    public function isDocumentPoolEnabled()
419
    {
420
        return $this->isDocumentPoolEnabled;
421
    }
422
423
    public function clearDocumentPool()
424
    {
425
        $this->documentPool = array();
426
        return $this;
427
    }
428
429
    /**
430
     * Check if documents are in pool
431
     *
432
     * @return bool
433
     */
434
    public function isDocumentPoolEmpty()
435
    {
436
        return !$this->documentPool;
437
    }
438
439
    /**
440
     * Store document to pool
441
     *
442
     * @param array $document
443
     * @return \Sokil\Mongo\Collection
444
     */
445
    public function addDocumentToDocumentPool(Document $document)
446
    {
447
        $documentId = (string) $document->getId();
448
449
        if(!isset($this->documentPool[$documentId])) {
450
            $this->documentPool[$documentId] = $document;
451
        } else {
452
            // merging because document after
453
            // load and before getting in second place may be changed
454
            // and this changes must be preserved:
455
            //
456
            // 1. Document loads and modifies in current session
457
            // 2. Document loads modified in another session
458
            // 3. Document loads once again in current session. Changes from stage 2 merges as unmodified
459
460
            $this->documentPool[$documentId]->mergeUnmodified($document->toArray());
461
        }
462
463
        return $this;
464
    }
465
466
    /**
467
     * Store documents to identity map
468
     *
469
     * @param array $documents list of Document instances
470
     * @return \Sokil\Mongo\Collection
471
     */
472
    public function addDocumentsToDocumentPool(array $documents)
473
    {
474
        foreach($documents as $document) {
475
            $this->addDocumentToDocumentPool($document);
476
        }
477
478
        return $this;
479
    }
480
481
    /**
482
     * Remove document instance from identity map
483
     *
484
     * @param \Sokil\Mongo\Document $document
485
     * @return \Sokil\Mongo\Collection
486
     */
487
    public function removeDocumentFromDocumentPool(Document $document)
488
    {
489
        unset($this->documentPool[(string) $document]);
490
        return $this;
491
    }
492
493
    /**
494
     * Get document from identity map by it's id
495
     *
496
     * @param string|int|\MongoId $id
497
     * @return \Sokil\Mongo\Document
498
     */
499
    public function getDocumentFromDocumentPool($id)
500
    {
501
        return $this->documentPool[(string) $id];
502
    }
503
504
    /**
505
     * Get documents from pool if they stored
506
     *
507
     * @param array $ids
508
     */
509
    public function getDocumentsFromDocumentPool(array $ids = null)
510
    {
511
        if(!$ids) {
512
            return $this->documentPool;
513
        }
514
515
        return array_intersect_key(
516
            $this->documentPool,
517
            array_flip(array_map('strval', $ids))
518
        );
519
    }
520
521
    /**
522
     * Get number of documents in document pool
523
     *
524
     * @return int
525
     */
526
    public function documentPoolCount()
527
    {
528
        return count($this->documentPool);
529
    }
530
531
    /**
532
     * Check if document exists in identity map
533
     *
534
     * @param \Sokil\Mongo\Document|\MongoId|int|string $document Document instance or it's id
535
     * @return boolean
536
     */
537
    public function isDocumentInDocumentPool($document)
538
    {
539
        if($document instanceof Document) {
540
            $document = $document->getId();
541
        }
542
543
        return isset($this->documentPool[(string) $document]);
544
    }
545
546
    /**
547
     * Get document by id
548
     * If callable specified, document always loaded directly omitting document pool.
549
     * Method may return document as array if cursor configured through Cursor::asArray()
550
     *
551
     * @param string|\MongoId $id
552
     * @param callable $callable cursor callable used to configure cursor
553
     * @return \Sokil\Mongo\Document|array|null
554
     */
555
    public function getDocument($id, $callable = null)
556
    {
557
        if(!$this->isDocumentPoolEnabled) {
558
            return $this->getDocumentDirectly($id, $callable);
559
        }
560
561
        if(!$callable && $this->isDocumentInDocumentPool($id)) {
562
            return $this->getDocumentFromDocumentPool($id);
563
        }
564
565
        $document = $this->getDocumentDirectly($id, $callable);
566
567
        // if callable configure cursor to return document as array,
568
        // than it can't be stored to document pool
569
        if($document instanceof Document) {
570
            $this->addDocumentToDocumentPool($document);
571
        }
572
573
        return $document;
574
    }
575
576
577
    /**
578
     * Get document by id directly omitting cache
579
     * Method may return document as array if cursor configured through Cursor::asArray()
580
     * 
581
     * @param string|\MongoId $id
582
     * @param callable $callable cursor callable used to configure cursor
583
     * @return \Sokil\Mongo\Document|array|null
584
     */
585
    public function getDocumentDirectly($id, $callable = null)
586
    {
587
        $cursor = $this->find();
588
589
        if(is_callable($callable)) {
590
            call_user_func($callable, $cursor);
591
        }
592
593
        return $cursor
594
            ->byId($id)
595
            ->skipDocumentPool()
596
            ->findOne();
597
    }
598
599
    /**
600
     * Check if document belongs to collection
601
     *
602
     * @param \Sokil\Mongo\Document $document
603
     * @return type
604
     */
605
    public function hasDocument(Document $document)
606
    {
607
        // check connection
608
        if($document->getCollection()->getDatabase()->getClient()->getDsn() !== $this->getDatabase()->getClient()->getDsn()) {
609
            return false;
610
        }
611
612
        // check database
613
        if ($document->getCollection()->getDatabase()->getName() !== $this->getDatabase()->getName()) {
614
            return false;
615
        }
616
617
        // check collection
618
        return $document->getCollection()->getName() == $this->getName();
619
    }
620
621
    /**
622
     * Get documents by list of id
623
     *
624
     * @param array $idList list of ids
625
     * @param callable $callable cursor callable used to configure cursor
626
     * @return array|null
627
     */
628
    public function getDocuments(array $idList, $callable = null)
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
629
630
631
    /**
632
     * Creates batch insert operation handler
633
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
634
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
635
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
636
     *   one item at a time) or can rearrange it. Defaults to TRUE
637
     * @return BatchInsert
638
     */
639
    public function createBatchInsert($writeConcern = null, $timeout = null, $ordered = null)
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_PUBLIC, expecting ';' or '{'
Loading history...
640
    {
641
        return new BatchInsert(
642
            $this,
643
            $writeConcern,
644
            $timeout,
645
            $ordered
646
        );
647
    }
648
649
    /**
650
     * Creates batch update operation handler
651
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
652
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
653
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
654
     *   one item at a time) or can rearrange it. Defaults to TRUE
655
     * @return BatchUpdate
656
     */
657
    public function createBatchUpdate($writeConcern = null, $timeout = null, $ordered = null)
658
    {
659
        return new BatchUpdate(
660
            $this,
661
            $writeConcern,
662
            $timeout,
663
            $ordered
664
        );
665
    }
666
667
    /**
668
     * Creates batch delete operation handler
669
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
670
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
671
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
672
     *   one item at a time) or can rearrange it. Defaults to TRUE
673
     * @return BatchDelete
674
     */
675
    public function createBatchDelete($writeConcern = null, $timeout = null, $ordered = null)
676
    {
677
        return new BatchDelete(
678
            $this,
679
            $writeConcern,
680
            $timeout,
681
            $ordered
682
        );
683
    }
684
685
    /**
686
     * @deprecated since 1.13. Use Document::delete()
687
     * @param \Sokil\Mongo\Document $document
688
     * @return \Sokil\Mongo\Collection
689
     */
690
    public function deleteDocument(Document $document)
691
    {
692
        $document->delete();
693
        return $this;
694
    }
695
696
    /**
697
     * Delete documents by expression
698
     * 
699
     * @param callable|array|\Sokil\Mongo\Expression $expression
700
     * @return \Sokil\Mongo\Collection
701
     * @throws Exception
702
     */
703
    public function batchDelete($expression = array())
704
    {
705
        // remove
706
        $result = $this->_mongoCollection->remove(
707
            Expression::convertToArray($expression)
708
        );
709
710
        // check result
711
        if(true !== $result && $result['ok'] != 1) {
712
            throw new Exception('Error removing documents from collection: ' . $result['err']);
713
        }
714
715
        return $this;
716
    }
717
718
    /**
719
     * @deprecated since 1.13. Use Collection::batchDelete();
720
     */
721
    public function deleteDocuments($expression = array())
722
    {
723
        return $this->batchDelete($expression);
724
    }
725
726
    /**
727
     * Insert multiple documents defined as arrays
728
     *
729
     * Prior to version 1.5.0 of the driver it was possible to use MongoCollection::batchInsert(),
730
     * however, as of 1.5.0 that method is now discouraged.
731
     *
732
     * You can use Collection::createBatchInsert()
733
     *
734
     * @param array $rows list of documents to insert, defined as arrays
735
     * @return \Sokil\Mongo\Collection
736
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
737
     * @throws \Sokil\Mongo\Exception
738
     */
739
    public function batchInsert($rows, $validate = true)
740
    {
741
        if($validate) {
742
            $document = $this->createDocument();
743
            foreach($rows as $row) {
744
                $document->merge($row);
745
746
                if(!$document->isValid()) {
747
                    throw new InvalidDocumentException('Document is invalid on batch insert');
748
                }
749
750
                $document->reset();
751
            }
752
        }
753
754
        $result = $this->_mongoCollection->batchInsert($rows);
755
756
        // If the w parameter is set to acknowledge the write,
757
        // returns an associative array with the status of the inserts ("ok")
758
        // and any error that may have occurred ("err").
759
        if(is_array($result)) {
760
            if($result['ok'] != 1) {
761
                throw new Exception('Batch insert error: ' . $result['err']);
762
            }
763
764
            return $this;
765
        }
766
767
        // Otherwise, returns TRUE if the batch insert was successfully sent,
768
        // FALSE otherwise.
769
        if(!$result) {
770
            throw new Exception('Batch insert error');
771
        }
772
773
        return $this;
774
    }
775
776
    /**
777
     * @deprecated since 1.13 Use Collection::batchInsert()
778
     */
779
    public function insertMultiple($rows, $validate = true)
780
    {
781
        return $this->batchInsert($rows, $validate);
782
    }
783
784
    /**
785
     * Direct insert of array to MongoDB without creating document object and validation
786
     *
787
     * @param array $document
788
     * @return \Sokil\Mongo\Collection
789
     * @throws Exception
790
     */
791
    public function insert(array $document)
792
    {
793
        $result = $this->_mongoCollection->insert($document);
794
795
        // if write concern acknowledged
796
        if(is_array($result)) {
797
            if($result['ok'] != 1) {
798
                throw new Exception('Insert error: ' . $result['err'] . ': ' . $result['errmsg']);
799
            }
800
801
            return $this;
802
        }
803
804
        // if write concern unacknowledged
805
        if(!$result) {
806
            throw new Exception('Insert error');
807
        }
808
809
        return $this;
810
    }
811
812
    /**
813
     * Update multiple documents
814
     *
815
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
816
     *  which documents will change.
817
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators to update
818
     * @param array $options update options, see http://php.net/manual/ru/mongocollection.update.php
819
     * @return \Sokil\Mongo\Collection
820
     * @throws \Sokil\Mongo\Exception
821
     */
822
    public function update($expression, $updateData, array $options = array())
823
    {
824
        // execute update operator
825
        $result = $this->_mongoCollection->update(
826
            Expression::convertToArray($expression),
827
            Operator::convertToArray($updateData),
828
            $options
829
        );
830
831
        // if write concern acknowledged
832
        if(is_array($result)) {
833
            if($result['ok'] != 1) {
834
                throw new Exception(sprintf('Update error: %s: %s', $result['err'], $result['errmsg']));
835
            }
836
            return $this;
837
        }
838
839
        // if write concern unacknowledged
840
        if(!$result) {
841
            throw new Exception('Update error');
842
        }
843
844
        return $this;
845
    }
846
847
    /**
848
     * Update multiple documents
849
     *
850
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
851
     *  which documents will change.
852
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
853
     *  to update
854
     * @return \Sokil\Mongo\Collection
855
     * @throws \Sokil\Mongo\Exception
856
     */
857
    public function batchUpdate($expression, $updateData)
858
    {
859
        return $this->update($expression, $updateData, array(
860
            'multiple'  => true,
861
        ));
862
    }
863
864
    /**
865
     * @deprecated since 1.13 Use Collection::batchUpdate()
866
     */
867
    public function updateMultiple($expression, $updateData)
868
    {
869
        return $this->batchUpdate($expression, $updateData);
870
    }
871
872
    /**
873
     * Update all documents
874
     *
875
     * @deprecated since 1.13. Use Collection::batchUpdate([])
876
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
877
     * @return \Sokil\Mongo\Collection
878
     * @throws \Sokil\Mongo\Exception
879
     */
880
    public function updateAll($updateData)
881
    {
882
        return $this->update(array(), $updateData, array(
883
            'multiple'  => true,
884
        ));
885
    }
886
887
    /**
888
     * Create aggregator pipeline instance
889
     *
890
     * @return \Sokil\Mongo\Pipeline
891
     * @deprecated since 1.10.10, use Collection::createAggregator() or callable in Collection::aggregate()
892
     */
893
    public function createPipeline()
894
    {
895
        return $this->createAggregator();
896
    }
897
898
    /**
899
     * Start aggregation
900
     *
901
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
902
     * @return \Sokil\Mongo\Pipeline
903
     */
904
    public function createAggregator()
905
    {
906
        return new Pipeline($this);
907
    }
908
909
    /**
910
     * Aggregate using pipeline
911
     *
912
     * @param callable|array|\Sokil\Mongo\Pipeline $pipeline list of pipeline stages
913
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
914
     * @return array result of aggregation
915
     * @throws \Sokil\Mongo\Exception
916
     */
917
    public function aggregate($pipeline)
918
    {
919
        // configure through callable
920
        if (is_callable($pipeline)) {
921
            $pipelineConfiguratorCallable = $pipeline;
922
            $pipeline = $this->createAggregator();
923
            call_user_func($pipelineConfiguratorCallable, $pipeline);
924
        }
925
926
        // get aggregation array
927
        if ($pipeline instanceof Pipeline) {
928
            $pipeline = $pipeline->toArray();
929
        } elseif (!is_array($pipeline)) {
930
            throw new Exception('Wrong pipeline specified');
931
        }
932
933
        // log
934
        $client = $this->_database->getClient();
935
        if($client->hasLogger()) {
936
            $client->getLogger()->debug(
937
                get_called_class() . ':<br><b>Pipeline</b>:<br>' .
938
                json_encode($pipeline));
939
        }
940
941
        // aggregate
942
        $status = $this->_database->executeCommand(array(
943
            'aggregate' => $this->getName(),
944
            'pipeline'  => $pipeline
945
        ));
946
947
        if($status['ok'] != 1) {
948
            throw new Exception('Aggregate error: ' . $status['errmsg']);
949
        }
950
951
        return $status['result'];
952
    }
953
954
    public function explainAggregate($pipeline)
955
    {
956
        if(version_compare($this->getDatabase()->getClient()->getDbVersion(), '2.6.0', '<')) {
957
            throw new Exception('Explain of aggregation implemented only from 2.6.0');
958
        }
959
960
        if($pipeline instanceof Pipeline) {
961
            $pipeline = $pipeline->toArray();
962
        }
963
        elseif(!is_array($pipeline)) {
964
            throw new Exception('Wrong pipeline specified');
965
        }
966
967
        // aggregate
968
        return $this->_database->executeCommand(array(
969
            'aggregate' => $this->getName(),
970
            'pipeline'  => $pipeline,
971
            'explain'   => true
972
        ));
973
    }
974
975
    /**
976
     * Validates a collection. The method scans a collection’s data structures
977
     * for correctness and returns a single document that describes the
978
     * relationship between the logical collection and the physical
979
     * representation of the data.
980
     *
981
     * @link http://docs.mongodb.org/manual/reference/method/db.collection.validate/
982
     * @param bool $full Specify true to enable a full validation and to return
983
     *      full statistics. MongoDB disables full validation by default because it
984
     *      is a potentially resource-intensive operation.
985
     * @return array
986
     * @throws Exception
987
     */
988
    public function validate($full = false)
989
    {
990
        $response = $this->_mongoCollection->validate($full);
991
        if(!$response || $response['ok'] != 1) {
992
            throw new Exception($response['errmsg']);
993
        }
994
995
        return $response;
996
    }
997
998
    /**
999
     * Create index
1000
     *
1001
     * @param array $key
1002
     * @param array $options see @link http://php.net/manual/en/mongocollection.ensureindex.php
1003
     * @return \Sokil\Mongo\Collection
1004
     */
1005
    public function ensureIndex($key, array $options = array())
1006
    {
1007
        $this->_mongoCollection->ensureIndex($key, $options);
1008
        return $this;
1009
    }
1010
1011
    /**
1012
     * Create unique index
1013
     *
1014
     * @param array $key
1015
     * @param boolean $dropDups
1016
     * @return \Sokil\Mongo\Collection
1017
     */
1018
    public function ensureUniqueIndex($key, $dropDups = false)
1019
    {
1020
        $this->_mongoCollection->ensureIndex($key, array(
1021
            'unique'    => true,
1022
            'dropDups'  => (bool) $dropDups,
1023
        ));
1024
1025
        return $this;
1026
    }
1027
1028
    /**
1029
     * Create sparse index.
1030
     *
1031
     * Sparse indexes only contain entries for documents that have the indexed
1032
     * field, even if the index field contains a null value. The index skips
1033
     * over any document that is missing the indexed field.
1034
     *
1035
     * @link http://docs.mongodb.org/manual/core/index-sparse/
1036
     *
1037
     * @param string|array $key An array specifying the index's fields as its
1038
     *  keys. For each field, the value is either the index direction or index
1039
     *  type. If specifying direction, specify 1 for ascending or -1
1040
     *  for descending.
1041
     *
1042
     * @return \Sokil\Mongo\Collection
1043
     */
1044
    public function ensureSparseIndex($key)
1045
    {
1046
        $this->_mongoCollection->ensureIndex($key, array(
1047
            'sparse'    => true,
1048
        ));
1049
1050
        return $this;
1051
    }
1052
1053
    /**
1054
     * Create TTL index
1055
     *
1056
     * @link http://docs.mongodb.org/manual/tutorial/expire-data/
1057
     *
1058
     * If seconds not specified then document expired at specified time, as
1059
     * described at @link http://docs.mongodb.org/manual/tutorial/expire-data/#expire-documents-at-a-certain-clock-time
1060
     *
1061
     * @param string|array $key key must be date to use TTL
1062
     * @param int $seconds
1063
     * @return \Sokil\Mongo\Collection
1064
     */
1065
    public function ensureTTLIndex($key, $seconds = 0)
1066
    {
1067
        $this->_mongoCollection->ensureIndex($key, array(
1068
            'expireAfterSeconds' => $seconds,
1069
        ));
1070
1071
        return $this;
1072
    }
1073
1074
    /**
1075
     * Create geo index 2dsphere
1076
     *
1077
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2dsphere-index/
1078
     *
1079
     * @param string $field
1080
     * @return \Sokil\Mongo\Collection
1081
     */
1082
    public function ensure2dSphereIndex($field)
1083
    {
1084
        $this->_mongoCollection->ensureIndex(array(
1085
            $field => '2dsphere',
1086
        ));
1087
1088
        return $this;
1089
    }
1090
1091
    /**
1092
     * Create geo index 2dsphere
1093
     *
1094
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2d-index/
1095
     *
1096
     * @param string $field
1097
     * @return \Sokil\Mongo\Collection
1098
     */
1099
    public function ensure2dIndex($field)
1100
    {
1101
        $this->_mongoCollection->ensureIndex(array(
1102
            $field => '2d',
1103
        ));
1104
1105
        return $this;
1106
    }
1107
1108
    /**
1109
     * Create indexes based on self::$_index metadata
1110
     *
1111
     * @return \Sokil\Mongo\Collection
1112
     * @throws \Exception
1113
     */
1114
    public function initIndexes()
1115
    {
1116
        // read index definition from collection options
1117
        // if not specified - use defined in property
1118
        $indexDefinition = $this->definition->getOption('index');
1119
1120
        // ensure indexes
1121
        foreach($indexDefinition as $options) {
1122
1123
            if(empty($options['keys'])) {
1124
                throw new Exception('Keys not specified');
1125
            }
1126
1127
            $keys = $options['keys'];
1128
            unset($options['keys']);
1129
1130
            $this->_mongoCollection->ensureIndex($keys, $options);
1131
        }
1132
1133
        return $this;
1134
    }
1135
1136
    /**
1137
     * Get index info
1138
     * @return array
1139
     */
1140
    public function getIndexes()
1141
    {
1142
        return $this->_mongoCollection->getIndexInfo();
1143
    }
1144
1145
    public function readPrimaryOnly()
1146
    {
1147
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY);
1148
        return $this;
1149
    }
1150
1151
    public function readPrimaryPreferred(array $tags = null)
1152
    {
1153
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY_PREFERRED, $tags);
1154
        return $this;
1155
    }
1156
1157
    public function readSecondaryOnly(array $tags = null)
1158
    {
1159
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY, $tags);
1160
        return $this;
1161
    }
1162
1163
    public function readSecondaryPreferred(array $tags = null)
1164
    {
1165
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, $tags);
1166
        return $this;
1167
    }
1168
1169
    public function readNearest(array $tags = null)
1170
    {
1171
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_NEAREST, $tags);
1172
        return $this;
1173
    }
1174
1175
    public function getReadPreference()
1176
    {
1177
        return $this->_mongoCollection->getReadPreference();
1178
    }
1179
1180
    /**
1181
     * Define write concern for all requests to current collection
1182
     *
1183
     * @param string|integer $w write concern
1184
     * @param int $timeout timeout in milliseconds
1185
     * @throws \Sokil\Mongo\Exception
1186
     * @return \Sokil\Mongo\Collection
1187
     */
1188
    public function setWriteConcern($w, $timeout = 10000)
1189
    {
1190
        if(!$this->_mongoCollection->setWriteConcern($w, (int) $timeout)) {
1191
            throw new Exception('Error setting write concern');
1192
        }
1193
1194
        return $this;
1195
    }
1196
1197
    /**
1198
     * Define unacknowledged write concern for all requests to current collection
1199
     *
1200
     * @param int $timeout timeout in milliseconds
1201
     * @throws \Sokil\Mongo\Exception
1202
     * @return \Sokil\Mongo\Collection
1203
     */
1204
    public function setUnacknowledgedWriteConcern($timeout = 10000)
1205
    {
1206
        $this->setWriteConcern(0, (int) $timeout);
1207
        return $this;
1208
    }
1209
1210
    /**
1211
     * Define majority write concern for all requests to current collection
1212
     *
1213
     * @param int $timeout timeout in milliseconds
1214
     * @throws \Sokil\Mongo\Exception
1215
     * @return \Sokil\Mongo\Collection
1216
     */
1217
    public function setMajorityWriteConcern($timeout = 10000)
1218
    {
1219
        $this->setWriteConcern('majority', (int) $timeout);
1220
        return $this;
1221
    }
1222
1223
    /**
1224
     * Get currently active write concern on all requests to collection
1225
     *
1226
     * @return int|string write concern
1227
     */
1228
    public function getWriteConcern()
1229
    {
1230
        return $this->_mongoCollection->getWriteConcern();
1231
    }
1232
1233
    /**
1234
     * Get collection stat
1235
     *
1236
     * @return array collection stat
1237
     */
1238
    public function stats()
1239
    {
1240
        return $this->getDatabase()->executeCommand(array(
1241
            'collstats' => $this->getName(),
1242
        ));
1243
    }
1244
}
1245