MongoDB::createDatabaseObject()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 13
loc 13
rs 9.8333
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('MongoDB', false)) {
17
    return;
18
}
19
20
use Alcaeus\MongoDbAdapter\Helper;
21
use Alcaeus\MongoDbAdapter\TypeConverter;
22
use Alcaeus\MongoDbAdapter\ExceptionConverter;
23
use MongoDB\Model\CollectionInfo;
24
25
/**
26
 * Instances of this class are used to interact with a database.
27
 * @link http://www.php.net/manual/en/class.mongodb.php
28
 */
29
class MongoDB
30
{
31
    use Helper\ReadPreference;
32
    use Helper\SlaveOkay;
33
    use Helper\WriteConcern;
34
35
    const PROFILING_OFF = 0;
36
    const PROFILING_SLOW = 1;
37
    const PROFILING_ON = 2;
38
39
    /**
40
     * @var MongoClient
41
     */
42
    protected $connection;
43
44
    /**
45
     * @var \MongoDB\Database
46
     */
47
    protected $db;
48
49
    /**
50
     * @var string
51
     */
52
    protected $name;
53
54
    /**
55
     * Creates a new database
56
     *
57
     * This method is not meant to be called directly. The preferred way to create an instance of MongoDB is through {@see Mongo::__get()} or {@see Mongo::selectDB()}.
58
     * @link http://www.php.net/manual/en/mongodb.construct.php
59
     * @param MongoClient $conn Database connection.
60
     * @param string $name Database name.
61
     * @throws Exception
62
     */
63
    public function __construct(MongoClient $conn, $name)
64
    {
65
        $this->checkDatabaseName($name);
66
        $this->connection = $conn;
67
        $this->name = (string) $name;
68
69
        $this->setReadPreferenceFromArray($conn->getReadPreference());
70
        $this->setWriteConcernFromArray($conn->getWriteConcern());
71
72
        $this->createDatabaseObject();
73
    }
74
75
    /**
76
     * @return \MongoDB\Database
77
     * @internal This method is not part of the ext-mongo API
78
     */
79
    public function getDb()
80
    {
81
        return $this->db;
82
    }
83
84
    /**
85
     * The name of this database
86
     *
87
     * @link http://www.php.net/manual/en/mongodb.--tostring.php
88
     * @return string Returns this database's name.
89
     */
90
    public function __toString()
91
    {
92
        return $this->name;
93
    }
94
95
    /**
96
     * Gets a collection
97
     *
98
     * @link http://www.php.net/manual/en/mongodb.get.php
99
     * @param string $name The name of the collection.
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->selectCollection($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
            trigger_error("The '{$name}' property is read-only", E_USER_DEPRECATED);
120
        }
121
    }
122
123
    /**
124
     * Returns information about collections in this database
125
     *
126
     * @link http://www.php.net/manual/en/mongodb.getcollectioninfo.php
127
     * @param array $options An array of options for listing the collections.
128
     * @return array
129
     */
130
    public function getCollectionInfo(array $options = [])
131
    {
132
        $includeSystemCollections = false;
133
        // The includeSystemCollections option is no longer supported in the command
134
        if (isset($options['includeSystemCollections'])) {
135
            $includeSystemCollections = $options['includeSystemCollections'];
136
            unset($options['includeSystemCollections']);
137
        }
138
139
        try {
140
            $collections = $this->db->listCollections($options);
141
        } catch (\MongoDB\Driver\Exception\Exception $e) {
142
            throw ExceptionConverter::toLegacy($e);
143
        }
144
145
        $getCollectionInfo = function (CollectionInfo $collectionInfo) {
146
            // @todo do away with __debugInfo once https://jira.mongodb.org/browse/PHPLIB-226 is fixed
147
            $info = $collectionInfo->__debugInfo();
148
149
            return array_filter(
150
                [
151
                    'name' => $collectionInfo->getName(),
152
                    'type' => isset($info['type']) ? $info['type'] : null,
153
                    'options' => $collectionInfo->getOptions(),
154
                    'info' => isset($info['info']) ? (array) $info['info'] : null,
155
                    'idIndex' => isset($info['idIndex']) ? (array) $info['idIndex'] : null,
156
                ],
157
                function ($item) {
158
                    return $item !== null;
159
                }
160
            );
161
        };
162
163
        $eligibleCollections = array_filter(
164
            iterator_to_array($collections),
165
            $this->getSystemCollectionFilterClosure($includeSystemCollections)
166
        );
167
168
        return array_map($getCollectionInfo, $eligibleCollections);
169
    }
170
171
    /**
172
     * Get all collections from this database
173
     *
174
     * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
175
     * @param array $options An array of options for listing the collections.
176
     * @return array Returns the names of the all the collections in the database as an array
177
     */
178
    public function getCollectionNames(array $options = [])
179
    {
180
        $includeSystemCollections = false;
181
        // The includeSystemCollections option is no longer supported in the command
182
        if (isset($options['includeSystemCollections'])) {
183
            $includeSystemCollections = $options['includeSystemCollections'];
184
            unset($options['includeSystemCollections']);
185
        }
186
187
        try {
188
            $collections = $this->db->listCollections($options);
189
        } catch (\MongoDB\Driver\Exception\Exception $e) {
190
            throw ExceptionConverter::toLegacy($e);
191
        }
192
193
        $getCollectionName = function (CollectionInfo $collectionInfo) {
194
            return $collectionInfo->getName();
195
        };
196
197
        $eligibleCollections = array_filter(
198
            iterator_to_array($collections),
199
            $this->getSystemCollectionFilterClosure($includeSystemCollections)
200
        );
201
202
        return array_map($getCollectionName, $eligibleCollections);
203
    }
204
205
    /**
206
     * @return MongoClient
207
     * @internal This method is not part of the ext-mongo API
208
     */
209
    public function getConnection()
210
    {
211
        return $this->connection;
212
    }
213
214
    /**
215
     * Fetches toolkit for dealing with files stored in this database
216
     *
217
     * @link http://www.php.net/manual/en/mongodb.getgridfs.php
218
     * @param string $prefix The prefix for the files and chunks collections.
219
     * @return MongoGridFS Returns a new gridfs object for this database.
220
     */
221
    public function getGridFS($prefix = "fs")
222
    {
223
        return new \MongoGridFS($this, $prefix);
224
    }
225
226
    /**
227
     * Gets this database's profiling level
228
     *
229
     * @link http://www.php.net/manual/en/mongodb.getprofilinglevel.php
230
     * @return int Returns the profiling level.
231
     */
232
    public function getProfilingLevel()
233
    {
234
        $result = $this->command(['profile' => -1]);
235
236
        return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
237
    }
238
239
    /**
240
     * Sets this database's profiling level
241
     *
242
     * @link http://www.php.net/manual/en/mongodb.setprofilinglevel.php
243
     * @param int $level Profiling level.
244
     * @return int Returns the previous profiling level.
245
     */
246
    public function setProfilingLevel($level)
247
    {
248
        $result = $this->command(['profile' => $level]);
249
250
        return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
251
    }
252
253
    /**
254
     * Drops this database
255
     *
256
     * @link http://www.php.net/manual/en/mongodb.drop.php
257
     * @return array Returns the database response.
258
     */
259
    public function drop()
260
    {
261
        return TypeConverter::toLegacy($this->db->drop());
262
    }
263
264
    /**
265
     * Repairs and compacts this database
266
     *
267
     * @link http://www.php.net/manual/en/mongodb.repair.php
268
     * @param bool $preserve_cloned_files [optional] <p>If cloned files should be kept if the repair fails.</p>
269
     * @param bool $backup_original_files [optional] <p>If original files should be backed up.</p>
270
     * @return array <p>Returns db response.</p>
271
     */
272
    public function repair($preserve_cloned_files = false, $backup_original_files = false)
273
    {
274
        $command = [
275
            'repairDatabase' => 1,
276
            'preserveClonedFilesOnFailure' => $preserve_cloned_files,
277
            'backupOriginalFiles' => $backup_original_files,
278
        ];
279
280
        return $this->command($command);
281
    }
282
283
    /**
284
     * Gets a collection
285
     *
286
     * @link http://www.php.net/manual/en/mongodb.selectcollection.php
287
     * @param string $name <b>The collection name.</b>
288
     * @throws Exception if the collection name is invalid.
289
     * @return MongoCollection Returns a new collection object.
290
     */
291
    public function selectCollection($name)
292
    {
293
        return new MongoCollection($this, $name);
294
    }
295
296
    /**
297
     * Creates a collection
298
     *
299
     * @link http://www.php.net/manual/en/mongodb.createcollection.php
300
     * @param string $name The name of the collection.
301
     * @param array $options
302
     * @return MongoCollection Returns a collection object representing the new collection.
303
     */
304
    public function createCollection($name, $options = [])
305
    {
306
        try {
307
            if (isset($options['capped'])) {
308
                $options['capped'] = (bool) $options['capped'];
309
            }
310
311
            $this->db->createCollection($name, $options);
312
        } catch (\MongoDB\Driver\Exception\Exception $e) {
313
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by MongoDB::createCollection of type MongoCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
314
        }
315
316
        return $this->selectCollection($name);
317
    }
318
319
    /**
320
     * Drops a collection
321
     *
322
     * @link http://www.php.net/manual/en/mongodb.dropcollection.php
323
     * @param MongoCollection|string $coll MongoCollection or name of collection to drop.
324
     * @return array Returns the database response.
325
     *
326
     * @deprecated Use MongoCollection::drop() instead.
327
     */
328
    public function dropCollection($coll)
329
    {
330
        if ($coll instanceof MongoCollection) {
331
            $coll = $coll->getName();
332
        }
333
334
        return TypeConverter::toLegacy($this->db->dropCollection((string) $coll));
335
    }
336
337
    /**
338
     * Get a list of collections in this database
339
     *
340
     * @link http://www.php.net/manual/en/mongodb.listcollections.php
341
     * @param array $options
342
     * @return MongoCollection[] Returns a list of MongoCollections.
343
     */
344
    public function listCollections(array $options = [])
345
    {
346
        return array_map([$this, 'selectCollection'], $this->getCollectionNames($options));
347
    }
348
349
    /**
350
     * Creates a database reference
351
     *
352
     * @link http://www.php.net/manual/en/mongodb.createdbref.php
353
     * @param string $collection The collection to which the database reference will point.
354
     * @param mixed $document_or_id
355
     * @return array Returns a database reference array.
356
     */
357
    public function createDBRef($collection, $document_or_id)
358
    {
359 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...
360
            $id = $document_or_id;
361
        } elseif (is_object($document_or_id)) {
362
            if (! isset($document_or_id->_id)) {
363
                $id = $document_or_id;
364
            } else {
365
                $id = $document_or_id->_id;
366
            }
367
        } elseif (is_array($document_or_id)) {
368
            if (! isset($document_or_id['_id'])) {
369
                return null;
370
            }
371
372
            $id = $document_or_id['_id'];
373
        } else {
374
            $id = $document_or_id;
375
        }
376
377
        return MongoDBRef::create($collection, $id);
378
    }
