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 ( f6151a...357aa0 )
by De
02:51
created

Collection::initIndexes()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 0
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
    protected $mongoCollectionClassName = '\MongoCollection';
33
34
    /**
35
     * @var string expression class. This class may be overloaded to define
36
     *  own query methods (whereUserAgeGreatedThan(), etc.)
37
     * @deprecated since 1.13 Use 'expressionClass' declaration in mapping
38
     */
39
    protected $_queryExpressionClass;
40
41
    /**
42
     * @deprecated since 1.13 Use 'documentClass' declaration in mapping
43
     * @var string Default class for document
44
     */
45
    private $documentClass;
46
47
    /**
48
     * List of arrays, where each item array is an index definition.
49
     * Every index definition must contain key 'keys' with list of fields and orders,
50
     * and optional options from @link http://php.net/manual/en/mongocollection.createindex.php:
51
     *
52
     * Example:
53
     * array(
54
     *     array(
55
     *         'keys' => array('field1' => 1, 'field2' => -1),
56
     *         'unique' => true
57
     *     ),
58
     *     ...
59
     * )
60
     * @var array list of indexes
61
     * @deprecated since 1.13 Use 'index' declaration in mapping
62
     */
63
    protected $_index;
64
65
    /**
66
     *
67
     * @var \Sokil\Mongo\Database
68
     */
69
    private $database;
70
71
    /**
72
     *
73
     * @var \MongoCollection
74
     */
75
    private $collection;
76
77
    /**
78
     * @var string
79
     */
80
    private $collectionName;
81
82
    /**
83
     * Implementation of identity map pattern
84
     *
85
     * @var array list of cached documents
86
     */
87
    private $documentPool = array();
88
89
    /**
90
     *
91
     * @var bool cache or not documents
92
     */
93
    private $isDocumentPoolEnabled = true;
94
95
    /**
96
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
97
     * @var bool default value of versioning
98
     */
99
    protected $versioning;
100
101
    /**
102
     * @var \Sokil\Mongo\Collection\Definition collection options
103
     */
104
    private $definition;
105
106
    public function __construct(
107
        Database $database,
108
        $collection,
109
        Definition $definition = null
110
    ) {
111
        // define db
112
        $this->database = $database;
113
114
        // init mongo collection
115
        if ($collection instanceof \MongoCollection) {
116
            $this->collectionName = $collection->getName();
117
            $this->collection = $collection;
118
        } else {
119
            $this->collectionName = $collection;
120
        }
121
122
        // init definition
123
        $this->definition = $definition ? $definition : new Definition();
124
125
        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...
126
            $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...
127
        }
128
129
        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...
130
            $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...
131
        }
132
133
        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...
134
            $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...
135
        }
136
137
        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...
138
            $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...
139
        }
140
    }
141
142
    /**
143
     * Start versioning documents on modify
144
     *
145
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
146
     * @return \Sokil\Mongo\Collection
147
     */
148
    public function enableVersioning()
149
    {
150
        $this->definition->setOption('versioning', true);
151
        return $this;
152
    }
153
154
    /**
155
     * Check if versioning enabled
156
     *
157
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
158
     * @return bool
159
     */
160
    public function isVersioningEnabled()
161
    {
162
        return $this->definition->getOption('versioning');
163
    }
164
165
    /**
166
     * Get option
167
     *
168
     * @param string|int $name
169
     * @return mixed
170
     */
171
    public function getOption($name)
172
    {
173
        return $this->definition->getOption($name);
174
    }
175
176
    public function getOptions()
177
    {
178
        return $this->definition->getOptions();
179
    }
180
181
    public function __get($name)
182
    {
183
        // support of deprecated property, use selg::getMongoCollection instead
184
        if ($name === '_mongoCollection') {
185
            return $this->getMongoCollection();
186
        }
187
188
        return $this->getDocument($name);
189
    }
190
191
    /**
192
     * Get name of collection
193
     * 
194
     * @return string name of collection
195
     */
196
    public function getName()
197
    {
198
        return $this->collectionName;
199
    }
200
201
    /**
202
     * Get native collection instance of mongo driver
203
     * 
204
     * @return \MongoCollection
205
     */
206
    public function getMongoCollection()
207
    {
208
        if (empty($this->collection)) {
209
            $mongoCollectionClassName = $this->mongoCollectionClassName;
210
            $this->collection = new $mongoCollectionClassName(
211
                $this->database->getMongoDB(),
212
                $this->collectionName
213
            );
214
        }
215
216
        return $this->collection;
217
    }
218
219
    /**
220
     *
221
     * @return \Sokil\Mongo\Database
222
     */
223
    public function getDatabase()
224
    {
225
        return $this->database;
226
    }
227
228
    /**
229
     * Delete collection
230
     * 
231
     * @return \Sokil\Mongo\Collection
232
     * @throws \Sokil\Mongo\Exception
233
     */
234
    public function delete() {
235
        $status = $this->getMongoCollection()->drop();
236
        if($status['ok'] != 1) {
237
            // check if collection exists
238
            if('ns not found' !== $status['errmsg']) {
239
                // collection exist
240
                throw new Exception('Error deleting collection ' . $this->getName() . ': ' . $status['errmsg']);
241
            }
242
        }
243
244
        return $this;
245
    }
246
247
    /**
248
     * Override to define class name of document by document data
249
     *
250
     * @param array $documentData
251
     * @return string Document class data
252
     */
253
    public function getDocumentClassName(array $documentData = null)
