Passed
Push — main ( 0ca8fa...68af6e )
by Andreas
02:01
created

PDO::__construct()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 53
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.3203

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 19
cts 29
cp 0.6552
crap 12.3203
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 36
    public function __construct($dsn, $username = null, $passwd = null, $options = [])
106
    {
107
108 36
        if (PHP_VERSION_ID < 80000) {
109 18
            trigger_error(
110 18
                "`crate/crate-pdo` will stop supporting PHP7 on one of the upcoming " .
111 18
                "releases. Please upgrade to PHP8.",
112 18
                E_USER_DEPRECATED
113 18
            );
114
        }
115
116 36
        $dsnParts = self::parseDSN($dsn);
117 36
        $servers  = self::serversFromDsnParts($dsnParts);
118
119 36
        $this->setServer(new ServerPool($servers));
120
121 36
        foreach ((array)$options as $attribute => $value) {
122 2
            $this->setAttribute($attribute, $value);
123
        }
124
125 36
        if (!empty($username)) {
126 4
            $this->setAttribute(self::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
127
        }
128
129 36
        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 36
        $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 36
        };
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 113
    private static function parseDSN($dsn)
184
    {
185 113
        $matches = [];
186
187 113
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
188 8
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
189
        }
190
191 105
        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 103
    private static function serversFromDsnParts($dsnParts)
202
    {
203 103
        return explode(',', trim($dsnParts[0], ','));
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 2
    #[\ReturnTypeWillChange]
210 2
    public function prepare(string $statement, array $options = [])
211
    {
212 4
        $options = ArrayUtils::toArray($options);
213
214 4
        if (isset($options[self::ATTR_CURSOR])) {
215
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
216
217
            return true;
218
        }
219
220 4
        $className = $this->attributes['statementClass'];
221
222 4
        return new $className($this, $this->request, $statement, $options);
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228 2
    public function beginTransaction(): bool
229
    {
230 2
        return true;
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236 2
    public function commit(): bool
237
    {
238 2
        return true;
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 2
    public function rollBack(): bool
245
    {
246 2
        throw new Exception\UnsupportedException;
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     */
252 2
    public function inTransaction(): bool
253
    {
254 2
        return false;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260
    #[\ReturnTypeWillChange]
261
    public function exec($statement)
262
    {
263
        $statement = $this->prepare($statement);
264
        $result    = $statement->execute();
265
266
        return $result === false ? false : $statement->rowCount();
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 2
    public function doQuery($statement)
273
    {
274 2
        $statement = $this->prepare($statement);
275 2
        $result    = $statement->execute();
276
277 2
        return $result === false ? false : $statement;
278
    }
279
280
    /**
281
     * {@inheritDoc}
282
     */
283 2
    public function lastInsertId(?string $name = null): string
284
    {
285 2
        throw new Exception\UnsupportedException;
286
    }
287
288
    /**
289
     * {@inheritDoc}
290
     */
291
    public function errorCode(): ?string
292
    {
293
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299
    public function errorInfo(): array
300
    {
301
        return $this->lastStatement === null ? ["00000", null, null] : $this->lastStatement->errorInfo();
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     *
307
     * @throws \Crate\PDO\Exception\PDOException
308
     * @throws \Crate\PDO\Exception\InvalidArgumentException
309
     */
310 16
    #[\ReturnTypeWillChange]
311 16
    public function setAttribute($attribute, $value)
312
    {
313
        switch ($attribute) {
314 32
            case self::ATTR_DEFAULT_FETCH_MODE:
315 2
                $this->attributes['defaultFetchMode'] = $value;
316 2
                break;
317
318 30
            case self::ATTR_ERRMODE:
319 2
                $this->attributes['errorMode'] = $value;
320 2
                break;
321
322 28
            case self::ATTR_STATEMENT_CLASS:
323 2
                $this->attributes['statementClass'] = $value;
324 2
                break;
325
326 26
            case self::ATTR_TIMEOUT:
327 4
                $this->attributes['timeout'] = (int)$value;
328 4
                break;
329
330 24
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
331 4
                if (!is_array($value) && $value !== null) {
332
                    throw new InvalidArgumentException(
333
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
334
                    );
335
                }
336
337 4
                $this->attributes['auth'] = $value;
338 4
                break;
339
340 24
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
341 4
                $this->attributes['defaultSchema'] = $value;
342 4
                break;
343
344 20
            case self::CRATE_ATTR_SSL_MODE:
345 18
                $this->attributes['sslMode'] = $value;
346 18
                break;
347
348 14
            case self::CRATE_ATTR_SSL_CA_PATH:
349 4
                $this->attributes['sslCa'] = $value;
350 4
                break;
351
352 12
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
353 2
                $this->attributes['sslCaPassword'] = $value;
354 2
                break;
355
356 10
            case self::CRATE_ATTR_SSL_CERT_PATH:
357 4
                $this->attributes['sslCert'] = $value;
358 4
                break;
359
360 8
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
361 2
                $this->attributes['sslCertPassword'] = $value;
362 2
                break;
363
364 6
            case self::CRATE_ATTR_SSL_KEY_PATH:
365 4
                $this->attributes['sslKey'] = $value;
366 4
                break;
367
368 4
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
369 2
                $this->attributes['sslKeyPassword'] = $value;
370 2
                break;
371
372
            default:
373 2
                throw new Exception\PDOException('Unsupported driver attribute');
374
        }
375
376
        // A setting changed so we need to reconfigure the server pool
377 30
        $this->server->configure($this);
378 30
        return true;
379
    }
380
381
    /**
382
     * {@inheritDoc}
383
     *
384
     * @throws \Crate\PDO\Exception\PDOException
385
     */
386 25
    #[\ReturnTypeWillChange]
387 25
    public function getAttribute($attribute)
388
    {
389
        switch ($attribute) {
390 50
            case self::ATTR_PREFETCH:
391 50
            case self::ATTR_PERSISTENT:
392 4
                return false;
393
394 50
            case self::ATTR_CLIENT_VERSION:
395 2
                return self::VERSION;
396
397 50
            case self::ATTR_SERVER_VERSION:
398
                return $this->server->getServerVersion();
399
400 50
            case self::ATTR_SERVER_INFO:
401
                return $this->server->getServerInfo();
402
403 50
            case self::ATTR_TIMEOUT:
404 50
                return $this->attributes['timeout'];
405
406 50
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
407 50
                return $this->attributes['auth'];
408
409 50
            case self::ATTR_DEFAULT_FETCH_MODE:
410 4
                return $this->attributes['defaultFetchMode'];
411
412 50
            case self::ATTR_ERRMODE:
413 2
                return $this->attributes['errorMode'];
414
415 50
            case self::ATTR_DRIVER_NAME:
416 2
                return static::DRIVER_NAME;
417
418 50
            case self::ATTR_STATEMENT_CLASS:
419 2
                return [$this->attributes['statementClass']];
420
421 50
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
422 50
                return $this->attributes['defaultSchema'];
423
424 50
            case self::CRATE_ATTR_SSL_MODE:
425 50
                return $this->attributes['sslMode'];
426
427 50
            case self::CRATE_ATTR_SSL_CA_PATH:
428 50
                return $this->attributes['sslCa'] ?? null;
429
430 50
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
431 50
                return $this->attributes['sslCaPassword'] ?? null;
432
433 50
            case self::CRATE_ATTR_SSL_CERT_PATH:
434 50
                return $this->attributes['sslCert'] ?? null;
435
436 50
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
437 50
                return $this->attributes['sslCertPassword'] ?? null;
438
439 50
            case self::CRATE_ATTR_SSL_KEY_PATH:
440 50
                return $this->attributes['sslKey'] ?? null;
441
442 50
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
443 50
                return $this->attributes['sslKeyPassword'] ?? null;
444
445
            default:
446
                // PHP Switch is a lose comparison
447 4
                if ($attribute === self::ATTR_AUTOCOMMIT) {
448 2
                    return true;
449
                }
450
451 2
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
452
        }
453
    }
454
455
    /**
456
     * {@inheritDoc}
457
     */
458 4
    #[\ReturnTypeWillChange]
459 4
    public function quote(string $string, int $parameter_type = self::PARAM_STR)
460
    {
461
        switch ($parameter_type) {
462 8
            case self::PARAM_INT:
463 2
                return (int)$string;
464
465 8
            case self::PARAM_BOOL:
466 2
                return (bool)$string;
467
468 8
            case self::PARAM_NULL:
469 2
                return null;
470
471 6
            case self::PARAM_LOB:
472 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
473
474 4
            case self::PARAM_STR:
475 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
476
477
            default:
478 2
                throw new Exception\InvalidArgumentException('Unknown param type');
479
        }
480
    }
481
482
    /**
483
     * {@inheritDoc}
484
     */
485 2
    public static function getAvailableDrivers(): array
486
    {
487 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
488
    }
489
490
    public function getServerVersion(): string
491
    {
492
        return $this->server->getServerVersion();
493
    }
494
495
    public function getServerInfo(): string
496
    {
497
        return $this->getServerVersion();
498
    }
499
}
500