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

PDO::getServerInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
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