Passed
Pull Request — main (#121)
by Alexander
01:59
created

PDO::setAttribute()   C

Complexity

Conditions 16
Paths 15

Size

Total Lines 67
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 16.0224

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 16
eloc 46
c 4
b 1
f 0
nc 15
nop 2
dl 0
loc 67
ccs 43
cts 45
cp 0.9556
crap 16.0224
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
class PDO extends BasePDO implements PDOInterface
35
{
36
    use PDOImplementation;
37
38
    public const VERSION     = '2.0.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
        return new PDOStatement($this, $this->request, $statement, $options);
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 2
    public function beginTransaction()
210
    {
211 2
        return true;
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217 2
    public function commit()
218
    {
219 2
        return true;
220
    }
221
222
    /**
223
     * {@inheritDoc}
224
     */
225 2
    public function rollBack()
226
    {
227 2
        throw new Exception\UnsupportedException;
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233 2
    public function inTransaction()
234
    {
235 2
        return false;
236
    }
237
238
    /**
239
     * {@inheritDoc}
240
     */
241
    public function exec($statement)
242
    {
243
        $statement = $this->prepare($statement);
244
        $result    = $statement->execute();
245
246
        return $result === false ? false : $statement->rowCount();
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     */
252 2
    public function doQuery($statement)
253
    {
254 2
        $statement = $this->prepare($statement);
255 2
        $result    = $statement->execute();
256
257 2
        return $result === false ? false : $statement;
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 2
    public function lastInsertId($name = null)
264
    {
265 2
        throw new Exception\UnsupportedException;
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     */
271
    public function errorCode()
272
    {
273
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
274
    }
275
276
    /**
277
     * {@inheritDoc}
278
     */
279
    public function errorInfo()
280
    {
281
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
282
    }
283
284
    /**
285
     * {@inheritDoc}
286
     *
287
     * @throws \Crate\PDO\Exception\PDOException
288
     * @throws \Crate\PDO\Exception\InvalidArgumentException
289
     */
290 32
    public function setAttribute($attribute, $value)
291
    {
292
        switch ($attribute) {
293 32
            case self::ATTR_DEFAULT_FETCH_MODE:
294 2
                $this->attributes['defaultFetchMode'] = $value;
295 2
                break;
296
297 30
            case self::ATTR_ERRMODE:
298 2
                $this->attributes['errorMode'] = $value;
299 2
                break;
300
301 28
            case self::ATTR_STATEMENT_CLASS:
302 2
                $this->attributes['statementClass'] = $value;
303 2
                break;
304
305 26
            case self::ATTR_TIMEOUT:
306 4
                $this->attributes['timeout'] = (int)$value;
307 4
                break;
308
309 24
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
310 4
                if (!is_array($value) && $value !== null) {
311
                    throw new InvalidArgumentException(
312
                        'Value probided to CRATE_ATTR_HTTP_BASIC_AUTH must be null or an array'
313
                    );
314
                }
315
316 4
                $this->attributes['auth'] = $value;
317 4
                break;
318
319 24
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
320 4
                $this->attributes['defaultSchema'] = $value;
321 4
                break;
322
323 20
            case self::CRATE_ATTR_SSL_MODE:
324 18
                $this->attributes['sslMode'] = $value;
325 18
                break;
326
327 14
            case self::CRATE_ATTR_SSL_CA_PATH:
328 4
                $this->attributes['sslCa'] = $value;
329 4
                break;
330
331 12
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
332 2
                $this->attributes['sslCaPassword'] = $value;
333 2
                break;
334
335 10
            case self::CRATE_ATTR_SSL_CERT_PATH:
336 4
                $this->attributes['sslCert'] = $value;
337 4
                break;
338
339 8
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
340 2
                $this->attributes['sslCertPassword'] = $value;
341 2
                break;
342
343 6
            case self::CRATE_ATTR_SSL_KEY_PATH:
344 4
                $this->attributes['sslKey'] = $value;
345 4
                break;
346
347 4
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
348 2
                $this->attributes['sslKeyPassword'] = $value;
349 2
                break;
350
351
            default:
352 2
                throw new Exception\PDOException('Unsupported driver attribute');
353
        }
354
355
        // A setting changed so we need to reconfigure the server pool
356 30
        $this->server->configure($this);
357 30
    }
358
359
    /**
360
     * {@inheritDoc}
361
     *
362
     * @throws \Crate\PDO\Exception\PDOException
363
     */
364 48
    public function getAttribute($attribute)
365
    {
366
        switch ($attribute) {
367 48
            case self::ATTR_PERSISTENT:
368 2
                return false;
369
370 48
            case self::ATTR_PREFETCH:
371 2
                return false;
372
373 48
            case self::ATTR_CLIENT_VERSION:
374 2
                return self::VERSION;
375
376 48
            case self::ATTR_SERVER_VERSION:
377
                return $this->server->getServerVersion();
378
379 48
            case self::ATTR_SERVER_INFO:
380
                return $this->server->getServerInfo();
381
382 48
            case self::ATTR_TIMEOUT:
383 48
                return $this->attributes['timeout'];
384
385 48
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
386 48
                return $this->attributes['auth'];
387
388 48
            case self::ATTR_DEFAULT_FETCH_MODE:
389 4
                return $this->attributes['defaultFetchMode'];
390
391 48
            case self::ATTR_ERRMODE:
392 2
                return $this->attributes['errorMode'];
393
394 48
            case self::ATTR_DRIVER_NAME:
395 2
                return static::DRIVER_NAME;
396
397 48
            case self::ATTR_STATEMENT_CLASS:
398 2
                return [$this->attributes['statementClass']];
399
400 48
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
401 48
                return $this->attributes['defaultSchema'];
402
403 48
            case self::CRATE_ATTR_SSL_MODE:
404 48
                return $this->attributes['sslMode'];
405
406 48
            case self::CRATE_ATTR_SSL_CA_PATH:
407 48
                return $this->attributes['sslCa'] ?? null;
408
409 48
            case self::CRATE_ATTR_SSL_CA_PASSWORD:
410 48
                return $this->attributes['sslCaPassword'] ?? null;
411
412 48
            case self::CRATE_ATTR_SSL_CERT_PATH:
413 48
                return $this->attributes['sslCert'] ?? null;
414
415 48
            case self::CRATE_ATTR_SSL_CERT_PASSWORD:
416 48
                return $this->attributes['sslCertPassword'] ?? null;
417
418 48
            case self::CRATE_ATTR_SSL_KEY_PATH:
419 48
                return $this->attributes['sslKey'] ?? null;
420
421 48
            case self::CRATE_ATTR_SSL_KEY_PASSWORD:
422 48
                return $this->attributes['sslKeyPassword'] ?? null;
423
424
            default:
425
                // PHP Switch is a lose comparison
426 4
                if ($attribute === self::ATTR_AUTOCOMMIT) {
427 2
                    return true;
428
                }
429
430 2
                throw new Exception\PDOException(sprintf('Unsupported driver attribute: %s', $attribute));
431
        }
432
    }
433
434
    /**
435
     * {@inheritDoc}
436
     */
437 8
    public function quote($string, $parameter_type = self::PARAM_STR)
438
    {
439
        switch ($parameter_type) {
440 8
            case self::PARAM_INT:
441 2
                return (int)$string;
442
443 8
            case self::PARAM_BOOL:
444 2
                return (bool)$string;
445
446 8
            case self::PARAM_NULL:
447 2
                return null;
448
449 6
            case self::PARAM_LOB:
450 2
                throw new Exception\UnsupportedException('This is not supported by crate.io');
451
452 4
            case self::PARAM_STR:
453 2
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
454
455
            default:
456 2
                throw new Exception\InvalidArgumentException('Unknown param type');
457
        }
458
    }
459
460
    /**
461
     * {@inheritDoc}
462
     */
463 2
    public static function getAvailableDrivers()
464
    {
465 2
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
466
    }
467
468
    public function getServerVersion()
469
    {
470
        return $this->server->getServerVersion();
471
    }
472
473
    public function getServerInfo()
474
    {
475
        return $this->getServerVersion();
476
    }
477
}
478