254
    {
255
        $documentClass = $this->definition->getOption('documentClass');
256
257
        if(is_callable($documentClass)) {
258
            return call_user_func($documentClass, $documentData);
259
        }
260
261
        if(class_exists($documentClass)) {
262
            return $documentClass;
263
        }
264
265
        throw new Exception('Property "documentClass" must be callable or valid name of class');
266
    }
267
268
    /**
269
     * Factory method to get not stored Document instance from array
270
     * @param array $data
271
     * @return Document
272
     */
273
    public function createDocument(array $data = null)
274
    {
275
        $className = $this->getDocumentClassName($data);
276
277
        /* @var $document \Sokil\Mongo\Document */
278
        $document = new $className(
279
            $this,
280
            $data,
281
            array('stored' => false) + $this->definition->getOptions()
282
        );
283
284
        // store document to identity map
285
        if($this->isDocumentPoolEnabled()) {
286
            $collection = $this;
287
            $document->onAfterInsert(function(\Sokil\Mongo\Event $event) use($collection) {
288
                $collection->addDocumentToDocumentPool($event->getTarget());
289
            });
290
        }
291
292
        return $document;
293
    }
294
295
    /**
296
     * Factory method to get document object from array of stored document
297
     *
298
     * @param array $data
299
     * @return \Sokil\Mongo\Document
300
     */
301
    public function hydrate($data, $useDocumentPool = true)
302
    {
303
        if (!is_array($data) || !isset($data['_id'])) {
304
            throw new Exception('Document must be stored and has _id key');
305
        }
306
307
        // if document already in pool - return it
308
        if($useDocumentPool && $this->isDocumentPoolEnabled() && $this->isDocumentInDocumentPool($data['_id'])) {
309
            return $this
310
                ->getDocumentFromDocumentPool($data['_id'])
311
                ->mergeUnmodified($data);
312
        }
313
314
        // init document instance
315
        $className = $this->getDocumentClassName($data);
316
        $document = new $className(
317
            $this,
318
            $data,
319
            array('stored' => true) + $this->definition->getOptions()
320
        );
321
322
        // store document in cache
323
        if($useDocumentPool && $this->isDocumentPoolEnabled()) {
324
            $this->addDocumentToDocumentPool($document);
325
        }
326
327
        return $document;
328
    }
329
330
    /**
331
     * Total count of documents in collection
332
     * 
333
     * @return int 
334
     */
335
    public function count()
336
    {
337
        return $this->find()->count();
0 ignored issues
show
Bug introduced by
The method count does only exist in Sokil\Mongo\Cursor, but not in Sokil\Mongo\Expression.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
338
    }
339
340
    /**
341
     * Retrieve a list of distinct values for the given key across a collection.
342
     *
343
     * @param string $selector field selector
344
     * @param array|callable|\Sokil\Mongo\Expression $expression expression to search documents
345
     * @return array distinct values
346
     */
347
    public function getDistinct($selector, $expression = null)
348
    {
349
        if($expression) {
350
            return $this->getMongoCollection()->distinct(
351
                $selector,
352
                Expression::convertToArray($expression)
353
            );
354
        }
355
356
        return $this->getMongoCollection()->distinct($selector);
357
    }
358
359
    /**
360
     * Create new Expression instance to use in query builder or update operations
361
     * 
362
     * @return \Sokil\Mongo\Expression
363
     */
364
    public function expression()
365
    {
366
        $className = $this->definition->getExpressionClass();
367
        return new $className;
368
    }
369
370
    /**
371
     * Create Operator instance to use in update operations
372
     * 
373
     * @return \Sokil\Mongo\Operator
374
     */
375
    public function operator()
376
    {
377
        return new Operator();
378
    }
379
380
    /**
381
     * Create document query builder
382
     *
383
     * @param $callable callable|null Function to configure query builder&
384
     * @return \Sokil\Mongo\Cursor|\Sokil\Mongo\Expression
385
     */
386
    public function find($callable = null)
387
    {
388
        /** @var \Sokil\Mongo\Cursor $cursor */
389
        $cursor = new Cursor($this, array(
390
            'expressionClass'   => $this->definition->getExpressionClass(),
391
            'batchSize'         => $this->definition->getOption('batchSize'),
392
            'clientTimeout'     => $this->definition->getOption('cursorClientTimeout'),
393
            'serverTimeout'     => $this->definition->getOption('cursorServerTimeout'),
394
        ));
395
396
        if(is_callable($callable)) {
397
            $callable($cursor->getExpression());
398
        }
399
400
        return $cursor;
401
    }
402
403
    /**
404
     * Create document query builder
405
     *
406
     * @return \Sokil\Mongo\Cursor
407
     */
408
    public function findAsArray($callable = null)
409
    {
410
        return $this
0 ignored issues
show
Bug introduced by
The method asArray does only exist in Sokil\Mongo\Cursor, but not in Sokil\Mongo\Expression.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
411
            ->find($callable)
412
            ->asArray();
413
    }
414
415
    /**
416
     * Stop storing found documents to pool
417
     *
418
     * @return \Sokil\Mongo\Collection
419
     */
420
    public function disableDocumentPool()
421
    {
422
        $this->isDocumentPoolEnabled = false;
423
        return $this;
424
    }
425
426
    /**
427
     * Start storing found documents to pool
428
     *
429
     * @return \Sokil\Mongo\Collection
430
     */
431
    public function enableDocumentPool()
432
    {
433
        $this->isDocumentPoolEnabled = true;
434
        return $this;
435
    }
