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