Completed
Pull Request — master (#210)
by
unknown
02:52
created

MongoClient::__construct()   C

Complexity

Conditions 10
Paths 128

Size

Total Lines 53
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 31
nc 128
nop 3
dl 0
loc 53
rs 6.08
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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('MongoClient', false)) {
17
    return;
18
}
19
20
use Alcaeus\MongoDbAdapter\Helper;
21
use Alcaeus\MongoDbAdapter\ExceptionConverter;
22
use MongoDB\Client;
23
24
/**
25
 * A connection between PHP and MongoDB. This class is used to create and manage connections
26
 * See MongoClient::__construct() and the section on connecting for more information about creating connections.
27
 * @link http://www.php.net/manual/en/class.mongoclient.php
28
 */
29
class MongoClient
30
{
31
    use Helper\ReadPreference;
32
    use Helper\WriteConcern;
33
34
    const VERSION = '1.6.12';
35
    const DEFAULT_HOST = "localhost";
36
    const DEFAULT_PORT = 27017;
37
    const RP_PRIMARY = "primary";
38
    const RP_PRIMARY_PREFERRED = "primaryPreferred";
39
    const RP_SECONDARY = "secondary";
40
    const RP_SECONDARY_PREFERRED = "secondaryPreferred";
41
    const RP_NEAREST = "nearest";
42
43
    /**
44
     * @var bool
45
     * @deprecated This will not properly work as the underlying driver connects lazily
46
     */
47
    public $connected = false;
48
49
    /**
50
     * @var
51
     */
52
    public $status;
53
54
    /**
55
     * @var string
56
     */
57
    protected $server;
58
59
    /**
60
     * @var
61
     */
62
    protected $persistent;
63
64
    /**
65
     * @var Client
66
     */
67
    private $client;
68
69
    /**
70
     * @var \MongoDB\Driver\Manager
71
     */
72
    private $manager;
73
74
    /**
75
     * Creates a new database connection object
76
     *
77
     * @link http://php.net/manual/en/mongo.construct.php
78
     * @param string $server The server name.
79
     * @param array $options An array of options for the connection.
80
     * @param array $driverOptions An array of options for the MongoDB driver.
81
     * @throws MongoConnectionException
82
     */
83
    public function __construct($server = 'default', array $options = ['connect' => true], array $driverOptions = [])
84
    {
85
        $username = $options['username'];
86
        $password = $options['password'];
87
        $db = $options['db'];
88
89
        unset($options['username']);
90
        unset($options['password']);
91
        unset($options['db']);
92
93
        $replicaSet = null;
94
95
        if (isset($options['replicaSet']) && !empty($options['replicaSet'])) {
96
            $replicaSet = $options['replicaSet'];
97
        }
98
99
        if (isset($options['replicaSet'])) {
100
            unset($options['replicaSet']);
101
        }
102
103
        $options['serverSelectionTimeoutMS'] = 60000;
104
        $options['serverSelectionTryOnce'] = false;
105
106
        if ($server === 'default') {
107
            $server = 'mongodb://' . $username . ':' . $password . '@' . self::DEFAULT_HOST . ':' . self::DEFAULT_PORT . '/' . $db;
108
        }
109
110
        if (isset($options['readPreferenceTags'])) {
111
            $options['readPreferenceTags'] = [$this->getReadPreferenceTags($options['readPreferenceTags'])];
112
        }
113
114
        $this->applyConnectionOptions($server, $options);
115
116
        $this->server = $server;
117
118
        if (false === strpos($this->server, 'mongodb://')) {
119
            $this->server = 'mongodb://' . $username . ':' . $password . '@' . $this->server . '/' . $db;
120
        } else {
121
            $this->server = str_replace('mongodb://', 'mongodb://' . $username . ':' . $password . '@', $this->server) . '/' . $db;
122
        }
123
124
        if ($replicaSet) {
125
            $this->server .= '?replicaSet=' . $replicaSet;
126
        }
127
128
        $this->client = new Client($this->server, $options, $driverOptions);
129
        $info = $this->client->__debugInfo();
130
        $this->manager = $info['manager'];
131
132
        if (isset($options['connect']) && $options['connect']) {
133
            $this->connect();
134
        }
135
    }
136
137
138
    /**
139
     * Closes this database connection
140
     *
141
     * @link http://www.php.net/manual/en/mongoclient.close.php
142
     * @param  boolean|string $connection
143
     * @return boolean If the connection was successfully closed.
144
     */
145
    public function close($connection = null)
0 ignored issues
show
Unused Code introduced by
The parameter $connection 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...
146
    {
147
        $this->connected = false;
148
149
        return false;
150
    }
151
152
    /**
153
     * Connects to a database server
154
     *
155
     * @link http://www.php.net/manual/en/mongoclient.connect.php
156
     *
157
     * @throws MongoConnectionException
158
     * @return boolean If the connection was successful.
159
     */
160
    public function connect()
161
    {
162
        $this->connected = true;
0 ignored issues
show
Deprecated Code introduced by
The property MongoClient::$connected has been deprecated with message: This will not properly work as the underlying driver connects lazily

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
163
164
        return true;
165
    }
166
167
    /**
168
     * Drops a database
169
     *
170
     * @link http://www.php.net/manual/en/mongoclient.dropdb.php
171
     * @param mixed $db The database to drop. Can be a MongoDB object or the name of the database.
172
     * @return array The database response.
173
     * @deprecated Use MongoDB::drop() instead.
174
     */
175
    public function dropDB($db)
176
    {
177
        return $this->selectDB($db)->drop();
178
    }
179
180
    /**
181
     * Gets a database
182
     *
183
     * @link http://php.net/manual/en/mongoclient.get.php
184
     * @param string $dbname The database name.
185
     * @return MongoDB The database name.
186
     */
187
    public function __get($dbname)
188
    {
189
        return $this->selectDB($dbname);
190
    }
191
192
    /**
193
     * Gets the client for this object
194
     *
195
     * @internal This part is not of the ext-mongo API and should not be used
196
     * @return Client
197
     */
198
    public function getClient()
199
    {
200
        return $this->client;
201
    }
202
203
    /**
204
     * Get connections
205
     *
206
     * Returns an array of all open connections, and information about each of the servers
207
     *
208
     * @return array
209
     */
210
    public static function getConnections()
211
    {
212
        return [];
213
    }
214
215
    /**
216
     * Get hosts
217
     *
218
     * This method is only useful with a connection to a replica set. It returns the status of all of the hosts in the
219
     * set. Without a replica set, it will just return an array with one element containing the host that you are
220
     * connected to.
221
     *
222
     * @return array
223
     */
224
    public function getHosts()
225
    {
226
        $this->forceConnect();
227
228
        $results = [];
229
230
        try {
231
            $servers = $this->manager->getServers();
232
        } catch (\MongoDB\Driver\Exception\Exception $e) {
233
            throw ExceptionConverter::toLegacy($e);
234
        }
235
236
        foreach ($servers as $server) {
237
            $key = sprintf('%s:%d;-;.;%d', $server->getHost(), $server->getPort(), getmypid());
238
            $info = $server->getInfo();
239
240
            switch ($server->getType()) {
241
                case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
242
                    $state = 1;
243
                    break;
244
                case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
245
                    $state = 2;
246
                    break;
247
                default:
248
                    $state = 0;
249
            }
250
251
            $results[$key] = [
252
                'host' => $server->getHost(),
253
                'port' => $server->getPort(),
254
                'health' => (int)$info['ok'],
255
                'state' => $state,
256
                'ping' => $server->getLatency(),
257
                'lastPing' => null,
258
            ];
259
        }
260
261
        return $results;
262
    }
263
264
    /**
265
     * Kills a specific cursor on the server
266
     *
267
     * @link http://www.php.net/manual/en/mongoclient.killcursor.php
268
     * @param string $server_hash The server hash that has the cursor.
269
     * @param int|MongoInt64 $id The ID of the cursor to kill.
270
     * @return bool
271
     */
272
    public function killCursor($server_hash, $id)
0 ignored issues
show
Unused Code introduced by
The parameter $server_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...
Unused Code introduced by
The parameter $id 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...
273
    {
274
        $this->notImplemented();
275
    }
276
277
    /**
278
     * Lists all of the databases available
279
     *
280
     * @link http://php.net/manual/en/mongoclient.listdbs.php
281
     * @return array Returns an associative array containing three fields. The first field is databases, which in turn contains an array. Each element of the array is an associative array corresponding to a database, giving the database's name, size, and if it's empty. The other two fields are totalSize (in bytes) and ok, which is 1 if this method ran successfully.
282
     */
283
    public function listDBs()
284
    {
285
        try {
286
            $databaseInfoIterator = $this->client->listDatabases();
287
        } catch (\MongoDB\Driver\Exception\Exception $e) {
288
            throw ExceptionConverter::toLegacy($e);
289
        }
290
291
        $databases = [
292
            'databases' => [],
293
            'totalSize' => 0,
294
            'ok' => 1.0,
295
        ];
296
297
        foreach ($databaseInfoIterator as $databaseInfo) {
298
            $databases['databases'][] = [
299
                'name' => $databaseInfo->getName(),
300
                'empty' => $databaseInfo->isEmpty(),
301
                'sizeOnDisk' => $databaseInfo->getSizeOnDisk(),
302
            ];
303
            $databases['totalSize'] += $databaseInfo->getSizeOnDisk();
304
        }
305
306
        return $databases;
307
    }
308
309
    /**
310
     * Gets a database collection
311
     *
312
     * @link http://www.php.net/manual/en/mongoclient.selectcollection.php
313
     * @param string $db The database name.
314
     * @param string $collection The collection name.
315
     * @return MongoCollection Returns a new collection object.
316
     * @throws Exception Throws Exception if the database or collection name is invalid.
317
     */
318
    public function selectCollection($db, $collection)
319
    {
320
        return new MongoCollection($this->selectDB($db), $collection);
321
    }
322
323
    /**
324
     * Gets a database
325
     *
326
     * @link http://www.php.net/manual/en/mongo.selectdb.php
327
     * @param string $name The database name.
328
     * @return MongoDB Returns a new db object.
329
     * @throws InvalidArgumentException
330
     */
331
    public function selectDB($name)
332
    {
333
        return new MongoDB($this, $name);
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function setReadPreference($readPreference, $tags = null)
340
    {
341
        return $this->setReadPreferenceFromParameters($readPreference, $tags);
342
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347
    public function setWriteConcern($wstring, $wtimeout = 0)
348
    {
349
        return $this->setWriteConcernFromParameters($wstring, $wtimeout);
350
    }
351
352
    /**
353
     * String representation of this connection
354
     *
355
     * @link http://www.php.net/manual/en/mongoclient.tostring.php
356
     * @return string Returns hostname and port for this connection.
357
     */
358
    public function __toString()
359
    {
360
        return $this->server;
361
    }
362
363
    /**
364
     * Forces a connection by executing the ping command
365
     */
366
    private function forceConnect()
367
    {
368
        try {
369
            $command = new \MongoDB\Driver\Command(['ping' => 1]);
370
            $this->manager->executeCommand('db', $command);
371
        } catch (\MongoDB\Driver\Exception\Exception $e) {
372
            throw ExceptionConverter::toLegacy($e);
373
        }
374
    }
375
376
    private function notImplemented()
377
    {
378
        throw new \Exception('Not implemented');
379
    }
380
381
    /**
382
     * @return array
383
     */
384
    function __sleep()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
385
    {
386
        return [
387
            'connected', 'status', 'server', 'persistent'
388
        ];
389
    }
390
391
    /**
392
     * @param $server
393
     * @return array
394
     */
395
    private function extractUrlOptions($server)
396
    {
397
        $queryOptions = explode('&', parse_url($server, PHP_URL_QUERY));
398
399
        $options = [];
400
        foreach ($queryOptions as $option) {
401
            if (strpos($option, '=') === false) {
402
                continue;
403
            }
404
405
            $keyValue = explode('=', $option);
406
            if ($keyValue[0] === 'readPreferenceTags') {
407
                $options[$keyValue[0]][] = $this->getReadPreferenceTags($keyValue[1]);
408
            } else {
409
                $options[$keyValue[0]] = $keyValue[1];
410
            }
411
        }
412
413
        return $options;
414
    }
415
416
    /**
417
     * @param $readPreferenceTagString
418
     * @return array
419
     */
420
    private function getReadPreferenceTags($readPreferenceTagString)
421
    {
422
        $tagSets = [];
423
        foreach (explode(',', $readPreferenceTagString) as $index => $tagSet) {
424
            $tags = explode(':', $tagSet);
425
            $tagSets[$tags[0]] = $tags[1];
426
        }
427
428
        return $tagSets;
429
    }
430
431
    /**
432
     * @param string $server
433
     * @param array $options
434
     */
435
    private function applyConnectionOptions($server, array $options)
436
    {
437
        $urlOptions = $this->extractUrlOptions($server);
438
439
        if (isset($urlOptions['wTimeout'])) {
440
            $urlOptions['wTimeoutMS'] = $urlOptions['wTimeout'];
441
            unset($urlOptions['wTimeout']);
442
        }
443
444 View Code Duplication
        if (isset($options['wTimeout'])) {
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...
445
            $options['wTimeoutMS'] = $options['wTimeout'];
446
            unset($options['wTimeout']);
447
        }
448
449
        // Special handling for readPreferenceTags which are merged
450
        if (isset($options['readPreferenceTags']) && isset($urlOptions['readPreferenceTags'])) {
451
            $options['readPreferenceTags'] = array_merge($urlOptions['readPreferenceTags'], $options['readPreferenceTags']);
452
            unset($urlOptions['readPreferenceTags']);
453
        }
454
455
        $urlOptions = array_merge($urlOptions, $options);
456
457
        if (isset($urlOptions['slaveOkay'])) {
458
            $this->setReadPreferenceFromSlaveOkay($urlOptions['slaveOkay']);
459
        } elseif (isset($urlOptions['readPreference']) || isset($urlOptions['readPreferenceTags'])) {
460
            $readPreference = isset($urlOptions['readPreference']) ? $urlOptions['readPreference'] : null;
461
            $tags = isset($urlOptions['readPreferenceTags']) ? $urlOptions['readPreferenceTags'] : null;
462
            $this->setReadPreferenceFromParameters($readPreference, $tags);
463
        }
464
465
        if (isset($urlOptions['w']) || isset($urlOptions['wTimeoutMs'])) {
466
            $writeConcern = (isset($urlOptions['w'])) ? $urlOptions['w'] : 1;
467
            $wTimeout = (isset($urlOptions['wTimeoutMs'])) ? $urlOptions['wTimeoutMs'] : null;
468
            $this->setWriteConcern($writeConcern, $wTimeout);
469
        }
470
    }
471
}
472