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

PDO::setAttribute()   C

Complexity

Conditions 16
Paths 15

Size

Total Lines 69
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 16.0664

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 16
eloc 48
c 4
b 1
f 0
nc 15
nop 2
dl 0
loc 69
ccs 44
cts 47
cp 0.9362
crap 16.0664
rs 5.5666

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 32
    #[\ReturnTypeWillChange]
310
    public function setAttribute($attribute, $value)
311
    {
312
        switch ($attribute) {
313 32
            case self::ATTR_DEFAULT_FETCH_MODE:
314 2
                $this->attributes['defaultFetchMode'] = $value;
315 2
                break;
316
317 30
            case self::ATTR_ERRMODE:
318 2
                $this->attributes['errorMode'] = $value;
319 2
                break;
320
321 28
            case self::ATTR_STATEMENT_CLASS:
322 2
                $this->attributes['statementClass'] = $value;
323 2
                break;
324
325 26
            case self::ATTR_TIMEOUT:
326 4
                $this->attributes['timeout'] = (int)$value;
327 4
                break;
328
329 24
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
330 4
                if (!is_array($value) && $value !== null) {
331
                    throw new InvalidArgumentException(
332
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
333
                    );
334
                }
335
336 4
                $this->attributes['auth'] = $value;
337 4
                break;
338
339 24
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
340 4
                $this->attributes['defaultSchema'] = $value;
341 4
                break;
342
343 20
            case self::CRATE_ATTR_SSL_MODE:
344 18
                $this->attributes['sslMode'] = $value;
345 18
                break;
346
347 14
            case self::CRATE_ATTR_SSL_CA_PATH:
348 4
                $this->attributes['sslCa'] = $value;
349 4
                break;
350
351 12
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
352 2
                $this->attributes['sslCaPassword'] = $value;
353 2
                break;
354
355 10
            case self::CRATE_ATTR_SSL_CERT_PATH:
356 4
                $this->attributes['sslCert'] = $value;
357 4
                break;
358
359 8
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
360 2
                $this->attributes['sslCertPassword'] = $value;
361 2
                break;
362
363 6
            case self::CRATE_ATTR_SSL_KEY_PATH:
364 4
                $this->attributes['sslKey'] = $value;
365 4
                break;
366
367 4
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
368 2
                $this->attributes['sslKeyPassword'] = $value;
369 2
                break;
370
371
            default:
372 2
                throw new Exception\PDOException('Unsupported driver attribute');
373
        }
374
375
        // A setting changed so we need to reconfigure the server pool
376 30
        $this->server->configure($this);
377 30
        return true;
378
    }
379
380
    /**
381
     * {@inheritDoc}
382
     *
383
     * @throws \Crate\PDO\Exception\PDOException
384
     */
385 50
    #[\ReturnTypeWillChange]
386
    public function getAttribute($attribute)
387
    {
388
        switch ($attribute) {
389 50
            case self::ATTR_PREFETCH:
390 50
            case self::ATTR_PERSISTENT:
391 4
                return false;
392
393 50
            case self::ATTR_CLIENT_VERSION:
394 2
                return self::VERSION;
395
396 50
            case self::ATTR_SERVER_VERSION:
397
                return $this->server->getServerVersion();
398
399 50
            case self::ATTR_SERVER_INFO:
400
                return $this->server->getServerInfo();
401
402 50
            case self::ATTR_TIMEOUT:
403 50
                return $this->attributes['timeout'];
404
405 50
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
406 50
                return $this->attributes['auth'];
407
408 50
            case self::ATTR_DEFAULT_FETCH_MODE:
409 4
                return $this->attributes['defaultFetchMode'];
410
411 50
            case self::ATTR_ERRMODE:
412 2
                return $this->attributes['errorMode'];
413
414 50
            case self::ATTR_DRIVER_NAME:
415 2
                return static::DRIVER_NAME;
416
417 50
            case self::ATTR_STATEMENT_CLASS:
418 2
                return [$this->attributes['statementClass']];
419
420 50
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
421 50
                return $this->attributes['defaultSchema'];
422
423 50
            case self::CRATE_ATTR_SSL_MODE:
424 50
                return $this->attributes['sslMode'];
425
426 50
            case self::CRATE_ATTR_SSL_CA_PATH:
427 50
                return $this->attributes['sslCa'] ?? null;
428
429 50
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
430 50
                return $this->attributes['sslCaPassword'] ?? null;
431
432 50
            case self::CRATE_ATTR_SSL_CERT_PATH:
433 50
                return $this->attributes['sslCert'] ?? null;
434
435 50
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
436 50
                return $this->attributes['sslCertPassword'] ?? null;
437
438 50
            case self::CRATE_ATTR_SSL_KEY_PATH:
439 50
                return $this->attributes['sslKey'] ?? null;
440
441 50
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
442 50
                return $this->attributes['sslKeyPassword'] ?? null;
443
444
            default:
445
                // PHP Switch is a lose comparison
446 4
                if ($attribute === self::ATTR_AUTOCOMMIT) {
447 2
                    return true;
448
                }
449
450 2
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
451
        }
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457 8
    #[\ReturnTypeWillChange]
458
    public function quote(string $string, int $parameter_type = self::PARAM_STR)
459
    {
460
        switch ($parameter_type) {
461 8
            case self::PARAM_INT:
462 2
                return (int)$string;
463
464 8
            case self::PARAM_BOOL:
465 2
                return (bool)$string;
466
467 8
            case self::PARAM_NULL:
468 2
                return null;
469
470 6
            case self::PARAM_LOB:
471 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
472
473 4
            case self::PARAM_STR:
474 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
475
476
            default:
477 2
                throw new Exception\InvalidArgumentException('Unknown param type');
478
        }
479
    }
480
481
    /**
482
     * {@inheritDoc}
483
     */
484 2
    public static function getAvailableDrivers(): array
485
    {
486 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
487
    }
488
489
    public function getServerVersion(): string
490
    {
491
        return $this->server->getServerVersion();
492
    }
493
494
    public function getServerInfo(): string
495
    {
496
        return $this->getServerVersion();
497
    }
498
}
499