436
437
    /**
438
     * Check if document pool enabled and requested documents store to it
439
     *
440
     * @return bool
441
     */
442
    public function isDocumentPoolEnabled()
443
    {
444
        return $this->isDocumentPoolEnabled;
445
    }
446
447
    public function clearDocumentPool()
448
    {
449
        $this->documentPool = array();
450
        return $this;
451
    }
452
453
    /**
454
     * Check if documents are in pool
455
     *
456
     * @return bool
457
     */
458
    public function isDocumentPoolEmpty()
459
    {
460
        return !$this->documentPool;
461
    }
462
463
    /**
464
     * Store document to pool
465
     *
466
     * @param array $document
467
     * @return \Sokil\Mongo\Collection
468
     */
469
    public function addDocumentToDocumentPool(Document $document)
470
    {
471
        $documentId = (string) $document->getId();
472
473
        if(!isset($this->documentPool[$documentId])) {
474
            $this->documentPool[$documentId] = $document;
475
        } else {
476
            // merging because document after
477
            // load and before getting in second place may be changed
478
            // and this changes must be preserved:
479
            //
480
            // 1. Document loads and modifies in current session
481
            // 2. Document loads modified in another session
482
            // 3. Document loads once again in current session. Changes from stage 2 merges as unmodified
483
484
            $this->documentPool[$documentId]->mergeUnmodified($document->toArray());
485
        }
486
487
        return $this;
488
    }
489
490
    /**
491
     * Store documents to identity map
492
     *
493
     * @param array $documents list of Document instances
494
     * @return \Sokil\Mongo\Collection
495
     */
496
    public function addDocumentsToDocumentPool(array $documents)
497
    {
498
        foreach($documents as $document) {
499
            $this->addDocumentToDocumentPool($document);
500
        }
501
502
        return $this;
503
    }
504
505
    /**
506
     * Remove document instance from identity map
507
     *
508
     * @param \Sokil\Mongo\Document $document
509
     * @return \Sokil\Mongo\Collection
510
     */
511
    public function removeDocumentFromDocumentPool(Document $document)
512
    {
513
        unset($this->documentPool[(string) $document]);
514
        return $this;
515
    }
516
517
    /**
518
     * Get document from identity map by it's id
519
     *
520
     * @param string|int|\MongoId $id
521
     * @return \Sokil\Mongo\Document
522
     */
523
    public function getDocumentFromDocumentPool($id)
524
    {
525
        return $this->documentPool[(string) $id];
526
    }
527
528
    /**
529
     * Get documents from pool if they stored
530
     *
531
     * @param array $ids
532
     */
533
    public function getDocumentsFromDocumentPool(array $ids = null)
534
    {
535
        if(!$ids) {
536
            return $this->documentPool;
537
        }
538
539
        return array_intersect_key(
540
            $this->documentPool,
541
            array_flip(array_map('strval', $ids))
542
        );
543
    }
544
545
    /**
546
     * Get number of documents in document pool
547
     *
548
     * @return int
549
     */
550
    public function documentPoolCount()
551
    {
552
        return count($this->documentPool);
553
    }
554
555
    /**
556
     * Check if document exists in identity map
557
     *
558
     * @param \Sokil\Mongo\Document|\MongoId|int|string $document Document instance or it's id
559
     * @return boolean
560
     */
561
    public function isDocumentInDocumentPool($document)
562
    {
563
        if($document instanceof Document) {
564
            $document = $document->getId();
565
        }
566
567
        return isset($this->documentPool[(string) $document]);
568
    }
569
570
    /**
571
     * Get document by id
572
     * If callable specified, document always loaded directly omitting document pool.
573
     * Method may return document as array if cursor configured through Cursor::asArray()
574
     *
575
     * @param string|\MongoId $id
576
     * @param callable $callable cursor callable used to configure cursor
577
     * @return \Sokil\Mongo\Document|array|null
578
     */
579
    public function getDocument($id, $callable = null)
580
    {
581
        if(!$this->isDocumentPoolEnabled) {
582
            return $this->getDocumentDirectly($id, $callable);
583
        }
584
585
        if(!$callable && $this->isDocumentInDocumentPool($id)) {
586
            return $this->getDocumentFromDocumentPool($id);
587
        }
588
589
        $document = $this->getDocumentDirectly($id, $callable);
590
591
        // if callable configure cursor to return document as array,
592
        // than it can't be stored to document pool
593
        if($document instanceof Document) {
594
            $this->addDocumentToDocumentPool($document);
595
        }
596
597
        return $document;
598
    }
599
600
    /**
601
     * Get Document instance by it's reference
602
     *
603
     * @param array $ref reference to document
604
     * @param bool  $useDocumentPool try to get document from pool or fetch document from database
605
     *
606
     * @return Document|null
607
     */
608 View Code Duplication
    public function getDocumentByReference(array $ref, $useDocumentPool = true)
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...
609
    {
610
        $documentArray = $this->getMongoCollection()->getDBRef($ref);
611
        if (null === $documentArray) {
612
            return null;
613
        }
614
615
        return $this->hydrate($documentArray, $useDocumentPool);
616
    }
617
618
    /**
619
     * Get document by id directly omitting cache
620
     * Method may return document as array if cursor configured through Cursor::asArray()
621
     * 
622
     * @param string|\MongoId $id
623
     * @param callable $callable cursor callable used to configure cursor
624
     * @return \Sokil\Mongo\Document|array|null
625
     */
626
    public function getDocumentDirectly($id, $callable = null)
