Passed
Push — main ( ca0bbc...018c78 )
by Andreas
01:42
created

PDO::__construct()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 53
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 20.2062

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 29
c 1
b 0
f 0
nc 16
nop 4
dl 0
loc 53
ccs 14
cts 29
cp 0.4828
crap 20.2062
rs 8.0555

How to fix   Long Method   

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
 * Licensed to CRATE Technology GmbH("Crate") under one or more contributor
4
 * license agreements.  See the NOTICE file distributed with this work for
5
 * additional information regarding copyright ownership.  Crate licenses
6
 * this file to you under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.  You may
8
 * obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
15
 * License for the specific language governing permissions and limitations
16
 * under the License.
17
 *
18
 * However, if you have executed another commercial license agreement
19
 * with Crate these terms will supersede the license and you may use the
20
 * software solely pursuant to the terms of the relevant commercial agreement.
21
 */
22
23
declare(strict_types=1);
24
25
namespace Crate\PDO;
26
27
use Crate\PDO\Exception\InvalidArgumentException;
28
use Crate\PDO\Exception\PDOException;
29
use Crate\PDO\Http\ServerInterface;
30
use Crate\PDO\Http\ServerPool;
31
use Crate\Stdlib\ArrayUtils;
32
use PDO as BasePDO;
33
34
use const PHP_VERSION_ID;
35
36
class PDO extends BasePDO implements PDOInterface
37
{
38
    use PDOImplementation;
39
40
    public const VERSION     = '2.1.4';
41
    public const DRIVER_NAME = 'crate';
42
43
    public const DSN_REGEX = '/^(?:crate:)(?:((?:[\w\.-]+:\d+\,?)+))\/?([\w]+)?$/';
44
45
    public const CRATE_ATTR_HTTP_BASIC_AUTH = 1000;
46
    public const CRATE_ATTR_DEFAULT_SCHEMA  = 1001;
47
48
    public const CRATE_ATTR_SSL_MODE                                       = 1008;
49
    public const CRATE_ATTR_SSL_MODE_DISABLED                              = 1;
50
    public const CRATE_ATTR_SSL_MODE_ENABLED_BUT_WITHOUT_HOST_VERIFICATION = 2;
51
    public const CRATE_ATTR_SSL_MODE_REQUIRED                              = 3;
52
53
    public const CRATE_ATTR_SSL_KEY_PATH      = 1002;
54
    public const CRATE_ATTR_SSL_KEY_PASSWORD  = 1003;
55
    public const CRATE_ATTR_SSL_CERT_PATH     = 1004;
56
    public const CRATE_ATTR_SSL_CERT_PASSWORD = 1005;
57
    public const CRATE_ATTR_SSL_CA_PATH       = 1006;
58
    public const CRATE_ATTR_SSL_CA_PASSWORD   = 1007;
59
60
    public const PARAM_FLOAT     = 6;
61
    public const PARAM_DOUBLE    = 7;
62
    public const PARAM_LONG      = 8;
63
    public const PARAM_ARRAY     = 9;
64
    public const PARAM_OBJECT    = 10;
65
    public const PARAM_TIMESTAMP = 11;
66
    public const PARAM_IP        = 12;
67
68
    /**
69
     * @var array
70
     */
71
    private $attributes = [
72
        'defaultFetchMode' => self::FETCH_BOTH,
73
        'errorMode'        => self::ERRMODE_SILENT,
74
        'sslMode'          => self::CRATE_ATTR_SSL_MODE_DISABLED,
75
        'statementClass'   => PDOStatement::class,
76
        'timeout'          => 0.0,
77
        'auth'             => [],
78
        'defaultSchema'    => 'doc',
79
        'bulkMode'         => false,
80
    ];
81
82
    /**
83
     * @var Http\ServerInterface
84
     */
85
    private $server;
86
87
    /**
88
     * @var PDOStatement|null
89
     */
90
    private $lastStatement;
91
92
    /**
93
     * @var callable
94
     */
95
    private $request;
96
97
    /**
98
     * {@inheritDoc}
99
     *
100
     * @param string     $dsn      The HTTP endpoint to call
101
     * @param null       $username Username for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $username is correct as it would always require null to be passed?
Loading history...
102
     * @param null       $passwd   Password for basic auth
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $passwd is correct as it would always require null to be passed?
Loading history...
103
     * @param null|array $options  Attributes to set on the PDO
104
     */
105 34
    public function __construct($dsn, $username = null, $passwd = null, $options = [])
106
    {
107
108 34
        if (PHP_VERSION_ID < 80000) {
109
            trigger_error(
110
                "`crate/crate-pdo` will stop supporting PHP7 on one of the upcoming " .
111
                "releases. Please upgrade to PHP8.",
112
                E_USER_DEPRECATED
113
            );
114
        }
115
116 34
        $dsnParts = self::parseDSN($dsn);
117 34
        $servers  = self::serversFromDsnParts($dsnParts);
118
119 34
        $this->setServer(new ServerPool($servers));
120
121 34
        foreach ((array)$options as $attribute => $value) {
122 2
            $this->setAttribute($attribute, $value);
123
        }
124
125 34
        if (!empty($username)) {
126 4
            $this->setAttribute(self::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
127
        }
128
129 34
        if (!empty($dsnParts[1])) {
130
            $this->setAttribute(self::CRATE_ATTR_DEFAULT_SCHEMA, $dsnParts[1]);
131
        }
132
133
        // Define a callback that will be used in the PDOStatements
134
        // This way we don't expose this as a public api to the end users.
135 34
        $this->request = function (PDOStatement $statement, $sql, array $parameters) {
136
137 2
            $this->lastStatement = $statement;
138
139
            try {
140 2
                if ($statement->isBulkMode()) {
141
                    return $this->server->executeBulk($sql, $parameters);
142
                } else {
143 2
                    return $this->server->execute($sql, $parameters);
144
                }
145
            } catch (Exception\RuntimeException $e) {
146
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_EXCEPTION) {
147
                    throw new Exception\PDOException($e->getMessage(), $e->getCode());
148
                }
149
150
                if ($this->getAttribute(self::ATTR_ERRMODE) === self::ERRMODE_WARNING) {
151
                    trigger_error(sprintf('[%d] %s', $e->getCode(), $e->getMessage()), E_USER_WARNING);
152
                }
153
154
                // should probably wrap this in a error object ?
155
                return [
156
                    'code'    => $e->getCode(),
157
                    'message' => $e->getMessage(),
158
                ];
159
            }
160 34
        };
161
    }
162
163
    /**
164
     * Change the server implementation
165
     *
166
     * @param ServerInterface $server
167
     */
168 28
    public function setServer(ServerInterface $server): void
169
    {
170 28
        $this->server = $server;
171 28
        $this->server->configure($this);
172
    }
173
174
    /**
175
     * Extract servers and optional custom schema from DSN string
176
     *
177
     * @param string $dsn The DSN string
178
     *
179
     * @throws \Crate\PDO\Exception\PDOException on an invalid DSN string
180
     *
181
     * @return array An array of ['host:post,host:port,...', 'schema']
182
     */
183 110
    private static function parseDSN($dsn)
184
    {
185 110
        $matches = [];
186
187 110
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
188 8
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
189
        }
190
191 102
        return array_slice($matches, 1);
192
    }
