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 ( 6052ae...5ff50a )
by De
04:29
created

Collection::addDocumentToDocumentPool()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

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

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

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

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

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
120
        }
121
    }
122
123
    protected function initCollection($collection)
124
    {
125
        // init mongo collection
126
        if($collection instanceof \MongoCollection) {
127
            $this->_mongoCollection = $collection;
128
        } else {
129
            $this->_mongoCollection = $this->_database->getMongoDB()->selectCollection($collection);
130
        }
131
    }
132
133
    /**
134
     * Start versioning documents on modify
135
     *
136
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
137
     * @return \Sokil\Mongo\Collection
138
     */
139
    public function enableVersioning()
140
    {
141
        $this->definition->setOption('versioning', true);
142
        return $this;
143
    }
144
145
    /**
146
     * Check if versioning enabled
147
     *
148
     * @deprecated since 1.13 Use 'versioning' declaration in mapping
149
     * @return bool
150
     */
151
    public function isVersioningEnabled()
152
    {
153
        return $this->definition->getOption('versioning');
154
    }
155
156
    /**
157
     * Get option
158
     *
159
     * @param string|int $name
160
     * @return mixed
161
     */
162
    public function getOption($name)
163
    {
164
        return $this->definition->getOption($name);
165
    }
166
167
    public function getOptions()
168
    {
169
        return $this->definition->getOptions();
170
    }
171
172
    public function __get($name)
173
    {
174
        return $this->getDocument($name);
175
    }
176
177
    /**
178
     * Get name of collection
179
     * 
180
     * @return string name of collection
181
     */
182
    public function getName()
183
    {
184
        return $this->_mongoCollection->getName();
185
    }
186
187
    /**
188
     * Get native collection instance of mongo driver
189
     * 
190
     * @return \MongoCollection
191
     */
192
    public function getMongoCollection()
193
    {
194
        return $this->_mongoCollection;
195
    }
196
197
    /**
198
     *
199
     * @return \Sokil\Mongo\Database
200
     */
201
    public function getDatabase()
202
    {
203
        return $this->_database;
204
    }
205
206
    /**
207
     * Delete collection
208
     * 
209
     * @return \Sokil\Mongo\Collection
210
     * @throws \Sokil\Mongo\Exception
211
     */
212
    public function delete() {
213
        $status = $this->_mongoCollection->drop();
214
        if($status['ok'] != 1) {
215
            // check if collection exists
216
            if('ns not found' !== $status['errmsg']) {
217
                // collection exist
218
                throw new Exception('Error deleting collection ' . $this->getName() . ': ' . $status['errmsg']);
219
            }
220
        }
221
222
        return $this;
223
    }
224
225
    /**
226
     * Override to define class name of document by document data
227
     *
228
     * @param array $documentData
229
     * @return string Document class data
230
     */
231
    public function getDocumentClassName(array $documentData = null)
232
    {
233
        $documentClass = $this->definition->getOption('documentClass');
234
235
        if(is_callable($documentClass)) {
236
            return call_user_func($documentClass, $documentData);
237
        }
238
239
        if(class_exists($documentClass)) {
240
            return $documentClass;
241
        }
242
243
        throw new Exception('Property "documentClass" must be callable or valid name of class');
244
    }
245
246
    /**
247
     * Factory method to get not stored Document instance from array
248
     * @param array $data
249
     * @return Document
250
     */
251
    public function createDocument(array $data = null)
252
    {
253
        $className = $this->getDocumentClassName($data);
254
255
        /* @var $document \Sokil\Mongo\Document */
256
        $document = new $className(
257
            $this,
258
            $data,
259
            array('stored' => false) + $this->definition->getOptions()
260
        );
261
262
        // store document to identity map
263
        if($this->isDocumentPoolEnabled()) {
264
            $collection = $this;
265
            $document->onAfterInsert(function(\Sokil\Mongo\Event $event) use($collection) {
266
                $collection->addDocumentToDocumentPool($event->getTarget());
267
            });
268
        }
269
270
        return $document;
271
    }
272
273
    /**
274
     * Factory method to get document object from array of stored document
275
     *
276
     * @param array $data
277
     * @return \Sokil\Mongo\Document
278
     */
279
    public function hydrate($data, $useDocumentPool = true)
280
    {
281
        if (!is_array($data) || !isset($data['_id'])) {
282
            throw new Exception('Document must be stored and has _id key');
283
        }
284
285
        // if document already in pool - return it
286
        if($useDocumentPool && $this->isDocumentPoolEnabled() && $this->isDocumentInDocumentPool($data['_id'])) {
287
            return $this
288
                ->getDocumentFromDocumentPool($data['_id'])
289
                ->mergeUnmodified($data);
290
        }
291
292
        // init document instance
293
        $className = $this->getDocumentClassName($data);
294
        $document = new $className(
295
            $this,
296
            $data,
297
            array('stored' => true) + $this->definition->getOptions()
298
        );
299
300
        // store document in cache
301
        if($useDocumentPool && $this->isDocumentPoolEnabled()) {
302
            $this->addDocumentToDocumentPool($document);
303
        }
304
305
        return $document;
306
    }