627
    {
628
        $cursor = $this->find();
629
630
        if(is_callable($callable)) {
631
            call_user_func($callable, $cursor);
632
        }
633
634
        return $cursor
0 ignored issues
show
Bug introduced by
The method byId does only exist in Sokil\Mongo\Cursor, but not in Sokil\Mongo\Expression.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
635
            ->byId($id)
636
            ->skipDocumentPool()
637
            ->findOne();
638
    }
639
640
    /**
641
     * Check if document belongs to collection
642
     *
643
     * @param \Sokil\Mongo\Document $document
644
     * @return type
645
     */
646
    public function hasDocument(Document $document)
647
    {
648
        // check connection
649
        if($document->getCollection()->getDatabase()->getClient()->getDsn() !== $this->getDatabase()->getClient()->getDsn()) {
650
            return false;
651
        }
652
653
        // check database
654
        if ($document->getCollection()->getDatabase()->getName() !== $this->getDatabase()->getName()) {
655
            return false;
656
        }
657
658
        // check collection
659
        return $document->getCollection()->getName() == $this->getName();
660
    }
661
662
    /**
663
     * Get documents by list of id
664
     *
665
     * @param array $idList list of ids
666
     * @param callable $callable cursor callable used to configure cursor
667
     * @return array|null
668
     */
669
    public function getDocuments(array $idList, $callable = null)
670
    {
671
        $idListToFindDirectly = $idList;
672
673
        // try to egt document from pool if enabled
674
        $documentsInDocumentPool = array();
675
        if ($this->isDocumentPoolEnabled && !$callable) {
676
            $documentsInDocumentPool = $this->getDocumentsFromDocumentPool($idList);
677
            if (count($documentsInDocumentPool) === count($idList)) {
678
                return $documentsInDocumentPool;
679
            }
680
681
            // skip ids already found in pool
682
            $idListToFindDirectly = array_diff_key(
683
                array_map('strval', $idList),
684
                array_keys($documentsInDocumentPool)
685
            );
686
        }
687
688
        // get documents directly
689
        $cursor = $this->find();
690
691
        if (is_callable($callable)) {
692
            call_user_func($callable, $cursor);
693
        }
694
695
        $documentsGettingDirectly = $cursor->byIdList($idListToFindDirectly)->findAll();
0 ignored issues
show
Bug introduced by
The method byIdList does only exist in Sokil\Mongo\Cursor, but not in Sokil\Mongo\Expression.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
696
        if (!$documentsGettingDirectly) {
697
            return $documentsInDocumentPool ? $documentsInDocumentPool : array();
698
        }
699
700
        if ($this->isDocumentPoolEnabled) {
701
            $this->addDocumentsToDocumentPool($documentsGettingDirectly);
702
        }
703
704
        return $documentsGettingDirectly + $documentsInDocumentPool;
705
    }
706
707
    /**
708
     * Creates batch insert operation handler
709
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
710
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
711
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
712
     *   one item at a time) or can rearrange it. Defaults to TRUE
713
     * @return BatchInsert
714
     */
715
    public function createBatchInsert($writeConcern = null, $timeout = null, $ordered = null)
716
    {
717
        return new BatchInsert(
718
            $this,
719
            $writeConcern,
720
            $timeout,
721
            $ordered
722
        );
723
    }
724
725
    /**
726
     * Creates batch update operation handler
727
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
728
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
729
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
730
     *   one item at a time) or can rearrange it. Defaults to TRUE
731
     * @return BatchUpdate
732
     */
733
    public function createBatchUpdate($writeConcern = null, $timeout = null, $ordered = null)
734
    {
735
        return new BatchUpdate(
736
            $this,
737
            $writeConcern,
738
            $timeout,
739
            $ordered
740
        );
741
    }
742
743
    /**
744
     * Creates batch delete operation handler
745
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
746
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
747
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
748
     *   one item at a time) or can rearrange it. Defaults to TRUE
749
     * @return BatchDelete
750
     */
751
    public function createBatchDelete($writeConcern = null, $timeout = null, $ordered = null)
752
    {
753
        return new BatchDelete(
754
            $this,
755
            $writeConcern,
756
            $timeout,
757
            $ordered
758
        );
759
    }
760
761
    /**
762
     * @deprecated since 1.13. Use Document::delete()
763
     * @param \Sokil\Mongo\Document $document
764
     * @return \Sokil\Mongo\Collection
765
     */
766
    public function deleteDocument(Document $document)
767
    {
768
        $document->delete();
769
        return $this;
770
    }
771
772
    /**
773
     * Delete documents by expression
774
     * 
775
     * @param callable|array|\Sokil\Mongo\Expression $expression
776
     * @return \Sokil\Mongo\Collection
777
     * @throws Exception
778
     */
779
    public function batchDelete($expression = array())
780
    {
781
        // remove
782
        $result = $this->getMongoCollection()->remove(
783
            Expression::convertToArray($expression)
784
        );
785
786
        // check result
787
        if(true !== $result && $result['ok'] != 1) {
788
            throw new Exception('Error removing documents from collection: ' . $result['err']);
789
        }
790
791
        return $this;
792
    }
793
794
    /**
795
     * @deprecated since 1.13. Use Collection::batchDelete();
796
     */
797
    public function deleteDocuments($expression = array())
798
    {
799
        return $this->batchDelete($expression);
800
    }