379
380
381
    /**
382
     * Fetches the document pointed to by a database reference
383
     *
384
     * @link http://www.php.net/manual/en/mongodb.getdbref.php
385
     * @param array $ref A database reference.
386
     * @return array Returns the document pointed to by the reference.
387
     */
388
    public function getDBRef(array $ref)
389
    {
390
        $db = (isset($ref['$db']) && $ref['$db'] !== $this->name) ? $this->connection->selectDB($ref['$db']) : $this;
391
        return MongoDBRef::get($db, $ref);
392
    }
393
394
    /**
395
     * Runs JavaScript code on the database server.
396
     *
397
     * @link http://www.php.net/manual/en/mongodb.execute.php
398
     * @param MongoCode|string $code Code to execute.
399
     * @param array $args [optional] Arguments to be passed to code.
400
     * @return array Returns the result of the evaluation.
401
     */
402
    public function execute($code, array $args = [])
403
    {
404
        return $this->command(['eval' => $code, 'args' => $args]);
405
    }
406
407
    /**
408
     * Execute a database command
409
     *
410
     * @link http://www.php.net/manual/en/mongodb.command.php
411
     * @param array $data The query to send.
412
     * @param array $options
413
     * @return array Returns database response.
414
     */
415
    public function command(array $data, $options = [], &$hash = null)
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
Unused Code introduced by
The parameter $hash 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...
416
    {
417
        try {
418
            $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
419
            $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...
420
421
            return iterator_to_array($cursor)[0];
422
        } catch (\MongoDB\Driver\Exception\Exception $e) {
423
            return ExceptionConverter::toResultArray($e);
424
        }
425
    }