307
308
    /**
309
     * Total count of documents in collection
310
     * 
311
     * @return int 
312
     */
313
    public function count()
314
    {
315
        return $this->find()->count();
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...
316
    }
317
318
    /**
319
     * Retrieve a list of distinct values for the given key across a collection.
320
     *
321
     * @param string $selector field selector
322
     * @param array|callable|\Sokil\Mongo\Expression $expression expression to search documents
323
     * @return array distinct values
324
     */
325
    public function getDistinct($selector, $expression = null)
326
    {
327
        if($expression) {
328
            return $this->_mongoCollection->distinct(
329
                $selector,
330
                Expression::convertToArray($expression)
331
            );
332
        }
333
334
        return $this->_mongoCollection->distinct($selector);
335
    }
336
337
    /**
338
     * Create new Expression instance to use in query builder or update operations
339
     * 
340
     * @return \Sokil\Mongo\Expression
341
     */
342
    public function expression()
343
    {
344
        $className = $this->definition->getExpressionClass();
345
        return new $className;
346
    }
347
348
    /**
349
     * Create Operator instance to use in update operations
350
     * 
351
     * @return \Sokil\Mongo\Operator
352
     */
353
    public function operator()
354
    {
355
        return new Operator();
356
    }
357
358
    /**
359
     * Create document query builder
360
     *
361
     * @param $callable callable|null Function to configure query builder&
362
     * @return \Sokil\Mongo\Cursor|\Sokil\Mongo\Expression
363
     */
364
    public function find($callable = null)
365
    {
366
        /** @var \Sokil\Mongo\Cursor $cursor */
367
        $cursor = new Cursor($this, array(
368
            'expressionClass'   => $this->definition->getExpressionClass(),
369
            'batchSize'         => $this->definition->getOption('batchSize'),
370
            'clientTimeout'     => $this->definition->getOption('cursorClientTimeout'),
371
            'serverTimeout'     => $this->definition->getOption('cursorServerTimeout'),
372
        ));
373
374
        if(is_callable($callable)) {
375
            $callable($cursor->getExpression());
376
        }
377
378
        return $cursor;
379
    }
380
381
    /**
382
     * Create document query builder
383
     *
384
     * @return \Sokil\Mongo\Cursor
385
     */
386
    public function findAsArray($callable = null)
387
    {
388
        return $this
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...
389
            ->find($callable)
390
            ->asArray();
391
    }
392
393
    /**
394
     * Stop storing found documents to pool
395
     *
396
     * @return \Sokil\Mongo\Collection
397
     */
398
    public function disableDocumentPool()
399
    {
400
        $this->isDocumentPoolEnabled = false;
401
        return $this;
402
    }
403
404
    /**
405
     * Start storing found documents to pool
406
     *
407
     * @return \Sokil\Mongo\Collection
408
     */
409
    public function enableDocumentPool()
410
    {
411
        $this->isDocumentPoolEnabled = true;
412
        return $this;
413
    }
414
415
    /**
416
     * Check if document pool enabled and requested documents store to it
417
     *
418
     * @return bool
419
     */
420
    public function isDocumentPoolEnabled()
421
    {
422
        return $this->isDocumentPoolEnabled;
423
    }
424
425
    public function clearDocumentPool()
426
    {
427
        $this->documentPool = array();
428
        return $this;
429
    }
430
431
    /**
432
     * Check if documents are in pool
433
     *
434
     * @return bool
435
     */
436
    public function isDocumentPoolEmpty()
437
    {
438
        return !$this->documentPool;
439
    }
440
441
    /**
442
     * Store document to pool
443
     *
444
     * @param array $document
445
     * @return \Sokil\Mongo\Collection
446
     */
447
    public function addDocumentToDocumentPool(Document $document)
448
    {
449
        $documentId = (string) $document->getId();
450
451
        if(!isset($this->documentPool[$documentId])) {
452
            $this->documentPool[$documentId] = $document;
453
        } else {
454
            // merging because document after
455
            // load and before getting in second place may be changed
456
            // and this changes must be preserved:
457
            //
458
            // 1. Document loads and modifies in current session
459
            // 2. Document loads modified in another session
460
            // 3. Document loads once again in current session. Changes from stage 2 merges as unmodified
461
462
            $this->documentPool[$documentId]->mergeUnmodified($document->toArray());
463
        }
464
465
        return $this;
466
    }
467
468
    /**
469
     * Store documents to identity map
470
     *
471
     * @param array $documents list of Document instances
472
     * @return \Sokil\Mongo\Collection
473
     */
474
    public function addDocumentsToDocumentPool(array $documents)
475
    {
476
        foreach($documents as $document) {
477
            $this->addDocumentToDocumentPool($document);
478
        }
479
480
        return $this;
481
    }
482
483
    /**
484
     * Remove document instance from identity map
485
     *
486
     * @param \Sokil\Mongo\Document $document
487
     * @return \Sokil\Mongo\Collection
488
     */