801
802
    /**
803
     * Insert multiple documents defined as arrays
804
     *
805
     * Prior to version 1.5.0 of the driver it was possible to use MongoCollection::batchInsert(),
806
     * however, as of 1.5.0 that method is now discouraged.
807
     *
808
     * You can use Collection::createBatchInsert()
809
     *
810
     * @param array $rows list of documents to insert, defined as arrays
811
     * @return \Sokil\Mongo\Collection
812
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
813
     * @throws \Sokil\Mongo\Exception
814
     */
815
    public function batchInsert($rows, $validate = true)
816
    {
817
        if($validate) {
818
            $document = $this->createDocument();
819
            foreach($rows as $row) {
820
                $document->merge($row);
821
822
                if(!$document->isValid()) {
823
                    throw new InvalidDocumentException('Document is invalid on batch insert');
824
                }
825
826
                $document->reset();
827
            }
828
        }
829
830
        $result = $this->getMongoCollection()->batchInsert($rows);
831
832
        // If the w parameter is set to acknowledge the write,
833
        // returns an associative array with the status of the inserts ("ok")
834
        // and any error that may have occurred ("err").
835 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...
836
            if($result['ok'] != 1) {
837
                throw new Exception('Batch insert error: ' . $result['err']);
838
            }
839
840
            return $this;
841
        }
842
843
        // Otherwise, returns TRUE if the batch insert was successfully sent,
844
        // FALSE otherwise.
845
        if(!$result) {
846
            throw new Exception('Batch insert error');
847
        }
848
849
        return $this;
850
    }
851
852
    /**
853
     * @deprecated since 1.13 Use Collection::batchInsert()
854
     */
855
    public function insertMultiple($rows, $validate = true)
856
    {
857
        return $this->batchInsert($rows, $validate);
858
    }
859
860
    /**
861
     * Direct insert of array to MongoDB without creating document object and validation
862
     *
863
     * @param array $document
864
     * @return \Sokil\Mongo\Collection
865
     * @throws Exception
866
     */
867
    public function insert(array $document)
868
    {
869
        $result = $this->getMongoCollection()->insert($document);
870
871
        // if write concern acknowledged
872 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...
873
            if($result['ok'] != 1) {
874
                throw new Exception('Insert error: ' . $result['err'] . ': ' . $result['errmsg']);
875
            }
876
877
            return $this;
878
        }
879
880
        // if write concern unacknowledged
881
        if(!$result) {
882
            throw new Exception('Insert error');
883
        }
884
885
        return $this;
886
    }
887
888
    /**
889
     * Update multiple documents
890
     *
891
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
892
     *  which documents will change.
893
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators to update
894
     * @param array $options update options, see http://php.net/manual/ru/mongocollection.update.php
895
     * @return \Sokil\Mongo\Collection
896
     * @throws \Sokil\Mongo\Exception
897
     */
898
    public function update($expression, $updateData, array $options = array())
899
    {
900
        // execute update operator
901
        $result = $this->getMongoCollection()->update(
902
            Expression::convertToArray($expression),
903
            Operator::convertToArray($updateData),
904
            $options
905
        );
906
907
        // if write concern acknowledged
908 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...
909
            if($result['ok'] != 1) {
910
                throw new Exception(sprintf('Update error: %s: %s', $result['err'], $result['errmsg']));
911
            }
912
            return $this;
913
        }
914
915
        // if write concern unacknowledged
916
        if(!$result) {
917
            throw new Exception('Update error');
918
        }
919
920
        return $this;
921
    }
922
923
    /**
924
     * Update multiple documents
925
     *
926
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
927
     *  which documents will change.
928
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
929
     *  to update
930
     * @return \Sokil\Mongo\Collection
931
     * @throws \Sokil\Mongo\Exception
932
     */
933
    public function batchUpdate($expression, $updateData)
934
    {
935
        return $this->update($expression, $updateData, array(
936
            'multiple'  => true,
937
        ));
938
    }
939
940
    /**
941
     * @deprecated since 1.13 Use Collection::batchUpdate()
942
     */
943
    public function updateMultiple($expression, $updateData)
944
    {
945
        return $this->batchUpdate($expression, $updateData);
946
    }
947
948
    /**
949
     * Update all documents
950
     *
951
     * @deprecated since 1.13. Use Collection::batchUpdate([])
952
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
953
     * @return \Sokil\Mongo\Collection
954
     * @throws \Sokil\Mongo\Exception
955
     */
956
    public function updateAll($updateData)
957
    {
958
        return $this->update(array(), $updateData, array(
959
            'multiple'  => true,
960
        ));
961
    }
962
963
    /**
964
     * Start aggregation
965
     *
966
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
967
     * @return \Sokil\Mongo\Pipeline
968
     */
969
    public function createAggregator()
970
    {
971
        return new Pipeline($this);
972
    }
973
974
    /**
975
     * Aggregate using pipeline
976
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
977
     *
978
     * @param callable|array|Pipeline $pipeline list of pipeline stages
979
     * @param array aggregate options
980
     * @param bool $asCursor return result as cursor
981
     *
982
     * @throws \Sokil\Mongo\Exception
983
     * @return array result of aggregation
984
     */
