MongoCollection::getName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
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
use MongoDB\Driver\Exception\CommandException;
24
25
/**
26
 * Represents a database collection.
27
 * @link http://www.php.net/manual/en/class.mongocollection.php
28
 */
29
class MongoCollection
30
{
31
    use Helper\ReadPreference;
32
    use Helper\SlaveOkay;
33
    use Helper\WriteConcern;
34
35
    const ASCENDING = 1;
36
    const DESCENDING = -1;
37
38
    /**
39
     * @var MongoDB
40
     */
41
    public $db = null;
42
43
    /**
44
     * @var string
45
     */
46
    protected $name;
47
48
    /**
49
     * @var \MongoDB\Collection
50
     */
51
    protected $collection;
52
53
    /**
54
     * Creates a new collection
55
     *
56
     * @link http://www.php.net/manual/en/mongocollection.construct.php
57
     * @param MongoDB $db Parent database.
58
     * @param string $name Name for this collection.
59
     * @throws Exception
60
     */
61
    public function __construct(MongoDB $db, $name)
62
    {
63
        $this->checkCollectionName($name);
64
        $this->db = $db;
65
        $this->name = (string) $name;
66
67
        $this->setReadPreferenceFromArray($db->getReadPreference());
68
        $this->setWriteConcernFromArray($db->getWriteConcern());
69
70
        $this->createCollectionObject();
71
    }
72
73
    /**
74
     * Gets the underlying collection for this object
75
     *
76
     * @internal This part is not of the ext-mongo API and should not be used
77
     * @return \MongoDB\Collection
78
     */
79
    public function getCollection()
80
    {
81
        return $this->collection;
82
    }
83
84
    /**
85
     * String representation of this collection
86
     *
87
     * @link http://www.php.net/manual/en/mongocollection.--tostring.php
88
     * @return string Returns the full name of this collection.
89
     */
90
    public function __toString()
91
    {
92
        return (string) $this->db . '.' . $this->name;
93
    }
94
95
    /**
96
     * Gets a collection
97
     *
98
     * @link http://www.php.net/manual/en/mongocollection.get.php
99
     * @param string $name The next string in the collection name.
100
     * @return MongoCollection
101
     */
102
    public function __get($name)
103
    {
104
        // Handle w and wtimeout properties that replicate data stored in $readPreference
105
        if ($name === 'w' || $name === 'wtimeout') {
106
            return $this->getWriteConcern()[$name];
107
        }
108
109
        return $this->db->selectCollection($this->name . '.' . str_replace(chr(0), '', $name));
110
    }
111
112
    /**
113
     * @param string $name
114
     * @param mixed $value
115
     */
116
    public function __set($name, $value)
117
    {
118
        if ($name === 'w' || $name === 'wtimeout') {
119
            $this->setWriteConcernFromArray([$name => $value] + $this->getWriteConcern());
120
            $this->createCollectionObject();
121
        }
122
    }
123
124
    /**
125
     * Perform an aggregation using the aggregation framework
126
     *
127
     * @link http://www.php.net/manual/en/mongocollection.aggregate.php
128
     * @param array $pipeline
129
     * @param array $op
130
     * @return array
131
     */
132
    public function aggregate(array $pipeline, array $op = [])
133
    {
134
        if (! TypeConverter::isNumericArray($pipeline)) {
135
            $operators = func_get_args();
136
            $pipeline = [];
137
            $options = [];
138
139
            $i = 0;
140
            foreach ($operators as $operator) {
141
                $i++;
142
                if (! is_array($operator)) {
143
                    trigger_error("Argument $i is not an array", E_USER_WARNING);
144
                    return;
145
                }
146
147
                $pipeline[] = $operator;
148
            }
149
        } else {
150
            $options = $op;
151
        }
152
153
        if (isset($options['cursor'])) {
154
            $options['useCursor'] = true;
155
156
            if (isset($options['cursor']['batchSize'])) {
157
                $options['batchSize'] = $options['cursor']['batchSize'];
158
            }
159
160
            unset($options['cursor']);
161
        } else {
162
            $options['useCursor'] = false;
163
        }
164
165
        try {
166
            $cursor = $this->collection->aggregate(TypeConverter::fromLegacy($pipeline), $options);
167
168
            return [
169
                'ok' => 1.0,
170
                'result' => TypeConverter::toLegacy($cursor),
171
                'waitedMS' => 0,
172
            ];
173
        } catch (\MongoDB\Driver\Exception\Exception $e) {
174
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
175
        }
176
    }
177
178
    /**
179
     * Execute an aggregation pipeline command and retrieve results through a cursor
180
     *
181
     * @link http://php.net/manual/en/mongocollection.aggregatecursor.php
182
     * @param array $pipeline
183
     * @param array $options
184
     * @return MongoCommandCursor
185
     */
186
    public function aggregateCursor(array $pipeline, array $options = [])
187
    {
188
        // Build command manually, can't use mongo-php-library here
189
        $command = [
190
            'aggregate' => $this->name,
191
            'pipeline' => $pipeline
192
        ];
193
194
        // Convert cursor option
195
        if (! isset($options['cursor'])) {
196
            $options['cursor'] = new \stdClass();
197
        }
198
199
        $command += $options;
200
201
        $cursor = new MongoCommandCursor($this->db->getConnection(), (string) $this, $command);
202
        $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...
203
204
        return $cursor;
205
    }
206
207
    /**
208
     * Returns this collection's name
209
     *
210
     * @link http://www.php.net/manual/en/mongocollection.getname.php
211
     * @return string
212
     */
213
    public function getName()
214
    {
215
        return $this->name;
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function setReadPreference($readPreference, $tags = null)
222
    {
223
        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
224
        $this->createCollectionObject();
225
226
        return $result;
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public function setWriteConcern($wstring, $wtimeout = 0)
233
    {
234
        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
235
        $this->createCollectionObject();
236
237
        return $result;
238
    }
239
240
    /**
241
     * Drops this collection
242
     *
243
     * @link http://www.php.net/manual/en/mongocollection.drop.php
244
     * @return array Returns the database response.
245
     */
246
    public function drop()
247
    {
248
        return TypeConverter::toLegacy($this->collection->drop());
249
    }
250
251
    /**
252
     * Validates this collection
253
     *
254
     * @link http://www.php.net/manual/en/mongocollection.validate.php
255
     * @param bool $scan_data Only validate indices, not the base collection.
256
     * @return array Returns the database's evaluation of this object.
257
     */
258
    public function validate($scan_data = false)
259
    {
260
        $command = [
261
            'validate' => $this->name,
262
            'full'     => $scan_data,
263
        ];
264
265
        return $this->db->command($command);
266
    }
267
268
    /**
269
     * Inserts an array into the collection
270
     *
271
     * @link http://www.php.net/manual/en/mongocollection.insert.php
272
     * @param array|object $a
273
     * @param array $options
274
     * @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.
275
     * @throws MongoCursorException if the "w" option is set and the write fails.
276
     * @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.
277
     * @return bool|array Returns an array containing the status of the insertion if the "w" option is set.
278
     */
279
    public function insert(&$a, array $options = [])
280
    {
281
        if ($this->ensureDocumentHasMongoId($a) === null) {
282
            trigger_error(sprintf('%s(): expects parameter %d to be an array or object, %s given', __METHOD__, 1, gettype($a)), E_USER_WARNING);
283
            return;
284
        }
285
286
        $this->mustBeArrayOrObject($a);
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|object $criteria Description of the objects to update.
371
     * @param array|object $newobj The object with which to update the matching records.
372
     * @param array $options
373
     * @return bool|array
374
     * @throws MongoException
375
     * @throws MongoWriteConcernException
376
     */
377
    public function update($criteria, $newobj, array $options = [])
378
    {
379
        $this->mustBeArrayOrObject($criteria);
380
        $this->mustBeArrayOrObject($newobj);
381
382
        $this->checkKeys((array) $newobj);
383
384
        $multiple = isset($options['multiple']) ? $options['multiple'] : false;
385
        $isReplace = ! \MongoDB\is_first_key_operator($newobj);
386
387
        if ($isReplace && $multiple) {
388
            throw new \MongoWriteConcernException('multi update only works with $ operators', 9);
389
        }
390
        unset($options['multiple']);
391
392
        $method = $isReplace ? 'replace' : 'update';
393
        $method .= $multiple ? 'Many' : 'One';
394
395
        try {
396
            /** @var \MongoDB\UpdateResult $result */
397
            $result = $this->collection->$method(
398
                TypeConverter::fromLegacy($criteria),
399
                TypeConverter::fromLegacy($newobj),
400
                $this->convertWriteConcernOptions($options)
401
            );
402
        } catch (\MongoDB\Driver\Exception\Exception $e) {
403
            throw ExceptionConverter::toLegacy($e);
404
        }
405
406
        if (! $result->isAcknowledged()) {
407
            return true;
408
        }
409
410
        return [
411
            'ok' => 1.0,
412
            'nModified' => $result->getModifiedCount(),
413
            'n' => $result->getMatchedCount(),
414
            'err' => null,
415
            'errmsg' => null,
416
            'updatedExisting' => $result->getUpsertedCount() == 0 && $result->getModifiedCount() > 0,
417
        ];
418
    }
419
420
    /**
421
     * Remove records from this collection
422
     *
423
     * @link http://www.php.net/manual/en/mongocollection.remove.php
424
     * @param array $criteria Query criteria for the documents to delete.
425
     * @param array $options An array of options for the remove operation.
426
     * @throws MongoCursorException
427
     * @throws MongoCursorTimeoutException
428
     * @return bool|array Returns an array containing the status of the removal
429
     * if the "w" option is set. Otherwise, returns TRUE.
430
     */
431
    public function remove(array $criteria = [], array $options = [])
432
    {
433
        $multiple = isset($options['justOne']) ? !$options['justOne'] : true;
434
        $method = $multiple ? 'deleteMany' : 'deleteOne';
435
436
        try {
437
            /** @var \MongoDB\DeleteResult $result */
438
            $result = $this->collection->$method(
439
                TypeConverter::fromLegacy($criteria),
440
                $this->convertWriteConcernOptions($options)
441
            );
442
        } catch (\MongoDB\Driver\Exception\Exception $e) {
443
            throw ExceptionConverter::toLegacy($e);
444
        }
445
446
        if (! $result->isAcknowledged()) {
447
            return true;
448
        }
449
450
        return [
451
            'ok' => 1.0,
452
            'n' => $result->getDeletedCount(),
453
            'err' => null,
454
            'errmsg' => null
455
        ];
456
    }
457
458
    /**
459
     * Querys this collection
460
     *
461
     * @link http://www.php.net/manual/en/mongocollection.find.php
462
     * @param array $query The fields for which to search.
463
     * @param array $fields Fields of the results to return.
464
     * @return MongoCursor
465
     */
466 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...
467
    {
468
        $cursor = new MongoCursor($this->db->getConnection(), (string) $this, $query, $fields);
469
        $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...
470
471
        return $cursor;
472
    }
473
474
    /**
475
     * Retrieve a list of distinct values for the given key across a collection
476
     *
477
     * @link http://www.php.net/manual/ru/mongocollection.distinct.php
478
     * @param string $key The key to use.
479
     * @param array $query An optional query parameters
480
     * @return array|bool Returns an array of distinct values, or FALSE on failure
481
     */
482
    public function distinct($key, array $query = [])
483
    {
484
        try {
485
            return array_map([TypeConverter::class, 'toLegacy'], $this->collection->distinct($key, TypeConverter::fromLegacy($query)));
486
        } catch (\MongoDB\Driver\Exception\Exception $e) {
487
            return false;
488
        }
489
    }
490
491
    /**
492
     * Update a document and return it
493
     *
494
     * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
495
     * @param array $query The query criteria to search for.
496
     * @param array $update The update criteria.
497
     * @param array $fields Optionally only return these fields.
498
     * @param array $options An array of options to apply, such as remove the match document from the DB and return it.
499
     * @return array Returns the original document, or the modified document when new is set.
500
     */
501
    public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
502
    {
503
        $query = TypeConverter::fromLegacy($query);
504
        try {
505
            if (isset($options['remove'])) {
506
                unset($options['remove']);
507
                $document = $this->collection->findOneAndDelete($query, $options);
508
            } else {
509
                $update = is_array($update) ? $update : [];
510
                if (isset($options['update']) && is_array($options['update'])) {
511
                    $update = $options['update'];
512
                    unset($options['update']);
513
                }
514
515
                $update = TypeConverter::fromLegacy($update);
516
517
                if (isset($options['new'])) {
518
                    $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
519
                    unset($options['new']);
520
                }
521
522
                $options['projection'] = TypeConverter::convertProjection($fields);
0 ignored issues
show
Bug introduced by
It seems like $fields defined by parameter $fields on line 501 can also be of type null; however, Alcaeus\MongoDbAdapter\T...er::convertProjection() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
523
524
                if (! \MongoDB\is_first_key_operator($update)) {
525
                    $document = $this->collection->findOneAndReplace($query, $update, $options);
526
                } else {
527
                    $document = $this->collection->findOneAndUpdate($query, $update, $options);
528
                }
529
            }
530
        } catch (\MongoDB\Driver\Exception\ConnectionException $e) {
531
            throw new MongoResultException($e->getMessage(), $e->getCode(), $e);
532
        } catch (\MongoDB\Driver\Exception\Exception $e) {
533
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
534
        }
535
536
        if ($document) {
537
            $document = TypeConverter::toLegacy($document);
538
        }
539
540
        return $document;
541
    }
542
543
    /**
544
     * Querys this collection, returning a single element
545
     *
546
     * @link http://www.php.net/manual/en/mongocollection.findone.php
547
     * @param array $query The fields for which to search.
548
     * @param array $fields Fields of the results to return.
549
     * @param array $options
550
     * @return array|null
551
     */
552
    public function findOne($query = [], array $fields = [], array $options = [])
553
    {
554
        // Can't typehint for array since MongoGridFS extends and accepts strings
555
        if (! is_array($query)) {
556
            trigger_error(sprintf('MongoCollection::findOne(): expects parameter 1 to be an array or object, %s given', gettype($query)), E_USER_WARNING);
557
            return;
558
        }
559
560
        $options = ['projection' => TypeConverter::convertProjection($fields)] + $options;
561
        try {
562
            $document = $this->collection->findOne(TypeConverter::fromLegacy($query), $options);
563
        } catch (\MongoDB\Driver\Exception\Exception $e) {
564
            throw ExceptionConverter::toLegacy($e);
565
        }
566
567
        if ($document !== null) {
568
            $document = TypeConverter::toLegacy($document);
569
        }
570
571
        return $document;
572
    }
573
574
    /**
575
     * Creates an index on the given field(s), or does nothing if the index already exists
576
     *
577
     * @link http://www.php.net/manual/en/mongocollection.createindex.php
578
     * @param array $keys Field or fields to use as index.
579
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
580
     * @return array Returns the database response.
581
     */
582
    public function createIndex($keys, array $options = [])
583
    {
584
        if (is_string($keys)) {
585
            if (empty($keys)) {
586
                throw new MongoException('empty string passed as key field');
587
            }
588
            $keys = [$keys => 1];
589
        }
590
591
        if (is_object($keys)) {
592
            $keys = (array) $keys;
593
        }
594
595
        if (! is_array($keys) || ! count($keys)) {
596
            throw new MongoException('index specification has no elements');
597
        }
598
599
        if (! isset($options['name'])) {
600
            $options['name'] = \MongoDB\generate_index_name($keys);
601
        }
602
603
        $indexes = iterator_to_array($this->collection->listIndexes());
604
        $indexCount = count($indexes);
605
        $collectionExists = true;
606
        $indexExists = false;
607
608
        // listIndexes returns 0 for non-existing collections while the legacy driver returns 1
609
        if ($indexCount === 0) {
610
            $collectionExists = false;
611
            $indexCount = 1;
612
        }
613
614
        foreach ($indexes as $index) {
615
            if ($index->getKey() === $keys || $index->getName() === $options['name']) {
616
                $indexExists = true;
617
                break;
618
            }
619
        }
620
621
        try {
622
            foreach (['w', 'wTimeoutMS', 'safe', 'timeout', 'wtimeout'] as $invalidOption) {
623
                if (isset($options[$invalidOption])) {
624
                    unset($options[$invalidOption]);
625
                }
626
            }
627
628
            $this->collection->createIndex($keys, $options);
629
        } catch (\MongoDB\Driver\Exception\Exception $e) {
630
            if (! $e instanceof CommandException || strpos($e->getMessage(), 'with a different name') === false) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\CommandException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
631
                throw ExceptionConverter::toLegacy($e, 'MongoResultException');
632
            }
633
        }
634
635
        $result = [
636
            'createdCollectionAutomatically' => !$collectionExists,
637
            'numIndexesBefore' => $indexCount,
638
            'numIndexesAfter' => $indexCount,
639
            'note' => 'all indexes already exist',
640
            'ok' => 1.0,
641
        ];
642
643
        if (! $indexExists) {
644
            $result['numIndexesAfter']++;
645
            unset($result['note']);
646
        }
647
648
        return $result;
649
    }
650
651
    /**
652
     * Creates an index on the given field(s), or does nothing if the index already exists
653
     *
654
     * @link http://www.php.net/manual/en/mongocollection.ensureindex.php
655
     * @param array $keys Field or fields to use as index.
656
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
657
     * @return array Returns the database response.
658
     * @deprecated Use MongoCollection::createIndex() instead.
659
     */
660
    public function ensureIndex(array $keys, array $options = [])
661
    {
662
        return $this->createIndex($keys, $options);
663
    }
664
665
    /**
666
     * Deletes an index from this collection
667
     *
668
     * @link http://www.php.net/manual/en/mongocollection.deleteindex.php
669
     * @param string|array $keys Field or fields from which to delete the index.
670
     * @return array Returns the database response.
671
     */
672
    public function deleteIndex($keys)
673
    {
674
        if (is_string($keys)) {
675
            $indexName = $keys;
676
            if (! preg_match('#_-?1$#', $indexName)) {
677
                $indexName .= '_1';
678
            }
679
        } elseif (is_array($keys)) {
680
            $indexName = \MongoDB\generate_index_name($keys);
681
        } else {
682
            throw new \InvalidArgumentException();
683
        }
684
685
        try {
686
            return TypeConverter::toLegacy($this->collection->dropIndex($indexName));
687
        } catch (\MongoDB\Driver\Exception\Exception $e) {
688
            return ExceptionConverter::toResultArray($e) + ['nIndexesWas' => count($this->getIndexInfo())];
689
        }
690
    }
691
692
    /**
693
     * Delete all indexes for this collection
694
     *
695
     * @link http://www.php.net/manual/en/mongocollection.deleteindexes.php
696
     * @return array Returns the database response.
697
     */
698
    public function deleteIndexes()
699
    {
700
        try {
701
            return TypeConverter::toLegacy($this->collection->dropIndexes());
702
        } catch (\MongoDB\Driver\Exception\Exception $e) {
703
            return ExceptionConverter::toResultArray($e);
704
        }
705
    }
706
707
    /**
708
     * Returns an array of index names for this collection
709
     *
710
     * @link http://www.php.net/manual/en/mongocollection.getindexinfo.php
711
     * @return array Returns a list of index names.
712
     */
713
    public function getIndexInfo()
714
    {
715
        $convertIndex = function (\MongoDB\Model\IndexInfo $indexInfo) {
716
            $infos = [
717
                'v' => $indexInfo->getVersion(),
718
                'key' => $indexInfo->getKey(),
719
                'name' => $indexInfo->getName(),
720
                'ns' => $indexInfo->getNamespace(),
721
            ];
722
723
            $additionalKeys = [
724
                'unique',
725
                'sparse',
726
                'partialFilterExpression',
727
                'expireAfterSeconds',
728
                'storageEngine',
729
                'weights',
730
                'default_language',
731
                'language_override',
732
                'textIndexVersion',
733
                'collation',
734
                '2dsphereIndexVersion',
735
                'bucketSize'
736
            ];
737
738
            foreach ($additionalKeys as $key) {
739
                if (! isset($indexInfo[$key])) {
740
                    continue;
741
                }
742
743
                $infos[$key] = $indexInfo[$key];
744
            }
745
746
            return $infos;
747
        };
748
749
        return array_map($convertIndex, iterator_to_array($this->collection->listIndexes()));
750
    }
751
752
    /**
753
     * Counts the number of documents in this collection
754
     *
755
     * @link http://www.php.net/manual/en/mongocollection.count.php
756
     * @param array|stdClass $query
757
     * @param array $options
758
     * @return int Returns the number of documents matching the query.
759
     */
760
    public function count($query = [], $options = [])
761
    {
762
        try {
763
            // Handle legacy mode - limit and skip as second and third parameters, respectively
764
            if (! is_array($options)) {
765
                $limit = $options;
766
                $options = [];
767
768
                if ($limit !== null) {
769
                    $options['limit'] = (int) $limit;
770
                }
771
772
                if (func_num_args() > 2) {
773
                    $options['skip'] = (int) func_get_args()[2];
774
                }
775
            }
776
777
            return $this->collection->count(TypeConverter::fromLegacy($query), $options);
0 ignored issues
show
Deprecated Code introduced by
The method MongoDB\Collection::count() has been deprecated with message: 1.4

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
778
        } catch (\MongoDB\Driver\Exception\Exception $e) {
779
            throw ExceptionConverter::toLegacy($e);
780
        }
781
    }
782
783
    /**
784
     * Saves an object to this collection
785
     *
786
     * @link http://www.php.net/manual/en/mongocollection.save.php
787
     * @param array|object $a Array to save. If an object is used, it may not have protected or private properties.
788
     * @param array $options Options for the save.
789
     * @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.
790
     * @throws MongoCursorException if the "w" option is set and the write fails.
791
     * @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.
792
     * @return array|boolean If w was set, returns an array containing the status of the save.
793
     * Otherwise, returns a boolean representing if the array was not empty (an empty array will not be inserted).
794
     */
795
    public function save(&$a, array $options = [])
796
    {
797
        $id = $this->ensureDocumentHasMongoId($a);
798
799
        $document = (array) $a;
800
801
        $options['upsert'] = true;
802
803
        try {
804
            /** @var \MongoDB\UpdateResult $result */
805
            $result = $this->collection->replaceOne(
806
                TypeConverter::fromLegacy(['_id' => $id]),
807
                TypeConverter::fromLegacy($document),
808
                $this->convertWriteConcernOptions($options)
809
            );
810
811
            if (! $result->isAcknowledged()) {
812
                return true;
813
            }
814
815
            $resultArray = [
816
                'ok' => 1.0,
817
                'nModified' => $result->getModifiedCount(),
818
                'n' => $result->getUpsertedCount() + $result->getModifiedCount(),
819
                'err' => null,
820
                'errmsg' => null,
821
                'updatedExisting' => $result->getUpsertedCount() == 0 && $result->getModifiedCount() > 0,
822
            ];
823
            if ($result->getUpsertedId() !== null) {
824
                $resultArray['upserted'] = TypeConverter::toLegacy($result->getUpsertedId());
825
            }
826
827
            return $resultArray;
828
        } catch (\MongoDB\Driver\Exception\Exception $e) {
829
            throw ExceptionConverter::toLegacy($e);
830
        }
831
    }
832
833
    /**
834
     * Creates a database reference
835
     *
836
     * @link http://www.php.net/manual/en/mongocollection.createdbref.php
837
     * @param array|object $document_or_id Object to which to create a reference.
838
     * @return array Returns a database reference array.
839
     */
840
    public function createDBRef($document_or_id)
841
    {
842 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...
843
            $id = $document_or_id;
844
        } elseif (is_object($document_or_id)) {
845
            if (! isset($document_or_id->_id)) {
846
                return null;
847
            }
848
849
            $id = $document_or_id->_id;
850
        } elseif (is_array($document_or_id)) {
851
            if (! isset($document_or_id['_id'])) {
852
                return null;
853
            }
854
855
            $id = $document_or_id['_id'];
856
        } else {
857
            $id = $document_or_id;
858
        }
859
860
        return MongoDBRef::create($this->name, $id);
861
    }
862
863
    /**
864
     * Fetches the document pointed to by a database reference
865
     *
866
     * @link http://www.php.net/manual/en/mongocollection.getdbref.php
867
     * @param array $ref A database reference.
868
     * @return array Returns the database document pointed to by the reference.
869
     */
870
    public function getDBRef(array $ref)
871
    {
872
        return $this->db->getDBRef($ref);
873
    }
874
875
    /**
876
     * Performs an operation similar to SQL's GROUP BY command
877
     *
878
     * @link http://www.php.net/manual/en/mongocollection.group.php
879
     * @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.
880
     * @param array $initial Initial value of the aggregation counter object.
881
     * @param MongoCode|string $reduce A function that aggregates (reduces) the objects iterated.
882
     * @param array $condition An condition that must be true for a row to be considered.
883
     * @return array
884
     */
885
    public function group($keys, array $initial, $reduce, array $condition = [])
886
    {
887
        if (is_string($reduce)) {
888
            $reduce = new MongoCode($reduce);
889
        }
890
891
        $command = [
892
            'group' => [
893
                'ns' => $this->name,
894
                '$reduce' => (string) $reduce,
895
                'initial' => $initial,
896
                'cond' => $condition,
897
            ],
898
        ];
899
900
        if ($keys instanceof MongoCode) {
901
            $command['group']['$keyf'] = (string) $keys;
902
        } else {
903
            $command['group']['key'] = $keys;
904
        }
905
        if (array_key_exists('condition', $condition)) {
906
            $command['group']['cond'] = $condition['condition'];
907
        }
908
        if (array_key_exists('finalize', $condition)) {
909
            if ($condition['finalize'] instanceof MongoCode) {
910
                $condition['finalize'] = (string) $condition['finalize'];
911
            }
912
            $command['group']['finalize'] = $condition['finalize'];
913
        }
914
915
        return $this->db->command($command);
916
    }
917
918
    /**
919
     * Returns an array of cursors to iterator over a full collection in parallel
920
     *
921
     * @link http://www.php.net/manual/en/mongocollection.parallelcollectionscan.php
922
     * @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.
923
     * @return MongoCommandCursor[]
924
     */
925
    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...
926
    {
927
        $this->notImplemented();
928
    }
929
930
    protected function notImplemented()
931
    {
932
        throw new \Exception('Not implemented');
933
    }
934
935
    /**
936
     * @return \MongoDB\Collection
937
     */
938 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...
939
    {
940
        $options = [
941
            'readPreference' => $this->readPreference,
942
            'writeConcern' => $this->writeConcern,
943
        ];
944
945
        if ($this->collection === null) {
946
            $this->collection = $this->db->getDb()->selectCollection($this->name, $options);
947
        } else {
948
            $this->collection = $this->collection->withOptions($options);
949
        }
950
    }
951
952
    /**
953
     * Converts legacy write concern options to a WriteConcern object
954
     *
955
     * @param array $options
956
     * @return array
957
     */
958
    private function convertWriteConcernOptions(array $options)
959
    {
960
        if (isset($options['safe'])) {
961
            $options['w'] = ($options['safe']) ? 1 : 0;
962
        }
963
964 View Code Duplication
        if (isset($options['wtimeout']) && !isset($options['wTimeoutMS'])) {
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...
965
            $options['wTimeoutMS'] = $options['wtimeout'];
966
        }
967
968
        if (isset($options['w']) || !isset($options['wTimeoutMS'])) {
969
            $collectionWriteConcern = $this->getWriteConcern();
970
            $writeConcern = $this->createWriteConcernFromParameters(
971
                isset($options['w']) ? $options['w'] : $collectionWriteConcern['w'],
972
                isset($options['wTimeoutMS']) ? $options['wTimeoutMS'] : $collectionWriteConcern['wtimeout']
973
            );
974
975
            $options['writeConcern'] = $writeConcern;
976
        }
977
978
        unset($options['safe']);
979
        unset($options['w']);
980
        unset($options['wTimeout']);
981
        unset($options['wTimeoutMS']);
982
983
        return $options;
984
    }
985
986
    private function checkKeys(array $array)
987
    {
988
        foreach ($array as $key => $value) {
989
            if (empty($key) && $key !== 0 && $key !== '0') {
990
                throw new \MongoException('zero-length keys are not allowed, did you use $ with double quotes?');
991
            }
992
993
            if (is_object($value) || is_array($value)) {
994
                $this->checkKeys((array) $value);
995
            }
996
        }
997
    }
998
999
    /**
1000
     * @param array|object $document
1001
     * @return MongoId
1002
     */
1003
    private function ensureDocumentHasMongoId(&$document)
1004
    {
1005
        if (is_array($document) || $document instanceof ArrayObject) {
1006
            if (! isset($document['_id'])) {
1007
                $document['_id'] = new \MongoId();
1008
            }
1009
1010
            $this->checkKeys((array) $document);
1011
1012
            return $document['_id'];
1013
        } elseif (is_object($document)) {
1014
            $reflectionObject = new \ReflectionObject($document);
1015
            foreach ($reflectionObject->getProperties() as $property) {
1016
                if (! $property->isPublic()) {
1017
                    throw new \MongoException('zero-length keys are not allowed, did you use $ with double quotes?');
1018
                }
1019
            }
1020
1021
            if (! isset($document->_id)) {
1022
                $document->_id = new \MongoId();
1023
            }
1024
1025
            $this->checkKeys((array) $document);
1026
1027
            return $document->_id;
1028
        }
1029
1030
        return null;
1031
    }
1032
1033
    private function checkCollectionName($name)
1034
    {
1035
        if (empty($name)) {
1036
            throw new Exception('Collection name cannot be empty');
1037
        } elseif (strpos($name, chr(0)) !== false) {
1038
            throw new Exception('Collection name cannot contain null bytes');
1039
        }
1040
    }
1041
1042
    /**
1043
     * @return array
1044
     */
1045
    public function __sleep()
1046
    {
1047
        return ['db', 'name'];
1048
    }
1049
1050
    private function mustBeArrayOrObject($a)
1051
    {
1052
        if (!is_array($a) && !is_object($a)) {
1053
            throw new \MongoException('document must be an array or object');
1054
        }
1055
    }
1056
}
1057