426
427
    /**
428
     * Check if there was an error on the most recent db operation performed
429
     *
430
     * @link http://www.php.net/manual/en/mongodb.lasterror.php
431
     * @return array Returns the error, if there was one.
432
     */
433
    public function lastError()
434
    {
435
        return $this->command(array('getLastError' => 1));
436
    }
437
438
    /**
439
     * Checks for the last error thrown during a database operation
440
     *
441
     * @link http://www.php.net/manual/en/mongodb.preverror.php
442
     * @return array Returns the error and the number of operations ago it occurred.
443
     */
444
    public function prevError()
445
    {
446
        return $this->command(array('getPrevError' => 1));
447
    }
448
449
    /**
450
     * Clears any flagged errors on the database
451
     *
452
     * @link http://www.php.net/manual/en/mongodb.reseterror.php
453
     * @return array Returns the database response.
454
     */
455
    public function resetError()
456
    {
457
        return $this->command(array('resetError' => 1));
458
    }
459
460
    /**
461
     * Creates a database error
462
     *
463
     * @link http://www.php.net/manual/en/mongodb.forceerror.php
464
     * @return boolean Returns the database response.
465
     */
466
    public function forceError()
467
    {
468
        return $this->command(array('forceerror' => 1));
469
    }
470
471
    /**
472
     * Log in to this database
473
     *
474
     * @link http://www.php.net/manual/en/mongodb.authenticate.php
475
     * @param string $username The username.
476
     * @param string $password The password (in plaintext).
477
     * @return array Returns database response. If the login was successful, it will return 1.
478
     *
479
     * @deprecated This method is not implemented, supply authentication credentials through the connection string instead.
480
     */