985
    public function aggregate(
986
        $pipeline,
987
        array $options = array(),
988
        $asCursor = false
989
    ) {
990
        // configure through callable
991
        if (is_callable($pipeline)) {
992
            $pipelineConfiguratorCallable = $pipeline;
993
            $pipeline = $this->createAggregator();
994
            call_user_func($pipelineConfiguratorCallable, $pipeline);
995
        }
996
997
        // get aggregation array
998
        if ($pipeline instanceof Pipeline) {
999
            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...
1000
                $options = array_merge($pipeline->getOptions(), $options);
1001
            } else {
1002
                $options = $pipeline->getOptions();
1003
            }
1004
            $pipeline = $pipeline->toArray();
1005
        } else if (!is_array($pipeline)) {
1006
            throw new Exception('Wrong pipeline specified');
1007
        }
1008
1009
        // log
1010
        $client = $this->database->getClient();
1011
        if ($client->isDebugEnabled()) {
1012
            // record pipeline
1013
            if ($client->hasLogger()) {
1014
                $client->getLogger()->debug(
1015
                    get_called_class() . ':<br><b>Pipeline</b>:<br>' .
1016
                    json_encode($pipeline)
1017
                );
1018
            }
1019
1020
            // Check options only in debug mode. In production common exception will raised
1021
            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...
1022
                // get db version
1023
                $dbVersion = $client->getDbVersion();
1024
1025
                // check options for db < 2.6
1026
                if (version_compare($dbVersion, '2.6.0', '<')) {
1027
                    if (!empty($options['explain'])) {
1028
                        throw new FeatureNotSupportedException('Explain of aggregation implemented only from 2.6.0');
1029
                    }
1030
1031
                    if (!empty($options['allowDiskUse'])) {
1032
                        throw new FeatureNotSupportedException('Option allowDiskUse of aggregation implemented only from 2.6.0');
1033
                    }
1034
1035
                    if (!empty($options['cursor'])) {
1036
                        throw new FeatureNotSupportedException('Option cursor of aggregation implemented only from 2.6.0');
1037
                    }
1038
                }
1039
1040
                // check options for db < 3.2
1041
                if (version_compare($dbVersion, '3.2.0', '<')) {
1042
                    if (!empty($options['bypassDocumentValidation'])) {
1043
                        throw new FeatureNotSupportedException('Option bypassDocumentValidation of aggregation implemented only from 3.2.0');
1044
                    }
1045
1046
                    if (!empty($options['readConcern'])) {
1047
                        throw new FeatureNotSupportedException('Option readConcern of aggregation implemented only from 3.2.0');
1048
                    }
1049
                }
1050
            }
1051
        }
1052
1053
        // return result as cursor
1054
        if ($asCursor) {
1055
            if (version_compare(\MongoClient::VERSION, '1.5.0', '<')) {
1056
                throw new FeatureNotSupportedException('Aggregate cursor supported from driver version 1.5');
1057
            }
1058
            $cursor = $this->getMongoCollection()->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...
1059
            return $cursor;
1060
        }
1061
1062
        // prepare command
1063
        $command = array(
1064
            'aggregate' => $this->getName(),
1065
            'pipeline'  => $pipeline,
1066
        );
1067
1068
        // add options
1069
        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...
1070
            $command += $options;
1071
        }
1072
1073
        // aggregate
1074
        $status = $this->database->executeCommand($command);
1075
1076
        if($status['ok'] != 1) {
1077
            throw new Exception('Aggregate error: ' . $status['errmsg']);
1078
        }
1079
1080
        // explain response
1081
        if (!empty($command['explain'])) {
1082
            return $status['stages'];
1083
        }
1084
1085
        // result response
1086
        return $status['result'];
1087
    }
1088
1089
    /**
1090
     * Explain aggregation
1091
     *
1092
     * @deprecated use pipeline option 'explain' in Collection::aggregate() or method Pipeline::explain()
1093
     * @param array|Pipeline $pipeline
1094
     * @return array result
1095
     * @throws Exception
1096
     */
1097
    public function explainAggregate($pipeline)
1098
    {
1099
        if (version_compare($this->getDatabase()->getClient()->getDbVersion(), '2.6.0', '<')) {
1100
            throw new Exception('Explain of aggregation implemented only from 2.6.0');
1101
        }
1102
1103
        if ($pipeline instanceof Pipeline) {
1104
            $pipeline = $pipeline->toArray();
1105
        } else if (!is_array($pipeline)) {
1106
            throw new Exception('Wrong pipeline specified');
1107
        }
1108
1109
        // aggregate
1110
        return $this->database->executeCommand(array(
1111
            'aggregate' => $this->getName(),
1112
            'pipeline'  => $pipeline,
1113
            'explain'   => true
1114
        ));
1115
    }
1116
1117
    /**
1118
     * Validates a collection. The method scans a collection’s data structures
1119
     * for correctness and returns a single document that describes the
1120
     * relationship between the logical collection and the physical
1121
     * representation of the data.
1122
     *
1123
     * @link http://docs.mongodb.org/manual/reference/method/db.collection.validate/
1124
     * @param bool $full Specify true to enable a full validation and to return
1125
     *      full statistics. MongoDB disables full validation by default because it
1126
     *      is a potentially resource-intensive operation.
1127
     * @return array
1128
     * @throws Exception
1129
     */
1130
    public function validate($full = false)
1131
    {
1132
        $response = $this->getMongoCollection()->validate($full);
1133
        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...
1134
            throw new Exception($response['errmsg']);
1135
        }
1136
1137
        return $response;
1138
    }
1139
1140
    /**
1141
     * Create index
1142
     *
1143
     * @deprecated since 1.19 Use self::createIndex()
1144
     * @param array $key
1145
     * @param array $options see @link http://php.net/manual/en/mongocollection.ensureindex.php
1146
     * @return \Sokil\Mongo\Collection
1147
     */
