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 ( c451b6...d37a73 )
by De
15:02
created

Collection::setUnacknowledgedWriteConcern()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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