Completed
Pull Request — master (#84)
by Andreas
03:52
created

MongoCollection::deleteIndex()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 19
rs 8.8571
cc 5
eloc 13
nc 7
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 28 and the first side effect is on line 17.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 */
15
16
if (class_exists('MongoCollection', false)) {
17
    return;
18
}
19
20
use Alcaeus\MongoDbAdapter\Helper;
21
use Alcaeus\MongoDbAdapter\TypeConverter;
22
use Alcaeus\MongoDbAdapter\ExceptionConverter;
23
24
/**
25
 * Represents a database collection.
26
 * @link http://www.php.net/manual/en/class.mongocollection.php
27
 */
28
class MongoCollection
29
{
30
    use Helper\ReadPreference;
31
    use Helper\SlaveOkay;
32
    use Helper\WriteConcern;
33
34
    const ASCENDING = 1;
35
    const DESCENDING = -1;
36
37
    /**
38
     * @var MongoDB
39
     */
40
    public $db = NULL;
41
42
    /**
43
     * @var string
44
     */
45
    protected $name;
46
47
    /**
48
     * @var \MongoDB\Collection
49
     */
50
    protected $collection;
51
52
    /**
53
     * Creates a new collection
54
     *
55
     * @link http://www.php.net/manual/en/mongocollection.construct.php
56
     * @param MongoDB $db Parent database.
57
     * @param string $name Name for this collection.
58
     * @throws Exception
59
     */
60
    public function __construct(MongoDB $db, $name)
61
    {
62
        $this->checkCollectionName($name);
63
        $this->db = $db;
64
        $this->name = $name;
65
66
        $this->setReadPreferenceFromArray($db->getReadPreference());
67
        $this->setWriteConcernFromArray($db->getWriteConcern());
68
69
        $this->createCollectionObject();
70
    }
71
72
    /**
73
     * Gets the underlying collection for this object
74
     *
75
     * @internal This part is not of the ext-mongo API and should not be used
76
     * @return \MongoDB\Collection
77
     */
78
    public function getCollection()
79
    {
80
        return $this->collection;
81
    }
82
83
    /**
84
     * String representation of this collection
85
     *
86
     * @link http://www.php.net/manual/en/mongocollection.--tostring.php
87
     * @return string Returns the full name of this collection.
88
     */
89
    public function __toString()
90
    {
91
        return (string) $this->db . '.' . $this->name;
92
    }
93
94
    /**
95
     * Gets a collection
96
     *
97
     * @link http://www.php.net/manual/en/mongocollection.get.php
98
     * @param string $name The next string in the collection name.
99
     * @return MongoCollection
100
     */
101
    public function __get($name)
102
    {
103
        // Handle w and wtimeout properties that replicate data stored in $readPreference
104
        if ($name === 'w' || $name === 'wtimeout') {
105
            return $this->getWriteConcern()[$name];
106
        }
107
108
        return $this->db->selectCollection($this->name . '.' . str_replace(chr(0), '', $name));
109
    }
110
111
    /**
112
     * @param string $name
113
     * @param mixed $value
114
     */
115
    public function __set($name, $value)
116
    {
117
        if ($name === 'w' || $name === 'wtimeout') {
118
            $this->setWriteConcernFromArray([$name => $value] + $this->getWriteConcern());
119
            $this->createCollectionObject();
120
        }
121
    }
122
123
    /**
124
     * Perform an aggregation using the aggregation framework
125
     *
126
     * @link http://www.php.net/manual/en/mongocollection.aggregate.php
127
     * @param array $pipeline
128
     * @param array $op
129
     * @return array
130
     */
131
    public function aggregate(array $pipeline, array $op = [])
132
    {
133
        if (! TypeConverter::isNumericArray($pipeline)) {
134
            $pipeline = [];
135
            $options = [];
136
137
            $i = 0;
138
            foreach (func_get_args() as $operator) {
139
                $i++;
140
                if (! is_array($operator)) {
141
                    trigger_error("Argument $i is not an array", E_WARNING);
142
                    return;
143
                }
144
145
                $pipeline[] = $operator;
146
            }
147
        } else {
148
            $options = $op;
149
        }
150
151
        if (isset($options['cursor'])) {
152
            $options['useCursor'] = true;
153
154
            if (isset($options['cursor']['batchSize'])) {
155
                $options['batchSize'] = $options['cursor']['batchSize'];
156
            }
157
158
            unset($options['cursor']);
159
        } else {
160
            $options['useCursor'] = false;
161
        }
162
163
        try {
164
            $cursor = $this->collection->aggregate(TypeConverter::fromLegacy($pipeline), $options);
165
166
            return [
167
                'ok' => 1.0,
168
                'result' => TypeConverter::toLegacy($cursor),
169
                'waitedMS' => 0,
170
            ];
171
        } catch (\MongoDB\Driver\Exception\Exception $e) {
172
            throw ExceptionConverter::toLegacy($e,'MongoResultException');
173
        }
174
    }
175
176
    /**
177
     * Execute an aggregation pipeline command and retrieve results through a cursor
178
     *
179
     * @link http://php.net/manual/en/mongocollection.aggregatecursor.php
180
     * @param array $pipeline
181
     * @param array $options
182
     * @return MongoCommandCursor
183
     */
184
    public function aggregateCursor(array $pipeline, array $options = [])
185
    {
186
        // Build command manually, can't use mongo-php-library here
187
        $command = [
188
            'aggregate' => $this->name,
189
            'pipeline' => $pipeline
190
        ];
191
192
        // Convert cursor option
193
        if (! isset($options['cursor'])) {
194
            $options['cursor'] = new \stdClass();
195
        }
196
197
        $command += $options;
198
199
        $cursor = new MongoCommandCursor($this->db->getConnection(), (string) $this, $command);
200
        $cursor->setReadPreference($this->getReadPreference());
0 ignored issues
show
Documentation introduced by
$this->getReadPreference() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201
202
        return $cursor;
203
    }
204
205
    /**
206
     * Returns this collection's name
207
     *
208
     * @link http://www.php.net/manual/en/mongocollection.getname.php
209
     * @return string
210
     */
211
    public function getName()
212
    {
213
        return $this->name;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function setReadPreference($readPreference, $tags = null)
220
    {
221
        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
222
        $this->createCollectionObject();
223
224
        return $result;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function setWriteConcern($wstring, $wtimeout = 0)
231
    {
232
        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
233
        $this->createCollectionObject();
234
235
        return $result;
236
    }
237
238
    /**
239
     * Drops this collection
240
     *
241
     * @link http://www.php.net/manual/en/mongocollection.drop.php
242
     * @return array Returns the database response.
243
     */
244
    public function drop()
245
    {
246
        return TypeConverter::toLegacy($this->collection->drop());
247
    }
248
249
    /**
250
     * Validates this collection
251
     *
252
     * @link http://www.php.net/manual/en/mongocollection.validate.php
253
     * @param bool $scan_data Only validate indices, not the base collection.
254
     * @return array Returns the database's evaluation of this object.
255
     */
256
    public function validate($scan_data = FALSE)
257
    {
258
        $command = [
259
            'validate' => $this->name,
260
            'full'     => $scan_data,
261
        ];
262
263
        return $this->db->command($command);
264
    }
265
266
    /**
267
     * Inserts an array into the collection
268
     *
269
     * @link http://www.php.net/manual/en/mongocollection.insert.php
270
     * @param array|object $a
271
     * @param array $options
272
     * @throws MongoException if the inserted document is empty or if it contains zero-length keys. Attempting to insert an object with protected and private properties will cause a zero-length key error.
273
     * @throws MongoCursorException if the "w" option is set and the write fails.
274
     * @throws MongoCursorTimeoutException if the "w" option is set to a value greater than one and the operation takes longer than MongoCursor::$timeout milliseconds to complete. This does not kill the operation on the server, it is a client-side timeout. The operation in MongoCollection::$wtimeout is milliseconds.
275
     * @return bool|array Returns an array containing the status of the insertion if the "w" option is set.
276
     */
277
    public function insert(&$a, array $options = [])
278
    {
279
        if (! $this->ensureDocumentHasMongoId($a)) {
280
            trigger_error(sprintf('%s(): expects parameter %d to be an array or object, %s given', __METHOD__, 1, gettype($a)), E_USER_WARNING);
281
            return;
282
        }
283
284
        if (! count((array)$a)) {
285
            throw new \MongoException('document must be an array or object');
286
        }
287
288
        try {
289
            $result = $this->collection->insertOne(
290
                TypeConverter::fromLegacy($a),
291
                $this->convertWriteConcernOptions($options)
292
            );
293
        } catch (\MongoDB\Driver\Exception\Exception $e) {
294
            throw ExceptionConverter::toLegacy($e);
295
        }
296
297
        if (! $result->isAcknowledged()) {
298
            return true;
299
        }
300
301
        return [
302
            'ok' => 1.0,
303
            'n' => 0,
304
            'err' => null,
305
            'errmsg' => null,
306
        ];
307
    }
308
309
    /**
310
     * Inserts multiple documents into this collection
311
     *
312
     * @link http://www.php.net/manual/en/mongocollection.batchinsert.php
313
     * @param array $a An array of arrays.
314
     * @param array $options Options for the inserts.
315
     * @throws MongoCursorException
316
     * @return mixed If "safe" is set, returns an associative array with the status of the inserts ("ok") and any error that may have occured ("err"). Otherwise, returns TRUE if the batch insert was successfully sent, FALSE otherwise.
317
     */
318
    public function batchInsert(array &$a, array $options = [])
319
    {
320
        if (empty($a)) {
321
            throw new \MongoException('No write ops were included in the batch');
322
        }
323
324
        $continueOnError = isset($options['continueOnError']) && $options['continueOnError'];
325
326
        foreach ($a as $key => $item) {
327
            try {
328
                if (! $this->ensureDocumentHasMongoId($a[$key])) {
329
                    if ($continueOnError) {
330
                        unset($a[$key]);
331
                    } else {
332
                        trigger_error(sprintf('%s expects parameter %d to be an array or object, %s given', __METHOD__, 1, gettype($a)), E_USER_WARNING);
333
                        return;
334
                    }
335
                }
336
            } catch (MongoException $e) {
337
                if ( ! $continueOnError) {
338
                    throw $e;
339
                }
340
            }
341
        }
342
343
        try {
344
            $result = $this->collection->insertMany(
345
                TypeConverter::fromLegacy(array_values($a)),
346
                $this->convertWriteConcernOptions($options)
347
            );
348
        } catch (\MongoDB\Driver\Exception\Exception $e) {
349
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
350
        }
351
352
        if (! $result->isAcknowledged()) {
353
            return true;
354
        }
355
356
        return [
357
            'ok' => 1.0,
358
            'connectionId' => 0,
359
            'n' => 0,
360
            'syncMillis' => 0,
361
            'writtenTo' => null,
362
            'err' => null,
363
        ];
364
    }
365
366
    /**
367
     * Update records based on a given criteria
368
     *
369
     * @link http://www.php.net/manual/en/mongocollection.update.php
370
     * @param array $criteria Description of the objects to update.
371
     * @param array $newobj The object with which to update the matching records.
372
     * @param array $options
373
     * @throws MongoCursorException
374
     * @return boolean
375
     */
376
    public function update(array $criteria , array $newobj, array $options = [])
377
    {
378
        $multiple = isset($options['multiple']) ? $options['multiple'] : false;
379
        $isReplace = ! \MongoDB\is_first_key_operator($newobj);
380
381
        if ($isReplace && $multiple) {
382
            throw new \MongoWriteConcernException('multi update only works with $ operators', 9);
383
        }
384
        unset($options['multiple']);
385
386
        $method = $isReplace ? 'replace' : 'update';
387
        $method .= $multiple ? 'Many' : 'One';
388
389
        try {
390
            /** @var \MongoDB\UpdateResult $result */
391
            $result = $this->collection->$method(
392
                TypeConverter::fromLegacy($criteria),
393
                TypeConverter::fromLegacy($newobj),
394
                $this->convertWriteConcernOptions($options)
395
            );
396
        } catch (\MongoDB\Driver\Exception\Exception $e) {
397
            throw ExceptionConverter::toLegacy($e);
398
        }
399
400
        if (! $result->isAcknowledged()) {
401
            return true;
402
        }
403
404
        return [
405
            'ok' => 1.0,
406
            'nModified' => $result->getModifiedCount(),
407
            'n' => $result->getMatchedCount(),
408
            'err' => null,
409
            'errmsg' => null,
410
            'updatedExisting' => $result->getUpsertedCount() == 0,
411
        ];
412
    }
413
414
    /**
415
     * Remove records from this collection
416
     *
417
     * @link http://www.php.net/manual/en/mongocollection.remove.php
418
     * @param array $criteria Query criteria for the documents to delete.
419
     * @param array $options An array of options for the remove operation.
420
     * @throws MongoCursorException
421
     * @throws MongoCursorTimeoutException
422
     * @return bool|array Returns an array containing the status of the removal
423
     * if the "w" option is set. Otherwise, returns TRUE.
424
     */
425
    public function remove(array $criteria = [], array $options = [])
426
    {
427
        $multiple = isset($options['justOne']) ? !$options['justOne'] : true;
428
        $method = $multiple ? 'deleteMany' : 'deleteOne';
429
430
        try {
431
            /** @var \MongoDB\DeleteResult $result */
432
            $result = $this->collection->$method(
433
                TypeConverter::fromLegacy($criteria),
434
                $this->convertWriteConcernOptions($options)
435
            );
436
        } catch (\MongoDB\Driver\Exception\Exception $e) {
437
            throw ExceptionConverter::toLegacy($e);
438
        }
439
440
        if (! $result->isAcknowledged()) {
441
            return true;
442
        }
443
444
        return [
445
            'ok' => 1.0,
446
            'n' => $result->getDeletedCount(),
447
            'err' => null,
448
            'errmsg' => null
449
        ];
450
    }
451
452
    /**
453
     * Querys this collection
454
     *
455
     * @link http://www.php.net/manual/en/mongocollection.find.php
456
     * @param array $query The fields for which to search.
457
     * @param array $fields Fields of the results to return.
458
     * @return MongoCursor
459
     */
460 View Code Duplication
    public function find(array $query = [], array $fields = [])
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...
461
    {
462
        $cursor = new MongoCursor($this->db->getConnection(), (string) $this, $query, $fields);
463
        $cursor->setReadPreference($this->getReadPreference());
0 ignored issues
show
Documentation introduced by
$this->getReadPreference() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
464
465
        return $cursor;
466
    }
467
468
    /**
469
     * Retrieve a list of distinct values for the given key across a collection
470
     *
471
     * @link http://www.php.net/manual/ru/mongocollection.distinct.php
472
     * @param string $key The key to use.
473
     * @param array $query An optional query parameters
474
     * @return array|bool Returns an array of distinct values, or FALSE on failure
475
     */
476
    public function distinct($key, array $query = [])
477
    {
478
        try {
479
            return array_map([TypeConverter::class, 'toLegacy'], $this->collection->distinct($key, $query));
480
        } catch (\MongoDB\Driver\Exception\Exception $e) {
481
            return false;
482
        }
483
    }
484
485
    /**
486
     * Update a document and return it
487
     *
488
     * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
489
     * @param array $query The query criteria to search for.
490
     * @param array $update The update criteria.
491
     * @param array $fields Optionally only return these fields.
492
     * @param array $options An array of options to apply, such as remove the match document from the DB and return it.
493
     * @return array Returns the original document, or the modified document when new is set.
494
     */
495
    public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
496
    {
497
        $query = TypeConverter::fromLegacy($query);
498
        try {
499
            if (isset($options['remove'])) {
500
                unset($options['remove']);
501
                $document = $this->collection->findOneAndDelete($query, $options);
502
            } else {
503
                $update = is_array($update) ? $update : [];
504
                if (isset($options['update']) && is_array($options['update'])) {
505
                    $update = array_merge($update, $options['update']);
506
                    unset($options['update']);
507
                }
508
509
                $update = TypeConverter::fromLegacy($update);
510
511
                if (isset($options['new'])) {
512
                    $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
513
                    unset($options['new']);
514
                }
515
516
                $options['projection'] = is_array($fields) ? TypeConverter::fromLegacy($fields) : [];
517
518
                if (! \MongoDB\is_first_key_operator($update)) {
519
                    $document = $this->collection->findOneAndReplace($query, $update, $options);
520
                } else {
521
                    $document = $this->collection->findOneAndUpdate($query, $update, $options);
522
                }
523
            }
524
        } catch (\MongoDB\Driver\Exception\ConnectionException $e) {
525
            throw new MongoResultException($e->getMessage(), $e->getCode(), $e);
526
        } catch (\MongoDB\Driver\Exception\Exception $e) {
527
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
528
        }
529
530
        if ($document) {
531
            $document = TypeConverter::toLegacy($document);
532
        }
533
534
        return $document;
535
    }
536
537
    /**
538
     * Querys this collection, returning a single element
539
     *
540
     * @link http://www.php.net/manual/en/mongocollection.findone.php
541
     * @param array $query The fields for which to search.
542
     * @param array $fields Fields of the results to return.
543
     * @param array $options
544
     * @return array|null
545
     */
546
    public function findOne($query = [], array $fields = [], array $options = [])
547
    {
548
        // Can't typehint for array since MongoGridFS extends and accepts strings
549
        if (! is_array($query)) {
550
            trigger_error(sprintf('MongoCollection::findOne(): expects parameter 1 to be an array or object, %s given', gettype($query)), E_WARNING);
551
            return;
552
        }
553
554
        $options = ['projection' => $fields] + $options;
555
        try {
556
            $document = $this->collection->findOne(TypeConverter::fromLegacy($query), $options);
557
        } catch (\MongoDB\Driver\Exception\Exception $e) {
558
            throw ExceptionConverter::toLegacy($e);
559
        }
560
561
        if ($document !== null) {
562
            $document = TypeConverter::toLegacy($document);
563
        }
564
565
        return $document;
566
    }
567
568
    /**
569
     * Creates an index on the given field(s), or does nothing if the index already exists
570
     *
571
     * @link http://www.php.net/manual/en/mongocollection.createindex.php
572
     * @param array $keys Field or fields to use as index.
573
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
574
     * @return array Returns the database response.
575
     */
576
    public function createIndex($keys, array $options = [])
577
    {
578
        if (is_string($keys)) {
579
            if (empty($keys)) {
580
                throw new MongoException('empty string passed as key field');
581
            }
582
            $keys = [$keys => 1];
583
        }
584
585
        if (is_object($keys)) {
586
            $keys = (array) $keys;
587
        }
588
589
        if (! is_array($keys) || ! count($keys)) {
590
            throw new MongoException('index specification has no elements');
591
        }
592
593
        if (! isset($options['name'])) {
594
            $options['name'] = \MongoDB\generate_index_name($keys);
595
        }
596
597
        $indexes = iterator_to_array($this->collection->listIndexes());
598
        $indexCount = count($indexes);
599
        $collectionExists = true;
600
        $indexExists = false;
601
602
        // listIndexes returns 0 for non-existing collections while the legacy driver returns 1
603
        if ($indexCount === 0) {
604
            $collectionExists = false;
605
            $indexCount = 1;
606
        }
607
608
        foreach ($indexes as $index) {
609
            if ($index->getKey() === $keys || $index->getName() === $options['name']) {
610
                $indexExists = true;
611
                break;
612
            }
613
        }
614
615
        try {
616
            foreach (['w', 'wTimeoutMS', 'safe', 'timeout', 'wtimeout'] as $invalidOption) {
617
                if (isset($options[$invalidOption])) {
618
                    unset($options[$invalidOption]);
619
                }
620
            }
621
622
            $this->collection->createIndex($keys, $options);
623
        } catch (\MongoDB\Driver\Exception\Exception $e) {
624
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
625
        }
626
627
        $result = [
628
            'createdCollectionAutomatically' => !$collectionExists,
629
            'numIndexesBefore' => $indexCount,
630
            'numIndexesAfter' => $indexCount,
631
            'note' => 'all indexes already exist',
632
            'ok' => 1.0,
633
        ];
634
635
        if (! $indexExists) {
636
            $result['numIndexesAfter']++;
637
            unset($result['note']);
638
        }
639
640
        return $result;
641
    }
642
643
    /**
644
     * Creates an index on the given field(s), or does nothing if the index already exists
645
     *
646
     * @link http://www.php.net/manual/en/mongocollection.ensureindex.php
647
     * @param array $keys Field or fields to use as index.
648
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
649
     * @return array Returns the database response.
650
     * @deprecated Use MongoCollection::createIndex() instead.
651
     */
652
    public function ensureIndex(array $keys, array $options = [])
653
    {
654
        return $this->createIndex($keys, $options);
655
    }
656
657
    /**
658
     * Deletes an index from this collection
659
     *
660
     * @link http://www.php.net/manual/en/mongocollection.deleteindex.php
661
     * @param string|array $keys Field or fields from which to delete the index.
662
     * @return array Returns the database response.
663
     */
664
    public function deleteIndex($keys)
665
    {
666
        if (is_string($keys)) {
667
            $indexName = $keys;
668
            if (! preg_match('#_-?1$#', $indexName)) {
669
                $indexName .= '_1';
670
            }
671
        } elseif (is_array($keys)) {
672
            $indexName = \MongoDB\generate_index_name($keys);
673
        } else {
674
            throw new \InvalidArgumentException();
675
        }
676
677
        try {
678
            return TypeConverter::toLegacy($this->collection->dropIndex($indexName));
679
        } catch (\MongoDB\Driver\Exception\Exception $e) {
680
            return ExceptionConverter::toResultArray($e) + ['nIndexesWas' => count($this->getIndexInfo())];
681
        }
682
    }
683
684
    /**
685
     * Delete all indexes for this collection
686
     *
687
     * @link http://www.php.net/manual/en/mongocollection.deleteindexes.php
688
     * @return array Returns the database response.
689
     */
690
    public function deleteIndexes()
691
    {
692
        try {
693
            return TypeConverter::toLegacy($this->collection->dropIndexes());
694
        } catch (\MongoDB\Driver\Exception\Exception $e) {
695
            return ExceptionConverter::toResultArray($e);
696
        }
697
    }
698
699
    /**
700
     * Returns an array of index names for this collection
701
     *
702
     * @link http://www.php.net/manual/en/mongocollection.getindexinfo.php
703
     * @return array Returns a list of index names.
704
     */
705
    public function getIndexInfo()
706
    {
707
        $convertIndex = function(\MongoDB\Model\IndexInfo $indexInfo) {
708
            return [
709
                'v' => $indexInfo->getVersion(),
710
                'key' => $indexInfo->getKey(),
711
                'name' => $indexInfo->getName(),
712
                'ns' => $indexInfo->getNamespace(),
713
            ];
714
        };
715
716
        return array_map($convertIndex, iterator_to_array($this->collection->listIndexes()));
717
    }
718
719
    /**
720
     * Counts the number of documents in this collection
721
     *
722
     * @link http://www.php.net/manual/en/mongocollection.count.php
723
     * @param array|stdClass $query
724
     * @param array $options
725
     * @return int Returns the number of documents matching the query.
726
     */
727
    public function count($query = [], $options = [])
728
    {
729
        try {
730
            // Handle legacy mode - limit and skip as second and third parameters, respectively
731
            if (! is_array($options)) {
732
                $limit = $options;
733
                $options = [];
734
735
                if ($limit !== null) {
736
                    $options['limit'] = (int) $limit;
737
                }
738
739
                if (func_num_args() > 2) {
740
                    $options['skip'] = (int) func_get_args()[2];
741
                }
742
            }
743
744
            return $this->collection->count(TypeConverter::fromLegacy($query), $options);
745
        } catch (\MongoDB\Driver\Exception\Exception $e) {
746
            throw ExceptionConverter::toLegacy($e);
747
        }
748
    }
749
750
    /**
751
     * Saves an object to this collection
752
     *
753
     * @link http://www.php.net/manual/en/mongocollection.save.php
754
     * @param array|object $a Array to save. If an object is used, it may not have protected or private properties.
755
     * @param array $options Options for the save.
756
     * @throws MongoException if the inserted document is empty or if it contains zero-length keys. Attempting to insert an object with protected and private properties will cause a zero-length key error.
757
     * @throws MongoCursorException if the "w" option is set and the write fails.
758
     * @throws MongoCursorTimeoutException if the "w" option is set to a value greater than one and the operation takes longer than MongoCursor::$timeout milliseconds to complete. This does not kill the operation on the server, it is a client-side timeout. The operation in MongoCollection::$wtimeout is milliseconds.
759
     * @return array|boolean If w was set, returns an array containing the status of the save.
760
     * Otherwise, returns a boolean representing if the array was not empty (an empty array will not be inserted).
761
     */
762
    public function save(&$a, array $options = [])
763
    {
764
        $id = $this->ensureDocumentHasMongoId($a);
765
766
        $document = (array) $a;
767
768
        $options['upsert'] = true;
769
770
        try {
771
            /** @var \MongoDB\UpdateResult $result */
772
            $result = $this->collection->replaceOne(
773
                TypeConverter::fromLegacy(['_id' => $id]),
774
                TypeConverter::fromLegacy($document),
775
                $this->convertWriteConcernOptions($options)
776
            );
777
778
            if (! $result->isAcknowledged()) {
779
                return true;
780
            }
781
782
            $resultArray = [
783
                'ok' => 1.0,
784
                'nModified' => $result->getModifiedCount(),
785
                'n' => $result->getUpsertedCount() + $result->getModifiedCount(),
786
                'err' => null,
787
                'errmsg' => null,
788
                'updatedExisting' => $result->getUpsertedCount() == 0,
789
            ];
790
            if ($result->getUpsertedId() !== null) {
791
                $resultArray['upserted'] = TypeConverter::toLegacy($result->getUpsertedId());
792
            }
793
794
            return $resultArray;
795
        } catch (\MongoDB\Driver\Exception\Exception $e) {
796
            throw ExceptionConverter::toLegacy($e);
797
        }
798
    }
799
800
    /**
801
     * Creates a database reference
802
     *
803
     * @link http://www.php.net/manual/en/mongocollection.createdbref.php
804
     * @param array|object $document_or_id Object to which to create a reference.
805
     * @return array Returns a database reference array.
806
     */
807
    public function createDBRef($document_or_id)
808
    {
809 View Code Duplication
        if ($document_or_id instanceof \MongoId) {
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...
810
            $id = $document_or_id;
811
        } elseif (is_object($document_or_id)) {
812
            if (! isset($document_or_id->_id)) {
813
                return null;
814
            }
815
816
            $id = $document_or_id->_id;
817
        } elseif (is_array($document_or_id)) {
818
            if (! isset($document_or_id['_id'])) {
819
                return null;
820
            }
821
822
            $id = $document_or_id['_id'];
823
        } else {
824
            $id = $document_or_id;
825
        }
826
827
        return MongoDBRef::create($this->name, $id);
828
    }
829
830
    /**
831
     * Fetches the document pointed to by a database reference
832
     *
833
     * @link http://www.php.net/manual/en/mongocollection.getdbref.php
834
     * @param array $ref A database reference.
835
     * @return array Returns the database document pointed to by the reference.
836
     */
837
    public function getDBRef(array $ref)
838
    {
839
        return $this->db->getDBRef($ref);
840
    }
841
842
    /**
843
     * Performs an operation similar to SQL's GROUP BY command
844
     *
845
     * @link http://www.php.net/manual/en/mongocollection.group.php
846
     * @param mixed $keys Fields to group by. If an array or non-code object is passed, it will be the key used to group results.
847
     * @param array $initial Initial value of the aggregation counter object.
848
     * @param MongoCode|string $reduce A function that aggregates (reduces) the objects iterated.
849
     * @param array $condition An condition that must be true for a row to be considered.
850
     * @return array
851
     */
852
    public function group($keys, array $initial, $reduce, array $condition = [])
853
    {
854
        if (is_string($reduce)) {
855
            $reduce = new MongoCode($reduce);
856
        }
857
858
        $command = [
859
            'group' => [
860
                'ns' => $this->name,
861
                '$reduce' => (string)$reduce,
862
                'initial' => $initial,
863
                'cond' => $condition,
864
            ],
865
        ];
866
867
        if ($keys instanceof MongoCode) {
868
            $command['group']['$keyf'] = (string)$keys;
869
        } else {
870
            $command['group']['key'] = $keys;
871
        }
872
        if (array_key_exists('condition', $condition)) {
873
            $command['group']['cond'] = $condition['condition'];
874
        }
875
        if (array_key_exists('finalize', $condition)) {
876
            if ($condition['finalize'] instanceof MongoCode) {
877
                $condition['finalize'] = (string)$condition['finalize'];
878
            }
879
            $command['group']['finalize'] = $condition['finalize'];
880
        }
881
882
        return $this->db->command($command);
883
    }
884
885
    /**
886
     * Returns an array of cursors to iterator over a full collection in parallel
887
     *
888
     * @link http://www.php.net/manual/en/mongocollection.parallelcollectionscan.php
889
     * @param int $num_cursors The number of cursors to request from the server. Please note, that the server can return less cursors than you requested.
890
     * @return MongoCommandCursor[]
891
     */
892
    public function parallelCollectionScan($num_cursors)
0 ignored issues
show
Unused Code introduced by
The parameter $num_cursors is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
893
    {
894
        $this->notImplemented();
895
    }
896
897
    protected function notImplemented()
898
    {
899
        throw new \Exception('Not implemented');
900
    }
901
902
    /**
903
     * @return \MongoDB\Collection
904
     */
905 View Code Duplication
    private function createCollectionObject()
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...
906
    {
907
        $options = [
908
            'readPreference' => $this->readPreference,
909
            'writeConcern' => $this->writeConcern,
910
        ];
911
912
        if ($this->collection === null) {
913
            $this->collection = $this->db->getDb()->selectCollection($this->name, $options);
914
        } else {
915
            $this->collection = $this->collection->withOptions($options);
916
        }
917
    }
918
919
    /**
920
     * Converts legacy write concern options to a WriteConcern object
921
     *
922
     * @param array $options
923
     * @return array
924
     */
925
    private function convertWriteConcernOptions(array $options)
926
    {
927
        if (isset($options['safe'])) {
928
            $options['w'] = ($options['safe']) ? 1 : 0;
929
        }
930
931
        if (isset($options['wtimeout']) && !isset($options['wTimeoutMS'])) {
932
            $options['wTimeoutMS'] = $options['wtimeout'];
933
        }
934
935
        if (isset($options['w']) || !isset($options['wTimeoutMS'])) {
936
            $collectionWriteConcern = $this->getWriteConcern();
937
            $writeConcern = $this->createWriteConcernFromParameters(
938
                isset($options['w']) ? $options['w'] : $collectionWriteConcern['w'],
939
                isset($options['wTimeoutMS']) ? $options['wTimeoutMS'] : $collectionWriteConcern['wtimeout']
940
            );
941
942
            $options['writeConcern'] = $writeConcern;
943
        }
944
945
        unset($options['safe']);
946
        unset($options['w']);
947
        unset($options['wTimeout']);
948
        unset($options['wTimeoutMS']);
949
950
        return $options;
951
    }
952
953
    /**
954
     * @param array|object $document
955
     * @return MongoId
956
     */
957
    private function ensureDocumentHasMongoId(&$document)
958
    {
959
        $checkKeys = function($array) {
960
            foreach (array_keys($array) as $key) {
961
                if (empty($key) && $key !== 0) {
962
                    throw new \MongoException('zero-length keys are not allowed, did you use $ with double quotes?');
963
                }
964
            }
965
        };
966
967
        if (is_array($document)) {
968
            if (! isset($document['_id'])) {
969
                $document['_id'] = new \MongoId();
970
            }
971
972
            $checkKeys($document);
973
974
            return $document['_id'];
975
        } elseif (is_object($document)) {
976
            $reflectionObject = new \ReflectionObject($document);
977
            foreach ($reflectionObject->getProperties() as $property) {
978
                if (! $property->isPublic()) {
979
                    throw new \MongoException('zero-length keys are not allowed, did you use $ with double quotes?');
980
                }
981
            }
982
983
            if (! isset($document->_id)) {
984
                $document->_id = new \MongoId();
985
            }
986
987
            $checkKeys((array) $document);
988
989
            return $document->_id;
990
        }
991
992
        return null;
993
    }
994
995
    private function checkCollectionName($name)
996
    {
997
        if (empty($name)) {
998
            throw new Exception('Collection name cannot be empty');
999
        } elseif (strpos($name, chr(0)) !== false) {
1000
            throw new Exception('Collection name cannot contain null bytes');
1001
        }
1002
    }
1003
1004
    /**
1005
     * @return array
1006
     */
1007
    public function __sleep()
1008
    {
1009
        return ['db', 'name'];
1010
    }
1011
}
1012
1013