1148
    public function ensureIndex(array $key, array $options = array())
1149
    {
1150
        return $this->createIndex($key, $options);
1151
    }
1152
1153
    /**
1154
     * Create index
1155
     *
1156
     * @param array $key
1157
     * @param array $options see @link http://php.net/manual/en/mongocollection.ensureindex.php
1158
     * @return \Sokil\Mongo\Collection
1159
     */
1160
    public function createIndex(array $key, array $options = array())
1161
    {
1162
        $this->getMongoCollection()->createIndex($key, $options);
1163
        return $this;
1164
    }
1165
    
1166
    /**
1167
     * Delete index
1168
     *
1169
     * @param array $key
1170
     * @return \Sokil\Mongo\Collection
1171
     */
1172
    public function deleteIndex(array $key)
1173
    {
1174
        $this->getMongoCollection()->deleteIndex($key);
1175
        return $this;
1176
    }
1177
1178
    /**
1179
     * Create unique index
1180
     *
1181
     * @param array $key
1182
     * @param boolean $dropDups
1183
     * @return \Sokil\Mongo\Collection
1184
     */
1185
    public function ensureUniqueIndex(array $key, $dropDups = false)
1186
    {
1187
        $this->getMongoCollection()->createIndex($key, array(
1188
            'unique'    => true,
1189
            'dropDups'  => (bool) $dropDups,
1190
        ));
1191
1192
        return $this;
1193
    }
1194
1195
    /**
1196
     * Create sparse index.
1197
     *
1198
     * Sparse indexes only contain entries for documents that have the indexed
1199
     * field, even if the index field contains a null value. The index skips
1200
     * over any document that is missing the indexed field.
1201
     *
1202
     * @link http://docs.mongodb.org/manual/core/index-sparse/
1203
     *
1204
     * @param string|array $key An array specifying the index's fields as its
1205
     *  keys. For each field, the value is either the index direction or index
1206
     *  type. If specifying direction, specify 1 for ascending or -1
1207
     *  for descending.
1208
     *
1209
     * @return \Sokil\Mongo\Collection
1210
     */
1211
    public function ensureSparseIndex(array $key)
1212
    {
1213
        $this->getMongoCollection()->createIndex($key, array(
1214
            'sparse'    => true,
1215
        ));
1216
1217
        return $this;
1218
    }
1219
1220
    /**
1221
     * Create TTL index
1222
     *
1223
     * @link http://docs.mongodb.org/manual/tutorial/expire-data/
1224
     *
1225
     * If seconds not specified then document expired at specified time, as
1226
     * described at @link http://docs.mongodb.org/manual/tutorial/expire-data/#expire-documents-at-a-certain-clock-time
1227
     *
1228
     * @param string|array $key key must be date to use TTL
1229
     * @param int $seconds
1230
     * @return \Sokil\Mongo\Collection
1231
     */
1232
    public function ensureTTLIndex(array $key, $seconds = 0)
1233
    {
1234
        $this->getMongoCollection()->createIndex($key, array(
1235
            'expireAfterSeconds' => $seconds,
1236
        ));
1237
1238
        return $this;
1239
    }
1240
1241
    /**
1242
     * Create geo index 2dsphere
1243
     *
1244
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2dsphere-index/
1245
     *
1246
     * @param string $field
1247
     * @return \Sokil\Mongo\Collection
1248
     */
1249 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...
1250
    {
1251
        if (is_array($field)) {
1252
            $keys = array_fill_keys($field, '2dsphere');
1253
        } else {
1254
            $keys = array(
1255
                $field => '2dsphere',
1256
            );
1257
        }
1258
1259
        $this->getMongoCollection()->createIndex($keys);
1260
1261
        return $this;
1262
    }
1263
1264
    /**
1265
     * Create geo index 2dsphere
1266
     *
1267
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2d-index/
1268
     *
1269
     * @param string $field
1270
     * @return \Sokil\Mongo\Collection
1271
     */
1272 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...
1273
    {
1274
        if (is_array($field)) {
1275
            $keys = array_fill_keys($field, '2d');
1276
        } else {
1277
            $keys = array(
1278
                $field => '2d',
1279
            );
1280
        }
1281
1282
        $this->getMongoCollection()->createIndex($keys);
1283
1284
        return $this;
1285
    }
1286
1287
    /**
1288
     * Create fulltext index
1289
     *
1290
     * @link https://docs.mongodb.org/manual/core/index-text/
1291
     * @link https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/
1292
     *
1293
     * If a collection contains documents or embedded documents that are in different languages,
1294
     * include a field named language in the documents or embedded documents and specify as its value the language
1295
     * for that document or embedded document.
1296
     *
1297
     * The specified language in the document overrides the default language for the text index.
1298
     * The specified language in an embedded document override the language specified in an enclosing document or
1299
     * the default language for the index.
1300
     *
1301
     * @param array|string $field definition of fields where full text index ensured.
1302
     *  May be string to ensure index on one field, array of fields to create full text index on few fields, and
1303
     *   widdcard '$**'  to create index on all fields of collection. Default value is '$**'
1304
     * @param $weights For a text index, the weight of an indexed field denotes the significance of the field
1305
     *   relative to the other indexed fields in terms of the text search score.
1306
     * @param $defaultLanguage The default language associated with the indexed data determines the rules to parse
1307
     *  word roots (i.e. stemming) and ignore stop words. The default language for the indexed data is english.
1308
     * @param $languageOverride To use a field with a name other than language, include the
1309
     *   language_override option when creating the index.
1310
     *
1311
     * @return Collection
1312
     */