489
    public function removeDocumentFromDocumentPool(Document $document)
490
    {
491
        unset($this->documentPool[(string) $document]);
492
        return $this;
493
    }
494
495
    /**
496
     * Get document from identity map by it's id
497
     *
498
     * @param string|int|\MongoId $id
499
     * @return \Sokil\Mongo\Document
500
     */
501
    public function getDocumentFromDocumentPool($id)
502
    {
503
        return $this->documentPool[(string) $id];
504
    }
505
506
    /**
507
     * Get documents from pool if they stored
508
     *
509
     * @param array $ids
510
     */
511
    public function getDocumentsFromDocumentPool(array $ids = null)
512
    {
513
        if(!$ids) {
514
            return $this->documentPool;
515
        }
516
517
        return array_intersect_key(
518
            $this->documentPool,
519
            array_flip(array_map('strval', $ids))
520
        );
521
    }
522
523
    /**
524
     * Get number of documents in document pool
525
     *
526
     * @return int
527
     */
528
    public function documentPoolCount()
529
    {
530
        return count($this->documentPool);
531
    }
532
533
    /**
534
     * Check if document exists in identity map
535
     *
536
     * @param \Sokil\Mongo\Document|\MongoId|int|string $document Document instance or it's id
537
     * @return boolean
538
     */
539
    public function isDocumentInDocumentPool($document)
540
    {
541
        if($document instanceof Document) {
542
            $document = $document->getId();
543
        }
544
545
        return isset($this->documentPool[(string) $document]);
546
    }
547
548
    /**
549
     * Get document by id
550
     * If callable specified, document always loaded directly omitting document pool.
551
     * Method may return document as array if cursor configured through Cursor::asArray()
552
     *
553
     * @param string|\MongoId $id
554
     * @param callable $callable cursor callable used to configure cursor
555
     * @return \Sokil\Mongo\Document|array|null
556
     */
557
    public function getDocument($id, $callable = null)
558
    {
559
        if(!$this->isDocumentPoolEnabled) {
560
            return $this->getDocumentDirectly($id, $callable);
561
        }
562
563
        if(!$callable && $this->isDocumentInDocumentPool($id)) {
564
            return $this->getDocumentFromDocumentPool($id);
565
        }
566
567
        $document = $this->getDocumentDirectly($id, $callable);
568
569
        // if callable configure cursor to return document as array,
570
        // than it can't be stored to document pool
571
        if($document instanceof Document) {
572
            $this->addDocumentToDocumentPool($document);
573
        }
574
575
        return $document;
576
    }
577
578
    /**
579
     * Get Document instance by it's reference
580
     *
581
     * @param array $ref reference to document
582
     * @param bool  $useDocumentPool try to get document from pool or fetch document from database
583
     *
584
     * @return Document|null
585
     */
586 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...
587
    {
588
        $documentArray = $this->_mongoCollection->getDBRef($ref);
589
        if (null === $documentArray) {
590
            return null;
591
        }
592
593
        return $this->hydrate($documentArray, $useDocumentPool);
594
    }
595
596
    /**
597
     * Get document by id directly omitting cache
598
     * Method may return document as array if cursor configured through Cursor::asArray()
599
     * 
600
     * @param string|\MongoId $id
601
     * @param callable $callable cursor callable used to configure cursor
602
     * @return \Sokil\Mongo\Document|array|null
603
     */
604
    public function getDocumentDirectly($id, $callable = null)
605
    {
606
        $cursor = $this->find();
607
608
        if(is_callable($callable)) {
609
            call_user_func($callable, $cursor);
610
        }
611
612
        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...
613
            ->byId($id)
614
            ->skipDocumentPool()
615
            ->findOne();
616
    }
617
618
    /**
619
     * Check if document belongs to collection
620
     *
621
     * @param \Sokil\Mongo\Document $document
622
     * @return type
623
     */
624
    public function hasDocument(Document $document)
625
    {
626
        // check connection
627
        if($document->getCollection()->getDatabase()->getClient()->getDsn() !== $this->getDatabase()->getClient()->getDsn()) {
628
            return false;
629
        }
630
631
        // check database
632
        if ($document->getCollection()->getDatabase()->getName() !== $this->getDatabase()->getName()) {
633
            return false;
634
        }
635
636
        // check collection
637
        return $document->getCollection()->getName() == $this->getName();
638
    }
639
640
    /**
641
     * Get documents by list of id
642
     *
643
     * @param array $idList list of ids
644
     * @param callable $callable cursor callable used to configure cursor
645
     * @return array|null
646
     */
647
    public function getDocuments(array $idList, $callable = null)
