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 ( bdbde4...6b1bf7 )
by De
04:09
created

Collection::initCollection()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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