193
194
    /**
195
     * Extract host:port pairs out of the DSN parts
196
     *
197
     * @param array $dsnParts The parts of the parsed DSN string
198
     *
199
     * @return array An array of host:port strings
200
     */
201 100
    private static function serversFromDsnParts($dsnParts)
202
    {
203 100
        return explode(',', trim($dsnParts[0], ','));
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 4
    public function prepare($statement, $options = null)
210
    {
211 4
        $options = ArrayUtils::toArray($options);
212
213 4
        if (isset($options[self::ATTR_CURSOR])) {
214
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
215
216
            return true;
217
        }
218
219 4
        $className = $this->attributes['statementClass'];
220
221 4
        return new $className($this, $this->request, $statement, $options);
222
    }
223
224
    /**
225
     * {@inheritDoc}
226
     */
227 2
    public function beginTransaction()
228
    {
229 2
        return true;
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     */
235 2
    public function commit()
236
    {
237 2
        return true;
238
    }
239
240
    /**
241
     * {@inheritDoc}
242
     */
243 2
    public function rollBack()
244
    {
245 2
        throw new Exception\UnsupportedException;
246
    }
247
248
    /**
249
     * {@inheritDoc}
250
     */
251 2
    public function inTransaction()
252
    {
253 2
        return false;
254
    }
255
256
    /**
257
     * {@inheritDoc}
258
     */
259
    public function exec($statement)
260
    {
261
        $statement = $this->prepare($statement);
262
        $result    = $statement->execute();
263
264
        return $result === false ? false : $statement->rowCount();
265
    }
266
267
    /**
268
     * {@inheritDoc}
269
     */
270 2
    public function doQuery($statement)
271
    {
272 2
        $statement = $this->prepare($statement);
273 2
        $result    = $statement->execute();
274
275 2
        return $result === false ? false : $statement;
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281 2
    public function lastInsertId($name = null)
282
    {
283 2
        throw new Exception\UnsupportedException;
284
    }
285
286
    /**
287
     * {@inheritDoc}
288
     */
289
    public function errorCode()
290
    {
291
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
292
    }
293
294
    /**
295
     * {@inheritDoc}
296
     */
297
    public function errorInfo()
298
    {
299
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     *
305
     * @throws \Crate\PDO\Exception\PDOException
306
     * @throws \Crate\PDO\Exception\InvalidArgumentException
307
     */
308 32
    public function setAttribute($attribute, $value)
309
    {
310
        switch ($attribute) {
311 32
            case self::ATTR_DEFAULT_FETCH_MODE:
312 2
                $this->attributes['defaultFetchMode'] = $value;
313 2
                break;
314
315 30
            case self::ATTR_ERRMODE:
316 2
                $this->attributes['errorMode'] = $value;
317 2
                break;
318
319 28
            case self::ATTR_STATEMENT_CLASS:
320 2
                $this->attributes['statementClass'] = $value;
321 2
                break;
322
323 26
            case self::ATTR_TIMEOUT:
324 4
                $this->attributes['timeout'] = (int)$value;
325 4
                break;
326
327 24
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
328 4
                if (!is_array($value) && $value !== null) {
329
                    throw new InvalidArgumentException(
330
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
331
                    );
332
                }
333
334 4
                $this->attributes['auth'] = $value;
335 4
                break;
336
337 24
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
338 4
                $this->attributes['defaultSchema'] = $value;
339 4
                break;
340
341 20
            case self::CRATE_ATTR_SSL_MODE:
342 18
                $this->attributes['sslMode'] = $value;
343 18
                break;
344
345 14
            case self::CRATE_ATTR_SSL_CA_PATH:
346 4
                $this->attributes['sslCa'] = $value;
347 4
                break;
348
349 12
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
350 2
                $this->attributes['sslCaPassword'] = $value;
351 2
                break;
352
353 10
            case self::CRATE_ATTR_SSL_CERT_PATH:
354 4
                $this->attributes['sslCert'] = $value;
355 4
                break;
356
357 8
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
358 2
                $this->attributes['sslCertPassword'] = $value;
359 2
                break;
360
361 6
            case self::CRATE_ATTR_SSL_KEY_PATH:
362 4
                $this->attributes['sslKey'] = $value;
363 4
                break;
364
365 4
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
366 2
                $this->attributes['sslKeyPassword'] = $value;
367 2
                break;
368
369
            default:
370 2
                throw new Exception\PDOException('Unsupported driver attribute');
371
        }
372
373
        // A setting changed so we need to reconfigure the server pool
374 30
        $this->server->configure($this);
375
    }
376
377
    /**
378
     * {@inheritDoc}
379
     *
380
     * @throws \Crate\PDO\Exception\PDOException
381
     */
382 50
    public function getAttribute($attribute)
383
    {
384
        switch ($attribute) {
385 50
            case self::ATTR_PERSISTENT:
386 2
                return false;
387
388 50
            case self::ATTR_PREFETCH:
389 2
                return false;
390
391 50
            case self::ATTR_CLIENT_VERSION:
392 2
                return self::VERSION;
393
394 50
            case self::ATTR_SERVER_VERSION:
395
                return $this->server->getServerVersion();
396
397 50
            case self::ATTR_SERVER_INFO:
398
                return $this->server->getServerInfo();
399
400 50
            case self::ATTR_TIMEOUT:
401 50
                return $this->attributes['timeout'];
402
403 50
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
404 50
                return $this->attributes['auth'];
405
406 50
            case self::ATTR_DEFAULT_FETCH_MODE:
407 4
                return $this->attributes['defaultFetchMode'];
408
409 50
            case self::ATTR_ERRMODE:
410 2
                return $this->attributes['errorMode'];
411
412 50
            case self::ATTR_DRIVER_NAME:
413 2
                return static::DRIVER_NAME;
414
415 50
            case self::ATTR_STATEMENT_CLASS:
416 2
                return [$this->attributes['statementClass']];
417
418 50
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
419 50
                return $this->attributes['defaultSchema'];
420
421 50
            case self::CRATE_ATTR_SSL_MODE:
422 50
                return $this->attributes['sslMode'];
423
424 50
            case self::CRATE_ATTR_SSL_CA_PATH:
425 50
                return $this->attributes['sslCa'] ?? null;
426
427 50
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
428 50
                return $this->attributes['sslCaPassword'] ?? null;
429
430 50
            case self::CRATE_ATTR_SSL_CERT_PATH:
431 50
                return $this->attributes['sslCert'] ?? null;
432
433 50
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
434 50
                return $this->attributes['sslCertPassword'] ?? null;
435
436 50
            case self::CRATE_ATTR_SSL_KEY_PATH:
437 50
                return $this->attributes['sslKey'] ?? null;
438
439 50
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
440 50
                return $this->attributes['sslKeyPassword'] ?? null;
441
442
            default:
443
                // PHP Switch is a lose comparison
444 4
                if ($attribute === self::ATTR_AUTOCOMMIT) {
445 2
                    return true;
446
                }
447
448 2
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
449
        }
450
    }
451
452
    /**
453
     * {@inheritDoc}
454
     */
455 8
    public function quote($string, $parameter_type = self::PARAM_STR)
456
    {
457
        switch ($parameter_type) {
458 8
            case self::PARAM_INT:
459 2
                return (int)$string;
460
461 8
            case self::PARAM_BOOL:
462 2
                return (bool)$string;
463
464 8
            case self::PARAM_NULL:
465 2
                return null;
466
467 6
            case self::PARAM_LOB:
468 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
469
470 4
            case self::PARAM_STR:
471 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
472
473
            default:
474 2
                throw new Exception\InvalidArgumentException('Unknown param type');
475
        }
476
    }
477
478
    /**
479
     * {@inheritDoc}
480
     */
481 2
    public static function getAvailableDrivers()
482
    {
483 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
484
    }
485
486
    public function getServerVersion()
487
    {
488
        return $this->server->getServerVersion();
489
    }
490
491
    public function getServerInfo()
492
    {
493
        return $this->getServerVersion();
494
    }
495
}
496