648
    {
649
        $idListToFindDirectly = $idList;
650
651
        // try to egt document from pool if enabled
652
        $documentsInDocumentPool = array();
653
        if ($this->isDocumentPoolEnabled && !$callable) {
654
            $documentsInDocumentPool = $this->getDocumentsFromDocumentPool($idList);
655
            if (count($documentsInDocumentPool) === count($idList)) {
656
                return $documentsInDocumentPool;
657
            }
658
659
            // skip ids already found in pool
660
            $idListToFindDirectly = array_diff_key(
661
                array_map('strval', $idList),
662
                array_keys($documentsInDocumentPool)
663
            );
664
        }
665
666
        // get documents directly
667
        $cursor = $this->find();
668
669
        if (is_callable($callable)) {
670
            call_user_func($callable, $cursor);
671
        }
672
673
        $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...
674
        if (!$documentsGettingDirectly) {
675
            return $documentsInDocumentPool ? $documentsInDocumentPool : array();
676
        }
677
678
        if ($this->isDocumentPoolEnabled) {
679
            $this->addDocumentsToDocumentPool($documentsGettingDirectly);
680
        }
681
682
        return $documentsGettingDirectly + $documentsInDocumentPool;
683
    }
684
685
    /**
686
     * Creates batch insert operation handler
687
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
688
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
689
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
690
     *   one item at a time) or can rearrange it. Defaults to TRUE
691
     * @return BatchInsert
692
     */
693
    public function createBatchInsert($writeConcern = null, $timeout = null, $ordered = null)
694
    {
695
        return new BatchInsert(
696
            $this,
697
            $writeConcern,
698
            $timeout,
699
            $ordered
700
        );
701
    }
702
703
    /**
704
     * Creates batch update operation handler
705
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
706
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
707
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
708
     *   one item at a time) or can rearrange it. Defaults to TRUE
709
     * @return BatchUpdate
710
     */
711
    public function createBatchUpdate($writeConcern = null, $timeout = null, $ordered = null)
712
    {
713
        return new BatchUpdate(
714
            $this,
715
            $writeConcern,
716
            $timeout,
717
            $ordered
718
        );
719
    }
720
721
    /**
722
     * Creates batch delete operation handler
723
     * @param int|string $writeConcern Write concern. Default is 1 (Acknowledged)
724
     * @param int $timeout Timeout for write concern. Default is 10000 milliseconds
725
     * @param bool $ordered Determins if MongoDB must apply this batch in order (sequentally,
726
     *   one item at a time) or can rearrange it. Defaults to TRUE
727
     * @return BatchDelete
728
     */
729
    public function createBatchDelete($writeConcern = null, $timeout = null, $ordered = null)
730
    {
731
        return new BatchDelete(
732
            $this,
733
            $writeConcern,
734
            $timeout,
735
            $ordered
736
        );
737
    }
738
739
    /**
740
     * @deprecated since 1.13. Use Document::delete()
741
     * @param \Sokil\Mongo\Document $document
742
     * @return \Sokil\Mongo\Collection
743
     */
744
    public function deleteDocument(Document $document)
745
    {
746
        $document->delete();
747
        return $this;
748
    }
749
750
    /**
751
     * Delete documents by expression
752
     * 
753
     * @param callable|array|\Sokil\Mongo\Expression $expression
754
     * @return \Sokil\Mongo\Collection
755
     * @throws Exception
756
     */
757
    public function batchDelete($expression = array())
758
    {
759
        // remove
760
        $result = $this->_mongoCollection->remove(
761
            Expression::convertToArray($expression)
762
        );
763
764
        // check result
765
        if(true !== $result && $result['ok'] != 1) {
766
            throw new Exception('Error removing documents from collection: ' . $result['err']);
767
        }
768
769
        return $this;
770
    }
771
772
    /**
773
     * @deprecated since 1.13. Use Collection::batchDelete();
774
     */
775
    public function deleteDocuments($expression = array())
776
    {
777
        return $this->batchDelete($expression);
778
    }
779
780
    /**
781
     * Insert multiple documents defined as arrays
782
     *
783
     * Prior to version 1.5.0 of the driver it was possible to use MongoCollection::batchInsert(),
784
     * however, as of 1.5.0 that method is now discouraged.
785
     *
786
     * You can use Collection::createBatchInsert()
787
     *
788
     * @param array $rows list of documents to insert, defined as arrays
789
     * @return \Sokil\Mongo\Collection
790
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
791
     * @throws \Sokil\Mongo\Exception
792
     */
793
    public function batchInsert($rows, $validate = true)
794
    {
795
        if($validate) {
796
            $document = $this->createDocument();
797
            foreach($rows as $row) {
798
                $document->merge($row);
799
800
                if(!$document->isValid()) {
801
                    throw new InvalidDocumentException('Document is invalid on batch insert');
802
                }
803
804
                $document->reset();
805
            }
806
        }
807
808
        $result = $this->_mongoCollection->batchInsert($rows);
809
810
        // If the w parameter is set to acknowledge the write,
811
        // returns an associative array with the status of the inserts ("ok")
812
        // and any error that may have occurred ("err").
813 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...
814
            if($result['ok'] != 1) {
815
                throw new Exception('Batch insert error: ' . $result['err']);
816
            }
817
818
            return $this;
819
        }
820
821
        // Otherwise, returns TRUE if the batch insert was successfully sent,
822
        // FALSE otherwise.
823
        if(!$result) {
824
            throw new Exception('Batch insert error');
825
        }
