Completed
Pull Request — master (#28)
by Andreas
03:19
created

MongoCollection   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 911
Duplicated Lines 4.06 %

Coupling/Cohesion

Components 1
Dependencies 18
Metric Value
wmc 123
lcom 1
cbo 18
dl 37
loc 911
rs 1.263

36 Methods

Rating   Name   Duplication   Size   Complexity  
A getCollection() 0 4 1
A __toString() 0 4 1
A __construct() 0 11 1
A __get() 0 9 3
A find() 7 7 1
A distinct() 0 8 2
A __set() 0 7 3
C aggregate() 0 38 7
A aggregateCursor() 0 20 2
A getName() 0 4 1
A setReadPreference() 0 7 1
A setWriteConcern() 0 7 1
A drop() 0 4 1
A validate() 0 9 1
B insert() 0 31 5
C batchInsert() 0 47 10
B update() 0 30 5
B remove() 0 26 5
C findAndModify() 0 31 8
A findOne() 0 15 3
C createIndex() 0 57 12
A ensureIndex() 0 6 1
A deleteIndex() 0 12 3
A deleteIndexes() 0 4 1
A getIndexInfo() 0 13 1
A count() 0 8 2
B save() 0 37 4
B createDBRef() 17 22 6
A getDBRef() 0 4 1
B group() 0 32 6
A parallelCollectionScan() 0 4 1
A notImplemented() 0 4 1
A createCollectionObject() 13 13 2
D convertWriteConcernOptions() 0 27 9
C ensureDocumentHasMongoId() 0 30 8
A checkCollectionName() 0 8 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MongoCollection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MongoCollection, and based on these observations, apply Extract Interface, too.

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
use Alcaeus\MongoDbAdapter\Helper;
17
use Alcaeus\MongoDbAdapter\TypeConverter;
18
use Alcaeus\MongoDbAdapter\ExceptionConverter;
19
20
/**
21
 * Represents a database collection.
22
 * @link http://www.php.net/manual/en/class.mongocollection.php
23
 */
24
class MongoCollection
25
{
26
    use Helper\ReadPreference;
27
    use Helper\SlaveOkay;
28
    use Helper\WriteConcern;
29
30
    const ASCENDING = 1;
31
    const DESCENDING = -1;
32
33
    /**
34
     * @var MongoDB
35
     */
36
    public $db = NULL;
37
38
    /**
39
     * @var string
40
     */
41
    protected $name;
42
43
    /**
44
     * @var \MongoDB\Collection
45
     */
46
    protected $collection;
47
48
    /**
49
     * Creates a new collection
50
     *
51
     * @link http://www.php.net/manual/en/mongocollection.construct.php
52
     * @param MongoDB $db Parent database.
53
     * @param string $name Name for this collection.
54
     * @throws Exception
55
     * @return MongoCollection
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
56
     */
57
    public function __construct(MongoDB $db, $name)
58
    {
59
        $this->checkCollectionName($name);
60
        $this->db = $db;
61
        $this->name = $name;
62
63
        $this->setReadPreferenceFromArray($db->getReadPreference());
64
        $this->setWriteConcernFromArray($db->getWriteConcern());
65
66
        $this->createCollectionObject();
67
    }
68
69
    /**
70
     * Gets the underlying collection for this object
71
     *
72
     * @internal This part is not of the ext-mongo API and should not be used
73
     * @return \MongoDB\Collection
74
     */
75
    public function getCollection()
76
    {
77
        return $this->collection;
78
    }
79
80
    /**
81
     * String representation of this collection
82
     *
83
     * @link http://www.php.net/manual/en/mongocollection.--tostring.php
84
     * @return string Returns the full name of this collection.
85
     */
86
    public function __toString()
87
    {
88
        return (string) $this->db . '.' . $this->name;
89
    }
90
91
    /**
92
     * Gets a collection
93
     *
94
     * @link http://www.php.net/manual/en/mongocollection.get.php
95
     * @param string $name The next string in the collection name.
96
     * @return MongoCollection
97
     */
98
    public function __get($name)
99
    {
100
        // Handle w and wtimeout properties that replicate data stored in $readPreference
101
        if ($name === 'w' || $name === 'wtimeout') {
102
            return $this->getWriteConcern()[$name];
103
        }
104
105
        return $this->db->selectCollection($this->name . '.' . $name);
106
    }
107
108
    /**
109
     * @param string $name
110
     * @param mixed $value
111
     */
112
    public function __set($name, $value)
113
    {
114
        if ($name === 'w' || $name === 'wtimeout') {
115
            $this->setWriteConcernFromArray([$name => $value] + $this->getWriteConcern());
116
            $this->createCollectionObject();
117
        }
118
    }
119
120
    /**
121
     * Perform an aggregation using the aggregation framework
122
     *
123
     * @link http://www.php.net/manual/en/mongocollection.aggregate.php
124
     * @param array $pipeline
125
     * @param array $op
126
     * @return array
127
     */
128
    public function aggregate(array $pipeline, array $op = [])
129
    {
130
        if (! TypeConverter::isNumericArray($pipeline)) {
131
            $pipeline = [];
132
            $options = [];
133
134
            $i = 0;
135
            foreach (func_get_args() as $operator) {
136
                $i++;
137
                if (! is_array($operator)) {
138
                    trigger_error("Argument $i is not an array", E_WARNING);
139
                    return;
140
                }
141
142
                $pipeline[] = $operator;
143
            }
144
        } else {
145
            $options = $op;
146
        }
147
148
        if (isset($options['cursor'])) {
149
            $options['useCursor'] = true;
150
151
            if (isset($options['cursor']['batchSize'])) {
152
                $options['batchSize'] = $options['cursor']['batchSize'];
153
            }
154
155
            unset($options['cursor']);
156
        } else {
157
            $options['useCursor'] = false;
158
        }
159
160
        try {
161
            return $this->collection->aggregate(TypeConverter::fromLegacy($pipeline), $options);
162
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
163
            throw ExceptionConverter::toLegacy($e);
164
        }
165
    }
166
167
    /**
168
     * Execute an aggregation pipeline command and retrieve results through a cursor
169
     *
170
     * @link http://php.net/manual/en/mongocollection.aggregatecursor.php
171
     * @param array $pipeline
172
     * @param array $options
173
     * @return MongoCommandCursor
174
     */
175
    public function aggregateCursor(array $pipeline, array $options = [])
176
    {
177
        // Build command manually, can't use mongo-php-library here
178
        $command = [
179
            'aggregate' => $this->name,
180
            'pipeline' => $pipeline
181
        ];
182
183
        // Convert cursor option
184
        if (! isset($options['cursor'])) {
185
            $options['cursor'] = new \stdClass();
186
        }
187
188
        $command += $options;
189
190
        $cursor = new MongoCommandCursor($this->db->getConnection(), (string) $this, $command);
191
        $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...
192
193
        return $cursor;
194
    }
195
196
    /**
197
     * Returns this collection's name
198
     *
199
     * @link http://www.php.net/manual/en/mongocollection.getname.php
200
     * @return string
201
     */
202
    public function getName()
203
    {
204
        return $this->name;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function setReadPreference($readPreference, $tags = null)
211
    {
212
        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
213
        $this->createCollectionObject();
214
215
        return $result;
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function setWriteConcern($wstring, $wtimeout = 0)
222
    {
223
        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
224
        $this->createCollectionObject();
225
226
        return $result;
227
    }
228
229
    /**
230
     * Drops this collection
231
     *
232
     * @link http://www.php.net/manual/en/mongocollection.drop.php
233
     * @return array Returns the database response.
234
     */
235
    public function drop()
236
    {
237
        return TypeConverter::toLegacy($this->collection->drop());
238
    }
239
240
    /**
241
     * Validates this collection
242
     *
243
     * @link http://www.php.net/manual/en/mongocollection.validate.php
244
     * @param bool $scan_data Only validate indices, not the base collection.
245
     * @return array Returns the database's evaluation of this object.
246
     */
247
    public function validate($scan_data = FALSE)
248
    {
249
        $command = [
250
            'validate' => $this->name,
251
            'full'     => $scan_data,
252
        ];
253
254
        return $this->db->command($command);
255
    }
256
257
    /**
258
     * Inserts an array into the collection
259
     *
260
     * @link http://www.php.net/manual/en/mongocollection.insert.php
261
     * @param array|object $a
262
     * @param array $options
263
     * @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.
264
     * @throws MongoCursorException if the "w" option is set and the write fails.
265
     * @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.
266
     * @return bool|array Returns an array containing the status of the insertion if the "w" option is set.
267
     */
268
    public function insert(&$a, array $options = [])
269
    {
270
        if (! $this->ensureDocumentHasMongoId($a)) {
271
            trigger_error(sprintf('%s(): expects parameter %d to be an array or object, %s given', __METHOD__, 1, gettype($a)), E_USER_WARNING);
272
            return;
273
        }
274
275
        if (! count((array)$a)) {
276
            throw new \MongoException('document must be an array or object');
277
        }
278
279
        try {
280
            $result = $this->collection->insertOne(
281
                TypeConverter::fromLegacy($a),
282
                $this->convertWriteConcernOptions($options)
283
            );
284
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
285
            throw ExceptionConverter::toLegacy($e);
286
        }
287
288
        if (! $result->isAcknowledged()) {
289
            return true;
290
        }
291
292
        return [
293
            'ok' => 1.0,
294
            'n' => 0,
295
            'err' => null,
296
            'errmsg' => null,
297
        ];
298
    }
299
300
    /**
301
     * Inserts multiple documents into this collection
302
     *
303
     * @link http://www.php.net/manual/en/mongocollection.batchinsert.php
304
     * @param array $a An array of arrays.
305
     * @param array $options Options for the inserts.
306
     * @throws MongoCursorException
307
     * @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.
308
     */
309
    public function batchInsert(array &$a, array $options = [])
310
    {
311
        if (empty($a)) {
312
            throw new \MongoException('No write ops were included in the batch');
313
        }
314
315
        $continueOnError = isset($options['continueOnError']) && $options['continueOnError'];
316
317
        foreach ($a as $key => $item) {
318
            try {
319
                if (! $this->ensureDocumentHasMongoId($a[$key])) {
320
                    if ($continueOnError) {
321
                        unset($a[$key]);
322
                    } else {
323
                        trigger_error(sprintf('%s expects parameter %d to be an array or object, %s given', __METHOD__, 1, gettype($a)), E_USER_WARNING);
324
                        return;
325
                    }
326
                }
327
            } catch (MongoException $e) {
328
                if ( ! $continueOnError) {
329
                    throw $e;
330
                }
331
            }
332
        }
333
334
        try {
335
            $result = $this->collection->insertMany(
336
                TypeConverter::fromLegacy(array_values($a)),
337
                $this->convertWriteConcernOptions($options)
338
            );
339
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
340
            throw ExceptionConverter::toLegacy($e);
341
        }
342
343
        if (! $result->isAcknowledged()) {
344
            return true;
345
        }
346
347
        return [
348
            'ok' => 1.0,
349
            'connectionId' => 0,
350
            'n' => 0,
351
            'syncMillis' => 0,
352
            'writtenTo' => null,
353
            'err' => null,
354
        ];
355
    }
356
357
    /**
358
     * Update records based on a given criteria
359
     *
360
     * @link http://www.php.net/manual/en/mongocollection.update.php
361
     * @param array $criteria Description of the objects to update.
362
     * @param array $newobj The object with which to update the matching records.
363
     * @param array $options
364
     * @throws MongoCursorException
365
     * @return boolean
366
     */
367
    public function update(array $criteria , array $newobj, array $options = [])
368
    {
369
        $multiple = isset($options['multiple']) ? $options['multiple'] : false;
370
        $method = $multiple ? 'updateMany' : 'updateOne';
371
        unset($options['multiple']);
372
373
        try {
374
            /** @var \MongoDB\UpdateResult $result */
375
            $result = $this->collection->$method(
376
                TypeConverter::fromLegacy($criteria),
377
                TypeConverter::fromLegacy($newobj),
378
                $this->convertWriteConcernOptions($options)
379
            );
380
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
381
            throw ExceptionConverter::toLegacy($e);
382
        }
383
384
        if (! $result->isAcknowledged()) {
385
            return true;
386
        }
387
388
        return [
389
            'ok' => 1.0,
390
            'nModified' => $result->getModifiedCount(),
391
            'n' => $result->getMatchedCount(),
392
            'err' => null,
393
            'errmsg' => null,
394
            'updatedExisting' => $result->getUpsertedCount() == 0,
395
        ];
396
    }
397
398
    /**
399
     * Remove records from this collection
400
     *
401
     * @link http://www.php.net/manual/en/mongocollection.remove.php
402
     * @param array $criteria Query criteria for the documents to delete.
403
     * @param array $options An array of options for the remove operation.
404
     * @throws MongoCursorException
405
     * @throws MongoCursorTimeoutException
406
     * @return bool|array Returns an array containing the status of the removal
407
     * if the "w" option is set. Otherwise, returns TRUE.
408
     */
409
    public function remove(array $criteria = [], array $options = [])
410
    {
411
        $multiple = isset($options['justOne']) ? !$options['justOne'] : true;
412
        $method = $multiple ? 'deleteMany' : 'deleteOne';
413
414
        try {
415
            /** @var \MongoDB\DeleteResult $result */
416
            $result = $this->collection->$method(
417
                TypeConverter::fromLegacy($criteria),
418
                $this->convertWriteConcernOptions($options)
419
            );
420
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
421
            throw ExceptionConverter::toLegacy($e);
422
        }
423
424
        if (! $result->isAcknowledged()) {
425
            return true;
426
        }
427
428
        return [
429
            'ok' => 1.0,
430
            'n' => $result->getDeletedCount(),
431
            'err' => null,
432
            'errmsg' => null
433
        ];
434
    }
435
436
    /**
437
     * Querys this collection
438
     *
439
     * @link http://www.php.net/manual/en/mongocollection.find.php
440
     * @param array $query The fields for which to search.
441
     * @param array $fields Fields of the results to return.
442
     * @return MongoCursor
443
     */
444 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...
445
    {
446
        $cursor = new MongoCursor($this->db->getConnection(), (string) $this, $query, $fields);
447
        $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...
448
449
        return $cursor;
450
    }
451
452
    /**
453
     * Retrieve a list of distinct values for the given key across a collection
454
     *
455
     * @link http://www.php.net/manual/ru/mongocollection.distinct.php
456
     * @param string $key The key to use.
457
     * @param array $query An optional query parameters
458
     * @return array|bool Returns an array of distinct values, or FALSE on failure
459
     */
460
    public function distinct($key, array $query = [])
461
    {
462
        try {
463
            return array_map([TypeConverter::class, 'toLegacy'], $this->collection->distinct($key, $query));
464
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
465
            return false;
466
        }
467
    }
468
469
    /**
470
     * Update a document and return it
471
     *
472
     * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
473
     * @param array $query The query criteria to search for.
474
     * @param array $update The update criteria.
475
     * @param array $fields Optionally only return these fields.
476
     * @param array $options An array of options to apply, such as remove the match document from the DB and return it.
477
     * @return array Returns the original document, or the modified document when new is set.
478
     */
479
    public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
480
    {
481
        $query = TypeConverter::fromLegacy($query);
482
        try {
483
            if (isset($options['remove'])) {
484
                unset($options['remove']);
485
                $document = $this->collection->findOneAndDelete($query, $options);
486
            } else {
487
                $update = is_array($update) ? TypeConverter::fromLegacy($update) : [];
488
489
                if (isset($options['new'])) {
490
                    $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
491
                    unset($options['new']);
492
                }
493
494
                $options['projection'] = is_array($fields) ? TypeConverter::fromLegacy($fields) : [];
495
496
                $document = $this->collection->findOneAndUpdate($query, $update, $options);
497
            }
498
        } catch (\MongoDB\Driver\Exception\ConnectionException $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\ConnectionException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
499
            throw new MongoResultException($e->getMessage(), $e->getCode(), $e);
500
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
501
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
502
        }
503
504
        if ($document) {
505
            $document = TypeConverter::toLegacy($document);
506
        }
507
508
        return $document;
509
    }
510
511
    /**
512
     * Querys this collection, returning a single element
513
     *
514
     * @link http://www.php.net/manual/en/mongocollection.findone.php
515
     * @param array $query The fields for which to search.
516
     * @param array $fields Fields of the results to return.
517
     * @param array $options
518
     * @return array|null
519
     */
520
    public function findOne(array $query = [], array $fields = [], array $options = [])
521
    {
522
        $options = ['projection' => $fields] + $options;
523
        try {
524
            $document = $this->collection->findOne(TypeConverter::fromLegacy($query), $options);
525
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
526
            throw ExceptionConverter::toLegacy($e);
527
        }
528
529
        if ($document !== null) {
530
            $document = TypeConverter::toLegacy($document);
531
        }
532
533
        return $document;
534
    }
535
536
    /**
537
     * Creates an index on the given field(s), or does nothing if the index already exists
538
     *
539
     * @link http://www.php.net/manual/en/mongocollection.createindex.php
540
     * @param array $keys Field or fields to use as index.
541
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
542
     * @return array Returns the database response.
543
     *
544
     * @todo This method does not yet return the correct result
545
     */
546
    public function createIndex($keys, array $options = [])
547
    {
548
        if (is_string($keys)) {
549
            if (empty($keys)) {
550
                throw new MongoException('empty string passed as key field');
551
            }
552
            $keys = [$keys => 1];
553
        }
554
555
        if (is_object($keys)) {
556
            $keys = (array) $keys;
557
        }
558
559
        if (! is_array($keys) || ! count($keys)) {
560
            throw new MongoException('keys cannot be empty');
561
        }
562
563
        // duplicate
564
        $neededOptions = ['unique' => 1, 'sparse' => 1, 'expireAfterSeconds' => 1, 'background' => 1, 'dropDups' => 1];
565
        $indexOptions = array_intersect_key($options, $neededOptions);
566
        $indexes = $this->collection->listIndexes();
567
        foreach ($indexes as $index) {
568
            if (! empty($options['name']) && $index->getName() === $options['name']) {
569
                throw new \MongoResultException(sprintf('index with name: %s already exists', $index->getName()));
570
            }
571
572
            if ($index->getKey() == $keys) {
573
                $currentIndexOptions = array_intersect_key($index->__debugInfo(), $neededOptions);
574
575
                unset($currentIndexOptions['name']);
576
                if ($currentIndexOptions != $indexOptions) {
577
                    throw new \MongoResultException('Index with same keys but different options already exists');
578
                }
579
580
                return [
581
                    'createdCollectionAutomatically' => false,
582
                    'numIndexesBefore' => count($indexes),
583
                    'numIndexesAfter' => count($indexes),
584
                    'note' => 'all indexes already exist',
585
                    'ok' => 1.0
586
                ];
587
            }
588
        }
589
590
        try {
591
            $this->collection->createIndex($keys, $this->convertWriteConcernOptions($options));
592
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
593
            throw ExceptionConverter::toLegacy($e);
594
        }
595
596
        return [
597
            'createdCollectionAutomatically' => true,
598
            'numIndexesBefore' => count($indexes),
599
            'numIndexesAfter' => count($indexes) + 1,
600
            'ok' => 1.0
601
        ];
602
    }
603
604
    /**
605
     * Creates an index on the given field(s), or does nothing if the index already exists
606
     *
607
     * @link http://www.php.net/manual/en/mongocollection.ensureindex.php
608
     * @param array $keys Field or fields to use as index.
609
     * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
610
     * @return boolean always true
611
     * @deprecated Use MongoCollection::createIndex() instead.
612
     */
613
    public function ensureIndex(array $keys, array $options = [])
614
    {
615
        $this->createIndex($keys, $options);
616
617
        return true;
618
    }
619
620
    /**
621
     * Deletes an index from this collection
622
     *
623
     * @link http://www.php.net/manual/en/mongocollection.deleteindex.php
624
     * @param string|array $keys Field or fields from which to delete the index.
625
     * @return array Returns the database response.
626
     */
627
    public function deleteIndex($keys)
628
    {
629
        if (is_string($keys)) {
630
            $indexName = $keys;
631
        } elseif (is_array($keys)) {
632
            $indexName = \MongoDB\generate_index_name($keys);
633
        } else {
634
            throw new \InvalidArgumentException();
635
        }
636
637
        return TypeConverter::toLegacy($this->collection->dropIndex($indexName));
638
    }
639
640
    /**
641
     * Delete all indexes for this collection
642
     *
643
     * @link http://www.php.net/manual/en/mongocollection.deleteindexes.php
644
     * @return array Returns the database response.
645
     */
646
    public function deleteIndexes()
647
    {
648
        return TypeConverter::toLegacy($this->collection->dropIndexes());
649
    }
650
651
    /**
652
     * Returns an array of index names for this collection
653
     *
654
     * @link http://www.php.net/manual/en/mongocollection.getindexinfo.php
655
     * @return array Returns a list of index names.
656
     */
657
    public function getIndexInfo()
658
    {
659
        $convertIndex = function(\MongoDB\Model\IndexInfo $indexInfo) {
660
            return [
661
                'v' => $indexInfo->getVersion(),
662
                'key' => $indexInfo->getKey(),
663
                'name' => $indexInfo->getName(),
664
                'ns' => $indexInfo->getNamespace(),
665
            ];
666
        };
667
668
        return array_map($convertIndex, iterator_to_array($this->collection->listIndexes()));
669
    }
670
671
    /**
672
     * Counts the number of documents in this collection
673
     *
674
     * @link http://www.php.net/manual/en/mongocollection.count.php
675
     * @param array|stdClass $query
676
     * @param array $options
677
     * @return int Returns the number of documents matching the query.
678
     */
679
    public function count($query = [], array $options = [])
680
    {
681
        try {
682
            return $this->collection->count(TypeConverter::fromLegacy($query), $options);
683
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
684
            throw ExceptionConverter::toLegacy($e);
685
        }
686
    }
687
688
    /**
689
     * Saves an object to this collection
690
     *
691
     * @link http://www.php.net/manual/en/mongocollection.save.php
692
     * @param array|object $a Array to save. If an object is used, it may not have protected or private properties.
693
     * @param array $options Options for the save.
694
     * @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.
695
     * @throws MongoCursorException if the "w" option is set and the write fails.
696
     * @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.
697
     * @return array|boolean If w was set, returns an array containing the status of the save.
698
     * Otherwise, returns a boolean representing if the array was not empty (an empty array will not be inserted).
699
     */
700
    public function save(&$a, array $options = [])
701
    {
702
        $id = $this->ensureDocumentHasMongoId($a);
703
704
        $document = (array) $a;
705
706
        $options['upsert'] = true;
707
708
        try {
709
            /** @var \MongoDB\UpdateResult $result */
710
            $result = $this->collection->replaceOne(
711
                TypeConverter::fromLegacy(['_id' => $id]),
712
                TypeConverter::fromLegacy($document),
713
                $this->convertWriteConcernOptions($options)
714
            );
715
716
            if (! $result->isAcknowledged()) {
717
                return true;
718
            }
719
720
            $resultArray = [
721
                'ok' => 1.0,
722
                'nModified' => $result->getModifiedCount(),
723
                'n' => $result->getUpsertedCount() + $result->getModifiedCount(),
724
                'err' => null,
725
                'errmsg' => null,
726
                'updatedExisting' => $result->getUpsertedCount() == 0,
727
            ];
728
            if ($result->getUpsertedId() !== null) {
729
                $resultArray['upserted'] = TypeConverter::toLegacy($result->getUpsertedId());
730
            }
731
732
            return $resultArray;
733
        } catch (\MongoDB\Driver\Exception\Exception $e) {
1 ignored issue
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
734
            throw ExceptionConverter::toLegacy($e);
735
        }
736
    }
737
738
    /**
739
     * Creates a database reference
740
     *
741
     * @link http://www.php.net/manual/en/mongocollection.createdbref.php
742
     * @param array|object $document_or_id Object to which to create a reference.
743
     * @return array Returns a database reference array.
744
     */
745
    public function createDBRef($document_or_id)
746
    {
747 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...
748
            $id = $document_or_id;
749
        } elseif (is_object($document_or_id)) {
750
            if (! isset($document_or_id->_id)) {
751
                return null;
752
            }
753
754
            $id = $document_or_id->_id;
755
        } elseif (is_array($document_or_id)) {
756
            if (! isset($document_or_id['_id'])) {
757
                return null;
758
            }
759
760
            $id = $document_or_id['_id'];
761
        } else {
762
            $id = $document_or_id;
763
        }
764
765
        return MongoDBRef::create($this->name, $id);
766
    }
767
768
    /**
769
     * Fetches the document pointed to by a database reference
770
     *
771
     * @link http://www.php.net/manual/en/mongocollection.getdbref.php
772
     * @param array $ref A database reference.
773
     * @return array Returns the database document pointed to by the reference.
774
     */
775
    public function getDBRef(array $ref)
776
    {
777
        return $this->db->getDBRef($ref);
778
    }
779
780
    /**
781
     * Performs an operation similar to SQL's GROUP BY command
782
     *
783
     * @link http://www.php.net/manual/en/mongocollection.group.php
784
     * @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.
785
     * @param array $initial Initial value of the aggregation counter object.
786
     * @param MongoCode|string $reduce A function that aggregates (reduces) the objects iterated.
787
     * @param array $condition An condition that must be true for a row to be considered.
788
     * @return array
789
     */
790
    public function group($keys, array $initial, $reduce, array $condition = [])
791
    {
792
        if (is_string($reduce)) {
793
            $reduce = new MongoCode($reduce);
794
        }
795
796
        $command = [
797
            'group' => [
798
                'ns' => $this->name,
799
                '$reduce' => (string)$reduce,
800
                'initial' => $initial,
801
                'cond' => $condition,
802
            ],
803
        ];
804
805
        if ($keys instanceof MongoCode) {
806
            $command['group']['$keyf'] = (string)$keys;
807
        } else {
808
            $command['group']['key'] = $keys;
809
        }
810
        if (array_key_exists('condition', $condition)) {
811
            $command['group']['cond'] = $condition['condition'];
812
        }
813
        if (array_key_exists('finalize', $condition)) {
814
            if ($condition['finalize'] instanceof MongoCode) {
815
                $condition['finalize'] = (string)$condition['finalize'];
816
            }
817
            $command['group']['finalize'] = $condition['finalize'];
818
        }
819
820
        return $this->db->command($command);
821
    }
822
823
    /**
824
     * Returns an array of cursors to iterator over a full collection in parallel
825
     *
826
     * @link http://www.php.net/manual/en/mongocollection.parallelcollectionscan.php
827
     * @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.
828
     * @return MongoCommandCursor[]
829
     */
830
    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...
831
    {
832
        $this->notImplemented();
833
    }
834
835
    protected function notImplemented()
836
    {
837
        throw new \Exception('Not implemented');
838
    }
839
840
    /**
841
     * @return \MongoDB\Collection
842
     */
843 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...
844
    {
845
        $options = [
846
            'readPreference' => $this->readPreference,
847
            'writeConcern' => $this->writeConcern,
848
        ];
849
850
        if ($this->collection === null) {
851
            $this->collection = $this->db->getDb()->selectCollection($this->name, $options);
852
        } else {
853
            $this->collection = $this->collection->withOptions($options);
854
        }
855
    }
856
857
    /**
858
     * Converts legacy write concern options to a WriteConcern object
859
     *
860
     * @param array $options
861
     * @return array
862
     */
863
    private function convertWriteConcernOptions(array $options)
864
    {
865
        if (isset($options['safe'])) {
866
            $options['w'] = ($options['safe']) ? 1 : 0;
867
        }
868
869
        if (isset($options['wtimeout']) && !isset($options['wTimeoutMS'])) {
870
            $options['wTimeoutMS'] = $options['wtimeout'];
871
        }
872
873
        if (isset($options['w']) || !isset($options['wTimeoutMS'])) {
874
            $collectionWriteConcern = $this->getWriteConcern();
875
            $writeConcern = $this->createWriteConcernFromParameters(
876
                isset($options['w']) ? $options['w'] : $collectionWriteConcern['w'],
877
                isset($options['wTimeoutMS']) ? $options['wTimeoutMS'] : $collectionWriteConcern['wtimeout']
878
            );
879
880
            $options['writeConcern'] = $writeConcern;
881
        }
882
883
        unset($options['safe']);
884
        unset($options['w']);
885
        unset($options['wTimeout']);
886
        unset($options['wTimeoutMS']);
887
888
        return $options;
889
    }
890
891
    /**
892
     * @param array|object $document
893
     * @return MongoId
894
     */
895
    private function ensureDocumentHasMongoId(&$document)
896
    {
897
        $checkKeys = function($array) {
898
            foreach (array_keys($array) as $key) {
899
                if (empty($key) || strpos($key, '*') === 1) {
900
                    throw new \MongoException('document contain invalid key');
901
                }
902
            }
903
        };
904
905
        if (is_array($document)) {
906
            if (! isset($document['_id'])) {
907
                $document['_id'] = new \MongoId();
908
            }
909
910
            $checkKeys($document);
911
912
            return $document['_id'];
913
        } elseif (is_object($document)) {
914
            if (! isset($document->_id)) {
915
                $document->_id = new \MongoId();
916
            }
917
918
            $checkKeys((array) $document);
919
920
            return $document->_id;
921
        }
922
923
        return null;
924
    }
925
926
    private function checkCollectionName($name)
927
    {
928
        if (empty($name)) {
929
            throw new Exception('Collection name cannot be empty');
930
        } elseif (strpos($name, chr(0)) !== false) {
931
            throw new Exception('Collection name cannot contain null bytes');
932
        }
933
    }
934
}
935
936