Passed
Pull Request — main (#123)
by Alexander
01:40
created

PDO::getAttribute()   D

Complexity

Conditions 21
Paths 21

Size

Total Lines 67
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 21.0475

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