826
827
        return $this;
828
    }
829
830
    /**
831
     * @deprecated since 1.13 Use Collection::batchInsert()
832
     */
833
    public function insertMultiple($rows, $validate = true)
834
    {
835
        return $this->batchInsert($rows, $validate);
836
    }
837
838
    /**
839
     * Direct insert of array to MongoDB without creating document object and validation
840
     *
841
     * @param array $document
842
     * @return \Sokil\Mongo\Collection
843
     * @throws Exception
844
     */
845
    public function insert(array $document)
846
    {
847
        $result = $this->_mongoCollection->insert($document);
848
849
        // if write concern acknowledged
850 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...
851
            if($result['ok'] != 1) {
852
                throw new Exception('Insert error: ' . $result['err'] . ': ' . $result['errmsg']);
853
            }
854
855
            return $this;
856
        }
857
858
        // if write concern unacknowledged
859
        if(!$result) {
860
            throw new Exception('Insert error');
861
        }
862
863
        return $this;
864
    }
865
866
    /**
867
     * Update multiple documents
868
     *
869
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
870
     *  which documents will change.
871
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators to update
872
     * @param array $options update options, see http://php.net/manual/ru/mongocollection.update.php
873
     * @return \Sokil\Mongo\Collection
874
     * @throws \Sokil\Mongo\Exception
875
     */
876
    public function update($expression, $updateData, array $options = array())
877
    {
878
        // execute update operator
879
        $result = $this->_mongoCollection->update(
880
            Expression::convertToArray($expression),
881
            Operator::convertToArray($updateData),
882
            $options
883
        );
884
885
        // if write concern acknowledged
886 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...
887
            if($result['ok'] != 1) {
888
                throw new Exception(sprintf('Update error: %s: %s', $result['err'], $result['errmsg']));
889
            }
890
            return $this;
891
        }
892
893
        // if write concern unacknowledged
894
        if(!$result) {
895
            throw new Exception('Update error');
896
        }
897
898
        return $this;
899
    }
900
901
    /**
902
     * Update multiple documents
903
     *
904
     * @param \Sokil\Mongo\Expression|array|callable $expression expression to define
905
     *  which documents will change.
906
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
907
     *  to update
908
     * @return \Sokil\Mongo\Collection
909
     * @throws \Sokil\Mongo\Exception
910
     */
911
    public function batchUpdate($expression, $updateData)
912
    {
913
        return $this->update($expression, $updateData, array(
914
            'multiple'  => true,
915
        ));
916
    }
917
918
    /**
919
     * @deprecated since 1.13 Use Collection::batchUpdate()
920
     */
921
    public function updateMultiple($expression, $updateData)
922
    {
923
        return $this->batchUpdate($expression, $updateData);
924
    }
925
926
    /**
927
     * Update all documents
928
     *
929
     * @deprecated since 1.13. Use Collection::batchUpdate([])
930
     * @param \Sokil\Mongo\Operator|array|callable $updateData new data or operators
931
     * @return \Sokil\Mongo\Collection
932
     * @throws \Sokil\Mongo\Exception
933
     */
934
    public function updateAll($updateData)
935
    {
936
        return $this->update(array(), $updateData, array(
937
            'multiple'  => true,
938
        ));
939
    }
940
941
    /**
942
     * Start aggregation
943
     *
944
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
945
     * @return \Sokil\Mongo\Pipeline
946
     */
947
    public function createAggregator()
948
    {
949
        return new Pipeline($this);
950
    }
951
952
    /**
953
     * Aggregate using pipeline
954
     * @link http://docs.mongodb.org/manual/reference/operator/aggregation/
955
     *
956
     * @param callable|array|Pipeline $pipeline list of pipeline stages
957
     * @param array aggregate options
958
     * @param bool $asCursor return result as cursor
959
     *
960
     * @throws \Sokil\Mongo\Exception
961
     * @return array result of aggregation
962
     */
963
    public function aggregate(
964
        $pipeline,
965
        array $options = array(),
966
        $asCursor = false
967
    ) {
968
        // configure through callable
969
        if (is_callable($pipeline)) {
970
            $pipelineConfiguratorCallable = $pipeline;
971
            $pipeline = $this->createAggregator();
972
            call_user_func($pipelineConfiguratorCallable, $pipeline);
973
        }
974
975
        // get aggregation array
976
        if ($pipeline instanceof Pipeline) {
977
            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...
978
                $options = array_merge($pipeline->getOptions(), $options);
979
            } else {
980
                $options = $pipeline->getOptions();
981
            }
982
            $pipeline = $pipeline->toArray();
983
        } else if (!is_array($pipeline)) {
984
            throw new Exception('Wrong pipeline specified');
985
        }
986
987
        // log
988
        $client = $this->_database->getClient();
