Passed
Pull Request — main (#146)
by Andreas
01:42
created

PDO::getAttribute()   D

Complexity

Conditions 21
Paths 21

Size

Total Lines 66
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 21.1728

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 21
eloc 43
c 2
b 0
f 0
nc 21
nop 1
dl 0
loc 66
ccs 38
cts 41
cp 0.9268
crap 21.1728
rs 4.1666

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
 * 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
            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 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 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
    #[\ReturnTypeWillChange]
210
    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
    public function exec($statement): int
261
    {
262
        $statement = $this->prepare($statement);
263
        $result    = $statement->execute();
264
265
        return $result === false ? false : $statement->rowCount();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result === false... $statement->rowCount() could return the type false which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     */
271 2
    public function doQuery($statement)
272
    {
273 2
        $statement = $this->prepare($statement);
274 2
        $result    = $statement->execute();
275
276 2
        return $result === false ? false : $statement;
277
    }
278
279
    /**
280
     * {@inheritDoc}
281
     */
282 2
    public function lastInsertId(?string $name = null): string
283
    {
284 2
        throw new Exception\UnsupportedException;
285
    }
286
287
    /**
288
     * {@inheritDoc}
289
     */
290
    public function errorCode(): ?string
291
    {
292
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298
    public function errorInfo(): array
299
    {
300
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->lastStatem...tStatement->errorInfo() could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
301
    }
302
303
    /**
304
     * {@inheritDoc}
305
     *
306
     * @throws \Crate\PDO\Exception\PDOException
307
     * @throws \Crate\PDO\Exception\InvalidArgumentException
308
     */
309 30
    public function setAttribute(int $attribute, $value): bool
310
    {
311
        switch ($attribute) {
312 30
            case self::ATTR_DEFAULT_FETCH_MODE:
313 2
                $this->attributes['defaultFetchMode'] = $value;
314 2
                break;
315
316 28
            case self::ATTR_ERRMODE:
317 2
                $this->attributes['errorMode'] = $value;
318 2
                break;
319
320 26
            case self::ATTR_STATEMENT_CLASS:
321 2
                $this->attributes['statementClass'] = $value;
322 2
                break;
323
324 24
            case self::ATTR_TIMEOUT:
325 4
                $this->attributes['timeout'] = (int)$value;
326 4
                break;
327
328 22
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
329 4
                if (!is_array($value) && $value !== null) {
330
                    throw new InvalidArgumentException(
331
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
332
                    );
333
                }
334
335 4
                $this->attributes['auth'] = $value;
336 4
                break;
337
338 22
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
339 4
                $this->attributes['defaultSchema'] = $value;
340 4
                break;
341
342 18
            case self::CRATE_ATTR_SSL_MODE:
343 18
                $this->attributes['sslMode'] = $value;
344 18
                break;
345
346 12
            case self::CRATE_ATTR_SSL_CA_PATH:
347 4
                $this->attributes['sslCa'] = $value;
348 4
                break;
349
350 10
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
351 2
                $this->attributes['sslCaPassword'] = $value;
352 2
                break;
353
354 8
            case self::CRATE_ATTR_SSL_CERT_PATH:
355 4
                $this->attributes['sslCert'] = $value;
356 4
                break;
357
358 6
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
359 2
                $this->attributes['sslCertPassword'] = $value;
360 2
                break;
361
362 4
            case self::CRATE_ATTR_SSL_KEY_PATH:
363 4
                $this->attributes['sslKey'] = $value;
364 4
                break;
365
366 2
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
367 2
                $this->attributes['sslKeyPassword'] = $value;
368 2
                break;
369
370
            default:
371
                throw new Exception\PDOException('Unsupported driver attribute');
372
        }
373
374
        // A setting changed so we need to reconfigure the server pool
375 30
        $this->server->configure($this);
376 30
        return true;
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     *
382
     * @throws \Crate\PDO\Exception\PDOException
383
     */
384 50
    #[\ReturnTypeWillChange]
385
    public function getAttribute(int $attribute)
386
    {
387
        switch ($attribute) {
388 50
            case self::ATTR_PREFETCH:
389 50
            case self::ATTR_PERSISTENT:
390 4
                return false;
391
392 50
            case self::ATTR_CLIENT_VERSION:
393 2
                return self::VERSION;
394
395 50
            case self::ATTR_SERVER_VERSION:
396
                return $this->server->getServerVersion();
397
398 50
            case self::ATTR_SERVER_INFO:
399
                return $this->server->getServerInfo();
400
401 50
            case self::ATTR_TIMEOUT:
402 50
                return $this->attributes['timeout'];
403
404 50
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
405 50
                return $this->attributes['auth'];
406
407 50
            case self::ATTR_DEFAULT_FETCH_MODE:
408 4
                return $this->attributes['defaultFetchMode'];
409
410 50
            case self::ATTR_ERRMODE:
411 2
                return $this->attributes['errorMode'];
412
413 50
            case self::ATTR_DRIVER_NAME:
414 2
                return static::DRIVER_NAME;
415
416 50
            case self::ATTR_STATEMENT_CLASS:
417 2
                return [$this->attributes['statementClass']];
418
419 50
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
420 50
                return $this->attributes['defaultSchema'];
421
422 50
            case self::CRATE_ATTR_SSL_MODE:
423 50
                return $this->attributes['sslMode'];
424
425 50
            case self::CRATE_ATTR_SSL_CA_PATH:
426 50
                return $this->attributes['sslCa'] ?? null;
427
428 50
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
429 50
                return $this->attributes['sslCaPassword'] ?? null;
430
431 50
            case self::CRATE_ATTR_SSL_CERT_PATH:
432 50
                return $this->attributes['sslCert'] ?? null;
433
434 50
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
435 50
                return $this->attributes['sslCertPassword'] ?? null;
436
437 50
            case self::CRATE_ATTR_SSL_KEY_PATH:
438 50
                return $this->attributes['sslKey'] ?? null;
439
440 50
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
441 50
                return $this->attributes['sslKeyPassword'] ?? null;
442
443
            default:
444
                // PHP Switch is a lose comparison
445 2
                if ($attribute === self::ATTR_AUTOCOMMIT) {
446 2
                    return true;
447
                }
448
449
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
450
        }
451
    }
452
453
    /**
454
     * {@inheritDoc}
455
     */
456 8
    #[\ReturnTypeWillChange]
457
    public function quote(string $string, int $parameter_type = self::PARAM_STR)
458
    {
459
        switch ($parameter_type) {
460 8
            case self::PARAM_INT:
461 2
                return (int)$string;
462
463 8
            case self::PARAM_BOOL:
464 2
                return (bool)$string;
465
466 8
            case self::PARAM_NULL:
467 2
                return null;
468
469 6
            case self::PARAM_LOB:
470 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
471
472 4
            case self::PARAM_STR:
473 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
474
475
            default:
476 2
                throw new Exception\InvalidArgumentException('Unknown param type');
477
        }
478
    }
479
480
    /**
481
     * {@inheritDoc}
482
     */
483 2
    public static function getAvailableDrivers(): array
484
    {
485 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
486
    }
487
488
    public function getServerVersion(): string
489
    {
490
        return $this->server->getServerVersion();
491
    }
492
493
    public function getServerInfo(): string
494
    {
495
        return $this->getServerVersion();
496
    }
497
}
498