Completed
Pull Request — master (#63)
by Andreas
21:41
created

MongoDB::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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
use Alcaeus\MongoDbAdapter\Helper;
17
use Alcaeus\MongoDbAdapter\TypeConverter;
18
use Alcaeus\MongoDbAdapter\ExceptionConverter;
19
use MongoDB\Model\CollectionInfo;
20
21
/**
22
 * Instances of this class are used to interact with a database.
23
 * @link http://www.php.net/manual/en/class.mongodb.php
24
 */
25
class MongoDB
26
{
27
    use Helper\ReadPreference;
28
    use Helper\SlaveOkay;
29
    use Helper\WriteConcern;
30
31
    const PROFILING_OFF = 0;
32
    const PROFILING_SLOW = 1;
33
    const PROFILING_ON = 2;
34
35
    /**
36
     * @var MongoClient
37
     */
38
    protected $connection;
39
40
    /**
41
     * @var \MongoDB\Database
42
     */
43
    protected $db;
44
45
    /**
46
     * @var string
47
     */
48
    protected $name;
49
50
    /**
51
     * Creates a new database
52
     *
53
     * 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()}.
54
     * @link http://www.php.net/manual/en/mongodb.construct.php
55
     * @param MongoClient $conn Database connection.
56
     * @param string $name Database name.
57
     * @throws Exception
58
     */
59
    public function __construct(MongoClient $conn, $name)
60
    {
61
        $this->checkDatabaseName($name);
62
        $this->connection = $conn;
63
        $this->name = $name;
64
65
        $this->setReadPreferenceFromArray($conn->getReadPreference());
66
        $this->setWriteConcernFromArray($conn->getWriteConcern());
67
68
        $this->createDatabaseObject();
69
    }
70
71
    /**
72
     * @return \MongoDB\Database
73
     * @internal This method is not part of the ext-mongo API
74
     */
75
    public function getDb()
76
    {
77
        return $this->db;
78
    }
79
80
    /**
81
     * The name of this database
82
     *
83
     * @link http://www.php.net/manual/en/mongodb.--tostring.php
84
     * @return string Returns this database's name.
85
     */
86
    public function __toString()
87
    {
88
        return $this->name;
89
    }
90
91
    /**
92
     * Gets a collection
93
     *
94
     * @link http://www.php.net/manual/en/mongodb.get.php
95
     * @param string $name The name of the collection.
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->selectCollection($name);
106
    }
107
108
    /**
109
     * @param string $name
110
     * @param mixed $value
111
     */
112
    public function __set($name, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $value 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...
113
    {
114
        if ($name === 'w' || $name === 'wtimeout') {
115
            trigger_error("The '{$name}' property is read-only", E_DEPRECATED);
116
        }
117
    }
118
119
    /**
120
     * Returns information about collections in this database
121
     *
122
     * @link http://www.php.net/manual/en/mongodb.getcollectioninfo.php
123
     * @param array $options An array of options for listing the collections.
124
     * @return array
125
     */
126
    public function getCollectionInfo(array $options = [])
127
    {
128
        $includeSystemCollections = false;
129
        // The includeSystemCollections option is no longer supported in the command
130
        if (isset($options['includeSystemCollections'])) {
131
            $includeSystemCollections = $options['includeSystemCollections'];
132
            unset($options['includeSystemCollections']);
133
        }
134
135
        try {
136
            $collections = $this->db->listCollections($options);
137
        } catch (\MongoDB\Driver\Exception\Exception $e) {
138
            throw ExceptionConverter::toLegacy($e);
139
        }
140
141
        $getCollectionInfo = function (CollectionInfo $collectionInfo) {
142
            return [
143
                'name' => $collectionInfo->getName(),
144
                'options' => $collectionInfo->getOptions(),
145
            ];
146
        };
147
148
        $eligibleCollections = array_filter(
149
            iterator_to_array($collections),
150
            $this->getSystemCollectionFilterClosure($includeSystemCollections)
151
        );
152
153
        return array_map($getCollectionInfo, $eligibleCollections);
154
    }
155
156
    /**
157
     * Get all collections from this database
158
     *
159
     * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
160
     * @param array $options An array of options for listing the collections.
161
     * @return array Returns the names of the all the collections in the database as an array
162
     */
163
    public function getCollectionNames(array $options = [])
164
    {
165
        $includeSystemCollections = false;
166
        // The includeSystemCollections option is no longer supported in the command
167
        if (isset($options['includeSystemCollections'])) {
168
            $includeSystemCollections = $options['includeSystemCollections'];
169
            unset($options['includeSystemCollections']);
170
        }
171
172
        try {
173
            $collections = $this->db->listCollections($options);
174
        } catch (\MongoDB\Driver\Exception\Exception $e) {
175
            throw ExceptionConverter::toLegacy($e);
176
        }
177
178
        $getCollectionName = function (CollectionInfo $collectionInfo) {
179
            return $collectionInfo->getName();
180
        };
181
182
        $eligibleCollections = array_filter(
183
            iterator_to_array($collections),
184
            $this->getSystemCollectionFilterClosure($includeSystemCollections)
185
        );
186
187
        return array_map($getCollectionName, $eligibleCollections);
188
    }
189
190
    /**
191
     * @return MongoClient
192
     * @internal This method is not part of the ext-mongo API
193
     */
194
    public function getConnection()
195
    {
196
        return $this->connection;
197
    }
198
199
    /**
200
     * Fetches toolkit for dealing with files stored in this database
201
     *
202
     * @link http://www.php.net/manual/en/mongodb.getgridfs.php
203
     * @param string $prefix The prefix for the files and chunks collections.
204
     * @return MongoGridFS Returns a new gridfs object for this database.
205
     */
206
    public function getGridFS($prefix = "fs")
207
    {
208
        return new \MongoGridFS($this, $prefix);
209
    }
210
211
    /**
212
     * Gets this database's profiling level
213
     *
214
     * @link http://www.php.net/manual/en/mongodb.getprofilinglevel.php
215
     * @return int Returns the profiling level.
216
     */
217
    public function getProfilingLevel()
218
    {
219
        $result = $this->command(['profile' => -1]);
220
221
        return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
222
    }
223
224
    /**
225
     * Sets this database's profiling level
226
     *
227
     * @link http://www.php.net/manual/en/mongodb.setprofilinglevel.php
228
     * @param int $level Profiling level.
229
     * @return int Returns the previous profiling level.
230
     */
231
    public function setProfilingLevel($level)
232
    {
233
        $result = $this->command(['profile' => $level]);
234
235
        return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
236
    }
237
238
    /**
239
     * Drops this database
240
     *
241
     * @link http://www.php.net/manual/en/mongodb.drop.php
242
     * @return array Returns the database response.
243
     */
244
    public function drop()
245
    {
246
        return TypeConverter::toLegacy($this->db->drop());
247
    }
248
249
    /**
250
     * Repairs and compacts this database
251
     *
252
     * @link http://www.php.net/manual/en/mongodb.repair.php
253
     * @param bool $preserve_cloned_files [optional] <p>If cloned files should be kept if the repair fails.</p>
254
     * @param bool $backup_original_files [optional] <p>If original files should be backed up.</p>
255
     * @return array <p>Returns db response.</p>
256
     */
257
    public function repair($preserve_cloned_files = FALSE, $backup_original_files = FALSE)
258
    {
259
        $command = [
260
            'repairDatabase' => 1,
261
            'preserveClonedFilesOnFailure' => $preserve_cloned_files,
262
            'backupOriginalFiles' => $backup_original_files,
263
        ];
264
265
        return $this->command($command);
266
    }
267
268
    /**
269
     * Gets a collection
270
     *
271
     * @link http://www.php.net/manual/en/mongodb.selectcollection.php
272
     * @param string $name <b>The collection name.</b>
273
     * @throws Exception if the collection name is invalid.
274
     * @return MongoCollection Returns a new collection object.
275
     */
276
    public function selectCollection($name)
277
    {
278
        return new MongoCollection($this, $name);
279
    }
280
281
    /**
282
     * Creates a collection
283
     *
284
     * @link http://www.php.net/manual/en/mongodb.createcollection.php
285
     * @param string $name The name of the collection.
286
     * @param array $options
287
     * @return MongoCollection Returns a collection object representing the new collection.
288
     */
289
    public function createCollection($name, $options)
290
    {
291
        try {
292
            if (isset($options['capped'])) {
293
                $options['capped'] = (bool) $options['capped'];
294
            }
295
296
            $this->db->createCollection($name, $options);
297
        } catch (\MongoDB\Driver\Exception\Exception $e) {
298
            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...
299
        }
300
301
        return $this->selectCollection($name);
302
    }
303
304
    /**
305
     * Drops a collection
306
     *
307
     * @link http://www.php.net/manual/en/mongodb.dropcollection.php
308
     * @param MongoCollection|string $coll MongoCollection or name of collection to drop.
309
     * @return array Returns the database response.
310
     *
311
     * @deprecated Use MongoCollection::drop() instead.
312
     */
313
    public function dropCollection($coll)
314
    {
315
        if ($coll instanceof MongoCollection) {
316
            $coll = $coll->getName();
317
        }
318
319
        return TypeConverter::toLegacy($this->db->dropCollection((string) $coll));
320
    }
321
322
    /**
323
     * Get a list of collections in this database
324
     *
325
     * @link http://www.php.net/manual/en/mongodb.listcollections.php
326
     * @param array $options
327
     * @return MongoCollection[] Returns a list of MongoCollections.
328
     */
329
    public function listCollections(array $options = [])
330
    {
331
        return array_map([$this, 'selectCollection'], $this->getCollectionNames($options));
332
    }
333
334
    /**
335
     * Creates a database reference
336
     *
337
     * @link http://www.php.net/manual/en/mongodb.createdbref.php
338
     * @param string $collection The collection to which the database reference will point.
339
     * @param mixed $document_or_id
340
     * @return array Returns a database reference array.
341
     */
342
    public function createDBRef($collection, $document_or_id)
343
    {
344 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...
345
            $id = $document_or_id;
346
        } elseif (is_object($document_or_id)) {
347
            if (! isset($document_or_id->_id)) {
348
                $id = $document_or_id;
349
            } else {
350
                $id = $document_or_id->_id;
351
            }
352
        } elseif (is_array($document_or_id)) {
353
            if (! isset($document_or_id['_id'])) {
354
                return null;
355
            }
356
357
            $id = $document_or_id['_id'];
358
        } else {
359
            $id = $document_or_id;
360
        }
361
362
        return MongoDBRef::create($collection, $id);
363
    }
364
365
366
    /**
367
     * Fetches the document pointed to by a database reference
368
     *
369
     * @link http://www.php.net/manual/en/mongodb.getdbref.php
370
     * @param array $ref A database reference.
371
     * @return array Returns the document pointed to by the reference.
372
     */
373
    public function getDBRef(array $ref)
374
    {
375
        return MongoDBRef::get($this, $ref);
376
    }
377
378
    /**
379
     * Runs JavaScript code on the database server.
380
     *
381
     * @link http://www.php.net/manual/en/mongodb.execute.php
382
     * @param MongoCode|string $code Code to execute.
383
     * @param array $args [optional] Arguments to be passed to code.
384
     * @return array Returns the result of the evaluation.
385
     */
386
    public function execute($code, array $args = [])
387
    {
388
        return $this->command(['eval' => $code, 'args' => $args]);
389
    }
390
391
    /**
392
     * Execute a database command
393
     *
394
     * @link http://www.php.net/manual/en/mongodb.command.php
395
     * @param array $data The query to send.
396
     * @param array $options
397
     * @return array Returns database response.
398
     */
399
    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...
400
    {
401
        try {
402
            $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
403
            $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...
404
405
            return iterator_to_array($cursor)[0];
406
        } catch (\MongoDB\Driver\Exception\Exception $e) {
407
            return ExceptionConverter::toResultArray($e);
408
        }
409
    }
410
411
    /**
412
     * Check if there was an error on the most recent db operation performed
413
     *
414
     * @link http://www.php.net/manual/en/mongodb.lasterror.php
415
     * @return array Returns the error, if there was one.
416
     */
417
    public function lastError()
418
    {
419
        return $this->command(array('getLastError' => 1));
420
    }
421
422
    /**
423
     * Checks for the last error thrown during a database operation
424
     *
425
     * @link http://www.php.net/manual/en/mongodb.preverror.php
426
     * @return array Returns the error and the number of operations ago it occurred.
427
     */
428
    public function prevError()
429
    {
430
        return $this->command(array('getPrevError' => 1));
431
    }
432
433
    /**
434
     * Clears any flagged errors on the database
435
     *
436
     * @link http://www.php.net/manual/en/mongodb.reseterror.php
437
     * @return array Returns the database response.
438
     */
439
    public function resetError()
440
    {
441
        return $this->command(array('resetError' => 1));
442
    }
443
444
    /**
445
     * Creates a database error
446
     *
447
     * @link http://www.php.net/manual/en/mongodb.forceerror.php
448
     * @return boolean Returns the database response.
449
     */
450
    public function forceError()
451
    {
452
        return $this->command(array('forceerror' => 1));
453
    }
454
455
    /**
456
     * Log in to this database
457
     *
458
     * @link http://www.php.net/manual/en/mongodb.authenticate.php
459
     * @param string $username The username.
460
     * @param string $password The password (in plaintext).
461
     * @return array Returns database response. If the login was successful, it will return 1.
462
     *
463
     * @deprecated This method is not implemented, supply authentication credentials through the connection string instead.
464
     */
465
    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...
466
    {
467
        throw new \Exception('The MongoDB::authenticate method is not supported. Please supply authentication credentials through the connection string');
468
    }
469
470
    /**
471
     * {@inheritdoc}
472
     */
473
    public function setReadPreference($readPreference, $tags = null)
474
    {
475
        $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
476
        $this->createDatabaseObject();
477
478
        return $result;
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484
    public function setWriteConcern($wstring, $wtimeout = 0)
485
    {
486
        $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
487
        $this->createDatabaseObject();
488
489
        return $result;
490
    }
491
492
    protected function notImplemented()
493
    {
494
        throw new \Exception('Not implemented');
495
    }
496
497
    /**
498
     * @return \MongoDB\Database
499
     */
500 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...
501
    {
502
        $options = [
503
            'readPreference' => $this->readPreference,
504
            'writeConcern' => $this->writeConcern,
505
        ];
506
507
        if ($this->db === null) {
508
            $this->db = $this->connection->getClient()->selectDatabase($this->name, $options);
509
        } else {
510
            $this->db = $this->db->withOptions($options);
511
        }
512
    }
513
514
    private function checkDatabaseName($name)
515
    {
516
        if (empty($name)) {
517
            throw new \Exception('Database name cannot be empty');
518
        }
519
        if (strlen($name) >= 64) {
520
            throw new \Exception('Database name cannot exceed 63 characters');
521
        }
522
        if (strpos($name, chr(0)) !== false) {
523
            throw new \Exception('Database name cannot contain null bytes');
524
        }
525
526
        $invalidCharacters = ['.', '$', '/', ' ', '\\'];
527
        foreach ($invalidCharacters as $char) {
528
            if (strchr($name, $char) !== false) {
529
                throw new \Exception('Database name contains invalid characters');
530
            }
531
        }
532
533
    }
534
535
    /**
536
     * @param bool $includeSystemCollections
537
     * @return Closure
538
     */
539
    private function getSystemCollectionFilterClosure($includeSystemCollections = false) {
540
        return function (CollectionInfo $collectionInfo) use ($includeSystemCollections) {
541
            return $includeSystemCollections || ! preg_match('#^system\.#', $collectionInfo->getName());
542
        };
543
    }
544
545
    /**
546
     * @return array
547
     */
548
    public function __sleep()
549
    {
550
        return ['connection', 'name'];
551
    }
552
}
553