989
        if ($client->isDebugEnabled()) {
990
            // record pipeline
991
            if ($client->hasLogger()) {
992
                $client->getLogger()->debug(
993
                    get_called_class() . ':<br><b>Pipeline</b>:<br>' .
994
                    json_encode($pipeline)
995
                );
996
            }
997
998
            // Check options only in debug mode. In production common exception will raised
999
            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...
1000
                // get db version
1001
                $dbVersion = $client->getDbVersion();
1002
1003
                // check options for db < 2.6
1004
                if (version_compare($dbVersion, '2.6.0', '<')) {
1005
                    if (!empty($options['explain'])) {
1006
                        throw new FeatureNotSupportedException('Explain of aggregation implemented only from 2.6.0');
1007
                    }
1008
1009
                    if (!empty($options['allowDiskUse'])) {
1010
                        throw new FeatureNotSupportedException('Option allowDiskUse of aggregation implemented only from 2.6.0');
1011
                    }
1012
1013
                    if (!empty($options['cursor'])) {
1014
                        throw new FeatureNotSupportedException('Option cursor of aggregation implemented only from 2.6.0');
1015
                    }
1016
                }
1017
1018
                // check options for db < 3.2
1019
                if (version_compare($dbVersion, '3.2.0', '<')) {
1020
                    if (!empty($options['bypassDocumentValidation'])) {
1021
                        throw new FeatureNotSupportedException('Option bypassDocumentValidation of aggregation implemented only from 3.2.0');
1022
                    }
1023
1024
                    if (!empty($options['readConcern'])) {
1025
                        throw new FeatureNotSupportedException('Option readConcern of aggregation implemented only from 3.2.0');
1026
                    }
1027
                }
1028
            }
1029
        }
1030
1031
        // return result as cursor
1032
        if ($asCursor) {
1033
            if (version_compare(\MongoClient::VERSION, '1.5.0', '<')) {
1034
                throw new FeatureNotSupportedException('Aggregate cursor supported from driver version 1.5');
1035
            }
1036
            $cursor = $this->_mongoCollection->aggregateCursor($pipeline, $options);
0 ignored issues
show
Bug introduced by
The method aggregateCursor() does not exist on MongoCollection. Did you maybe mean aggregate()?

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

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

Loading history...
1037
            return $cursor;
1038
        }
1039
1040
        // prepare command
1041
        $command = array(
1042
            'aggregate' => $this->getName(),
1043
            'pipeline'  => $pipeline,
1044
        );
1045
1046
        // add options
1047
        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...
1048
            $command += $options;
1049
        }
1050
1051
        // aggregate
1052
        $status = $this->_database->executeCommand($command);
1053
1054
        if($status['ok'] != 1) {
1055
            throw new Exception('Aggregate error: ' . $status['errmsg']);
1056
        }
1057
1058
        // explain response
1059
        if (!empty($command['explain'])) {
1060
            return $status['stages'];
1061
        }
1062
1063
        // result response
1064
        return $status['result'];
1065
    }
1066
1067
    /**
1068
     * Explain aggregation
1069
     *
1070
     * @deprecated use pipeline option 'explain' in Collection::aggregate() or method Pipeline::explain()
1071
     * @param array|Pipeline $pipeline
1072
     * @return array result
1073
     * @throws Exception
1074
     */
1075
    public function explainAggregate($pipeline)
1076
    {
1077
        if (version_compare($this->getDatabase()->getClient()->getDbVersion(), '2.6.0', '<')) {
1078
            throw new Exception('Explain of aggregation implemented only from 2.6.0');
1079
        }
1080
1081
        if ($pipeline instanceof Pipeline) {
1082
            $pipeline = $pipeline->toArray();
1083
        } else if (!is_array($pipeline)) {
1084
            throw new Exception('Wrong pipeline specified');
1085
        }
1086
1087
        // aggregate
1088
        return $this->_database->executeCommand(array(
1089
            'aggregate' => $this->getName(),
1090
            'pipeline'  => $pipeline,
1091
            'explain'   => true
1092
        ));
1093
    }
1094
1095
    /**
1096
     * Validates a collection. The method scans a collection’s data structures
1097
     * for correctness and returns a single document that describes the
1098
     * relationship between the logical collection and the physical
1099
     * representation of the data.
1100
     *
1101
     * @link http://docs.mongodb.org/manual/reference/method/db.collection.validate/
1102
     * @param bool $full Specify true to enable a full validation and to return
1103
     *      full statistics. MongoDB disables full validation by default because it
1104
     *      is a potentially resource-intensive operation.
1105
     * @return array
1106
     * @throws Exception
1107
     */
1108
    public function validate($full = false)
1109
    {
1110
        $response = $this->_mongoCollection->validate($full);
1111
        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...
1112
            throw new Exception($response['errmsg']);
1113
        }
1114
1115
        return $response;
1116
    }
1117
1118
    /**
1119
     * Create index
1120
     *
1121
     * @param array $key
1122
     * @param array $options see @link http://php.net/manual/en/mongocollection.ensureindex.php
1123
     * @return \Sokil\Mongo\Collection
1124
     */
1125
    public function ensureIndex(array $key, array $options = array())
1126
    {
1127
        $this->_mongoCollection->ensureIndex($key, $options);
1128
        return $this;
1129
    }
1130
    