1313
    public function ensureFulltextIndex(
1314
        $field = '$**',
1315
        array $weights = null,
1316
        $defaultLanguage = Language::ENGLISH,
1317
        $languageOverride = null
1318
    ) {
1319
        // keys
1320
        if (is_array($field)) {
1321
            $keys = array_fill_keys($field, 'text');
1322
        } else {
1323
            $keys = array(
1324
                $field => 'text',
1325
            );
1326
        }
1327
1328
        // options
1329
        $options = array(
1330
            'default_language' => $defaultLanguage,
1331
        );
1332
1333
        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...
1334
            $options['weights'] = $weights;
1335
        }
1336
1337
        if ($languageOverride) {
1338
            $options['language_override'] = $languageOverride;
1339
        }
1340
1341
        // create index
1342
        $this->getMongoCollection()->createIndex($keys, $options);
1343
1344
        return $this;
1345
    }
1346
1347
1348
1349
    /**
1350
     * Create indexes based on self::$_index metadata
1351
     *
1352
     * @return \Sokil\Mongo\Collection
1353
     * @throws \Exception
1354
     */
1355
    public function initIndexes()
1356
    {
1357
        // read index definition from collection options
1358
        // if not specified - use defined in property
1359
        $indexDefinition = $this->definition->getOption('index');
1360
1361
        // ensure indexes
1362
        foreach($indexDefinition as $options) {
1363
1364
            if(empty($options['keys'])) {
1365
                throw new Exception('Keys not specified');
1366
            }
1367
1368
            $keys = $options['keys'];
1369
            unset($options['keys']);
1370
1371
            if (is_string($keys)) {
1372
                $keys = array($keys => 1);
1373
            }
1374
1375
            $this->getMongoCollection()->createIndex($keys, $options);
1376
        }
1377
1378
        return $this;
1379
    }
1380
1381
    /**
1382
     * Get index info
1383
     * @return array
1384
     */
1385
    public function getIndexes()
1386
    {
1387
        return $this->getMongoCollection()->getIndexInfo();
1388
    }
1389
1390
    public function readPrimaryOnly()
1391
    {
1392
        $this->getMongoCollection()->setReadPreference(\MongoClient::RP_PRIMARY);
1393
        return $this;
1394
    }
1395
1396
    public function readPrimaryPreferred(array $tags = null)
1397
    {
1398
        $this->getMongoCollection()->setReadPreference(\MongoClient::RP_PRIMARY_PREFERRED, $tags);
1399
        return $this;
1400
    }
1401
1402
    public function readSecondaryOnly(array $tags = null)
1403
    {
1404
        $this->getMongoCollection()->setReadPreference(\MongoClient::RP_SECONDARY, $tags);
1405
        return $this;
1406
    }
1407
1408
    public function readSecondaryPreferred(array $tags = null)
1409
    {
1410
        $this->getMongoCollection()->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, $tags);
1411
        return $this;
1412
    }
1413
1414
    public function readNearest(array $tags = null)
1415
    {
1416
        $this->getMongoCollection()->setReadPreference(\MongoClient::RP_NEAREST, $tags);
1417
        return $this;
1418
    }
1419
1420
    public function getReadPreference()
1421
    {
1422
        return $this->getMongoCollection()->getReadPreference();
1423
    }
1424
1425
    /**
1426
     * Define write concern for all requests to current collection
1427
     *
1428
     * @param string|integer $w write concern
1429
     * @param int $timeout timeout in milliseconds
1430
     * @throws \Sokil\Mongo\Exception
1431
     * @return \Sokil\Mongo\Collection
1432
     */
1433
    public function setWriteConcern($w, $timeout = 10000)
1434
    {
1435
        if(!$this->getMongoCollection()->setWriteConcern($w, (int) $timeout)) {
1436
            throw new Exception('Error setting write concern');
1437
        }
1438
1439
        return $this;
1440
    }
1441
1442
    /**
1443
     * Define unacknowledged write concern for all requests to current collection
1444
     *
1445
     * @param int $timeout timeout in milliseconds
1446
     * @throws \Sokil\Mongo\Exception
1447
     * @return \Sokil\Mongo\Collection
1448
     */
1449
    public function setUnacknowledgedWriteConcern($timeout = 10000)
1450
    {
1451
        $this->setWriteConcern(0, (int) $timeout);
1452
        return $this;
1453
    }
1454
1455
    /**
1456
     * Define majority write concern for all requests to current collection
1457
     *
1458
     * @param int $timeout timeout in milliseconds
1459
     * @throws \Sokil\Mongo\Exception
1460
     * @return \Sokil\Mongo\Collection
1461
     */
1462
    public function setMajorityWriteConcern($timeout = 10000)
1463
    {
1464
        $this->setWriteConcern('majority', (int) $timeout);
1465
        return $this;
1466
    }
1467
1468
    /**
1469
     * Get currently active write concern on all requests to collection
1470
     *
1471
     * @return int|string write concern
1472
     */
1473
    public function getWriteConcern()
1474
    {
1475
        return $this->getMongoCollection()->getWriteConcern();
1476
    }
1477
1478
    /**
1479
     * Get collection stat
1480
     *
1481
     * @return array collection stat
1482
     */
1483
    public function stats()
1484
    {
1485
        return $this->getDatabase()->executeCommand(array(
1486
            'collstats' => $this->getName(),
1487
        ));
1488
    }
1489
}
1490