Passed
Pull Request — main (#146)
by Andreas
01:38
created

PDO::quote()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
c 3
b 0
f 0
nc 6
nop 2
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
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
use ReturnTypeWillChange;
0 ignored issues
show
Bug introduced by
The type ReturnTypeWillChange was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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