1131
    /**
1132
     * Delete index
1133
     *
1134
     * @param array $key
1135
     * @return \Sokil\Mongo\Collection
1136
     */
1137
    public function deleteIndex(array $key)
1138
    {
1139
        $this->_mongoCollection->deleteIndex($key);
1140
        return $this;
1141
    }
1142
1143
    /**
1144
     * Create unique index
1145
     *
1146
     * @param array $key
1147
     * @param boolean $dropDups
1148
     * @return \Sokil\Mongo\Collection
1149
     */
1150
    public function ensureUniqueIndex(array $key, $dropDups = false)
1151
    {
1152
        $this->_mongoCollection->ensureIndex($key, array(
1153
            'unique'    => true,
1154
            'dropDups'  => (bool) $dropDups,
1155
        ));
1156
1157
        return $this;
1158
    }
1159
1160
    /**
1161
     * Create sparse index.
1162
     *
1163
     * Sparse indexes only contain entries for documents that have the indexed
1164
     * field, even if the index field contains a null value. The index skips
1165
     * over any document that is missing the indexed field.
1166
     *
1167
     * @link http://docs.mongodb.org/manual/core/index-sparse/
1168
     *
1169
     * @param string|array $key An array specifying the index's fields as its
1170
     *  keys. For each field, the value is either the index direction or index
1171
     *  type. If specifying direction, specify 1 for ascending or -1
1172
     *  for descending.
1173
     *
1174
     * @return \Sokil\Mongo\Collection
1175
     */
1176
    public function ensureSparseIndex(array $key)
1177
    {
1178
        $this->_mongoCollection->ensureIndex($key, array(
1179
            'sparse'    => true,
1180
        ));
1181
1182
        return $this;
1183
    }
1184
1185
    /**
1186
     * Create TTL index
1187
     *
1188
     * @link http://docs.mongodb.org/manual/tutorial/expire-data/
1189
     *
1190
     * If seconds not specified then document expired at specified time, as
1191
     * described at @link http://docs.mongodb.org/manual/tutorial/expire-data/#expire-documents-at-a-certain-clock-time
1192
     *
1193
     * @param string|array $key key must be date to use TTL
1194
     * @param int $seconds
1195
     * @return \Sokil\Mongo\Collection
1196
     */
1197
    public function ensureTTLIndex(array $key, $seconds = 0)
1198
    {
1199
        $this->_mongoCollection->ensureIndex($key, array(
1200
            'expireAfterSeconds' => $seconds,
1201
        ));
1202
1203
        return $this;
1204
    }
1205
1206
    /**
1207
     * Create geo index 2dsphere
1208
     *
1209
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2dsphere-index/
1210
     *
1211
     * @param string $field
1212
     * @return \Sokil\Mongo\Collection
1213
     */
1214 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...
1215
    {
1216
        if (is_array($field)) {
1217
            $keys = array_fill_keys($field, '2dsphere');
1218
        } else {
1219
            $keys = array(
1220
                $field => '2dsphere',
1221
            );
1222
        }
1223
1224
        $this->_mongoCollection->ensureIndex($keys);
1225
1226
        return $this;
1227
    }
1228
1229
    /**
1230
     * Create geo index 2dsphere
1231
     *
1232
     * @link http://docs.mongodb.org/manual/tutorial/build-a-2d-index/
1233
     *
1234
     * @param string $field
1235
     * @return \Sokil\Mongo\Collection
1236
     */
1237 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...
1238
    {
1239
        if (is_array($field)) {
1240
            $keys = array_fill_keys($field, '2d');
1241
        } else {
1242
            $keys = array(
1243
                $field => '2d',
1244
            );
1245
        }
1246
1247
        $this->_mongoCollection->ensureIndex($keys);
1248
1249
        return $this;
1250
    }
1251
1252
    /**
1253
     * Create fulltext index
1254
     *
1255
     * @link https://docs.mongodb.org/manual/core/index-text/
1256
     * @link https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/
1257
     *
1258
     * If a collection contains documents or embedded documents that are in different languages,
1259
     * include a field named language in the documents or embedded documents and specify as its value the language
1260
     * for that document or embedded document.
1261
     *
1262
     * The specified language in the document overrides the default language for the text index.
1263
     * The specified language in an embedded document override the language specified in an enclosing document or
1264
     * the default language for the index.
1265
     *
1266
     * @param array|string $field definition of fields where full text index ensured.
1267
     *  May be string to ensure index on one field, array of fields to create full text index on few fields, and
1268
     *   widdcard '$**'  to create index on all fields of collection. Default value is '$**'
1269
     * @param $weights For a text index, the weight of an indexed field denotes the significance of the field
1270
     *   relative to the other indexed fields in terms of the text search score.
1271
     * @param $defaultLanguage The default language associated with the indexed data determines the rules to parse
1272
     *  word roots (i.e. stemming) and ignore stop words. The default language for the indexed data is english.
1273
     * @param $languageOverride To use a field with a name other than language, include the
1274
     *   language_override option when creating the index.
1275
     *
1276
     * @return Collection
1277
     */