481
    public function authenticate($username, $password)
0 ignored issues
show
Unused Code introduced by
The parameter $username 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...
Unused Code introduced by
The parameter $password 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...
482
    {
483
        throw new \Exception('The MongoDB::authenticate method is not supported. Please supply authentication credentials through the connection string');
484
    }
485
486
    /**
487
     * {@inheritdoc}
488
     */
489
    public function setReadPreference($readPreference, $tags = null)
490
    {
491
        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
492
        $this->createDatabaseObject();
493
494
        return $result;
495
    }
496
497
    /**
498
     * {@inheritdoc}
499
     */
500
    public function setWriteConcern($wstring, $wtimeout = 0)
501
    {
502
        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
503
        $this->createDatabaseObject();
504
505
        return $result;
506
    }
507
508
    protected function notImplemented()
509
    {
510
        throw new \Exception('Not implemented');
511
    }
512
513
    /**
514
     * @return \MongoDB\Database
515
     */
516 View Code Duplication
    private function createDatabaseObject()
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...
517
    {
518
        $options = [
519
            'readPreference' => $this->readPreference,
520
            'writeConcern' => $this->writeConcern,
521
        ];
522
523
        if ($this->db === null) {
524
            $this->db = $this->connection->getClient()->selectDatabase($this->name, $options);
525
        } else {
526
            $this->db = $this->db->withOptions($options);
527
        }
528
    }
529
530
    private function checkDatabaseName($name)
531
    {
532
        if (empty($name)) {
533
            throw new \Exception('Database name cannot be empty');
534
        }
535
        if (strlen($name) >= 64) {
536
            throw new \Exception('Database name cannot exceed 63 characters');
537
        }
538
        if (strpos($name, chr(0)) !== false) {
539
            throw new \Exception('Database name cannot contain null bytes');
540
        }
541
542
        $invalidCharacters = ['.', '$', '/', ' ', '\\'];
543
        foreach ($invalidCharacters as $char) {
544
            if (strchr($name, $char) !== false) {
545
                throw new \Exception('Database name contains invalid characters');
546
            }
547
        }
548
    }
549
550
    /**
551
     * @param bool $includeSystemCollections
552
     * @return Closure
553
     */
554
    private function getSystemCollectionFilterClosure($includeSystemCollections = false)
555
    {
556
        return function (CollectionInfo $collectionInfo) use ($includeSystemCollections) {
557
            return $includeSystemCollections || ! preg_match('#^system\.#', $collectionInfo->getName());
558
        };
559
    }
560
561
    /**
562
     * @return array
563
     */
564
    public function __sleep()
565
    {
566
        return ['connection', 'name'];
567
    }
568
}
569