Passed
Push — main ( 28cc2a...82d1e2 )
by Andreas
02:06
created

PDO::__construct()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 13.2607

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 24
nc 8
nop 4
dl 0
loc 44
ccs 13
cts 23
cp 0.5652
crap 13.2607
rs 8.4444
c 1
b 0
f 0
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