1278
    public function ensureFulltextIndex(
1279
        $field = '$**',
1280
        array $weights = null,
1281
        $defaultLanguage = Language::ENGLISH,
1282
        $languageOverride = null
1283
    ) {
1284
        // keys
1285
        if (is_array($field)) {
1286
            $keys = array_fill_keys($field, 'text');
1287
        } else {
1288
            $keys = array(
1289
                $field => 'text',
1290
            );
1291
        }
1292
1293
        // options
1294
        $options = array(
1295
            'default_language' => $defaultLanguage,
1296
        );
1297
1298
        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...
1299
            $options['weights'] = $weights;
1300
        }
1301
1302
        if ($languageOverride) {
1303
            $options['language_override'] = $languageOverride;
1304
        }
1305
1306
        // create index
1307
        $this->_mongoCollection->ensureIndex($keys, $options);
1308
1309
        return $this;
1310
    }
1311
1312
1313
1314
    /**
1315
     * Create indexes based on self::$_index metadata
1316
     *
1317
     * @return \Sokil\Mongo\Collection
1318
     * @throws \Exception
1319
     */
1320
    public function initIndexes()
1321
    {
1322
        // read index definition from collection options
1323
        // if not specified - use defined in property
1324
        $indexDefinition = $this->definition->getOption('index');
1325
1326
        // ensure indexes
1327
        foreach($indexDefinition as $options) {
1328
1329
            if(empty($options['keys'])) {
1330
                throw new Exception('Keys not specified');
1331
            }
1332
1333
            $keys = $options['keys'];
1334
            unset($options['keys']);
1335
1336
            $this->_mongoCollection->ensureIndex($keys, $options);
1337
        }
1338
1339
        return $this;
1340
    }
1341
1342
    /**
1343
     * Get index info
1344
     * @return array
1345
     */
1346
    public function getIndexes()
1347
    {
1348
        return $this->_mongoCollection->getIndexInfo();
1349
    }
1350
1351
    public function readPrimaryOnly()
1352
    {
1353
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY);
1354
        return $this;
1355
    }
1356
1357
    public function readPrimaryPreferred(array $tags = null)
1358
    {
1359
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_PRIMARY_PREFERRED, $tags);
1360
        return $this;
1361
    }
1362
1363
    public function readSecondaryOnly(array $tags = null)
1364
    {
1365
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY, $tags);
1366
        return $this;
1367
    }
1368
1369
    public function readSecondaryPreferred(array $tags = null)
1370
    {
1371
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, $tags);
1372
        return $this;
1373
    }
1374
1375
    public function readNearest(array $tags = null)
1376
    {
1377
        $this->_mongoCollection->setReadPreference(\MongoClient::RP_NEAREST, $tags);
1378
        return $this;
1379
    }
1380
1381
    public function getReadPreference()
1382
    {
1383
        return $this->_mongoCollection->getReadPreference();
1384
    }
1385
1386
    /**
1387
     * Define write concern for all requests to current collection
1388
     *
1389
     * @param string|integer $w write concern
1390
     * @param int $timeout timeout in milliseconds
1391
     * @throws \Sokil\Mongo\Exception
1392
     * @return \Sokil\Mongo\Collection
1393
     */
1394
    public function setWriteConcern($w, $timeout = 10000)
1395
    {
1396
        if(!$this->_mongoCollection->setWriteConcern($w, (int) $timeout)) {
1397
            throw new Exception('Error setting write concern');
1398
        }
1399
1400
        return $this;
1401
    }
1402
1403
    /**
1404
     * Define unacknowledged write concern for all requests to current collection
1405
     *
1406
     * @param int $timeout timeout in milliseconds
1407
     * @throws \Sokil\Mongo\Exception
1408
     * @return \Sokil\Mongo\Collection
1409
     */
1410
    public function setUnacknowledgedWriteConcern($timeout = 10000)
1411
    {
1412
        $this->setWriteConcern(0, (int) $timeout);
1413
        return $this;
1414
    }
1415
1416
    /**
1417
     * Define majority write concern for all requests to current collection
1418
     *
1419
     * @param int $timeout timeout in milliseconds
1420
     * @throws \Sokil\Mongo\Exception
1421
     * @return \Sokil\Mongo\Collection
1422
     */
1423
    public function setMajorityWriteConcern($timeout = 10000)
1424
    {
1425
        $this->setWriteConcern('majority', (int) $timeout);
1426
        return $this;
1427
    }
1428
1429
    /**
1430
     * Get currently active write concern on all requests to collection
1431
     *
1432
     * @return int|string write concern
1433
     */
1434
    public function getWriteConcern()
1435
    {
1436
        return $this->_mongoCollection->getWriteConcern();
1437
    }
1438
1439
    /**
1440
     * Get collection stat
1441
     *
1442
     * @return array collection stat
1443
     */
1444
    public function stats()
1445
    {
1446
        return $this->getDatabase()->executeCommand(array(
1447
            'collstats' => $this->getName(),
1448
        ));
1449
    }
1450
}
1451