Completed
Pull Request — default-schema (#32)
by
unknown
05:07
created

PDO::setAttribute()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 1 Features 1
Metric Value
c 8
b 1
f 1
dl 0
loc 37
rs 4.8197
cc 10
eloc 26
nc 9
nop 2

How to fix   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
namespace Crate\PDO;
24
25
use Crate\PDO\Exception\PDOException;
26
use Crate\Stdlib\ArrayUtils;
27
use PDO as BasePDO;
28
29
class PDO extends BasePDO implements PDOInterface
30
{
31
    const VERSION = '0.3.1';
32
    const DRIVER_NAME = 'crate';
33
34
    const DSN_REGEX = '/^(?:crate)?(?::([\w\d\.-]+:\d+))+\/?([\w]+)?$/';
35
36
    const CRATE_ATTR_HTTP_BASIC_AUTH    = 1000;
37
    /**
38
     * @deprecated
39
     */
40
    const ATTR_HTTP_BASIC_AUTH          = CRATE_ATTR_HTTP_BASIC_AUTH;
41
42
    const CRATE_ATTR_DEFAULT_SCHEMA     = 1001;
43
44
    const PARAM_FLOAT       = 6;
45
    const PARAM_DOUBLE      = 7;
46
    const PARAM_LONG        = 8;
47
    const PARAM_ARRAY       = 9;
48
    const PARAM_OBJECT      = 10;
49
    const PARAM_TIMESTAMP   = 11;
50
    const PARAM_IP          = 12;
51
52
    /**
53
     * @var array
54
     */
55
    private $attributes = [
56
        'defaultFetchMode' => self::FETCH_BOTH,
57
        'errorMode'        => self::ERRMODE_SILENT,
58
        'statementClass'   => 'Crate\PDO\PDOStatement',
59
        'timeout'          => 5.0,
60
        'auth'             => [],
61
        'defaultSchema'    => 'doc'
62
    ];
63
64
    /**
65
     * @var Http\ClientInterface
66
     */
67
    private $client;
68
69
    /**
70
     * @var PDOStatement|null
71
     */
72
    private $lastStatement;
73
74
    /**
75
     * @var callable
76
     */
77
    private $request;
78
79
    /**
80
     * {@inheritDoc}
81
     *
82
     * @param string     $dsn      The HTTP endpoint to call
83
     * @param null       $username Unused
84
     * @param null       $passwd   Unused
85
     * @param null|array $options  Attributes to set on the PDO
86
     */
87
    public function __construct($dsn, $username, $passwd, $options)
88
    {
89
        foreach (ArrayUtils::toArray($options) as $attribute => $value) {
90
            $this->setAttribute($attribute, $value);
91
        }
92
93
        $dsnParts = self::parseDSN($dsn);
94
        $uri     = self::computeURI($dsnParts[0]);
95
96
        $this->client = new Http\Client($uri, [
97
            'timeout' => $this->attributes['timeout']
98
        ]);
99
100
        if (!empty($username) && !empty($passwd)) {
101
            $this->setAttribute(PDO::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
102
        }
103
104
        if (!empty($dsnParts[1])) {
105
            $this->setAttribute(PDO::CRATE_ATTR_DEFAULT_SCHEMA, $dsnParts[1]);
106
        }
107
108
        // Define a callback that will be used in the PDOStatements
109
        // This way we don't expose this as a public api to the end users.
110
        $this->request = function (PDOStatement $statement, $sql, array $parameters) {
111
112
            $this->lastStatement = $statement;
113
114
            try {
115
116
                return $this->client->execute($sql, $parameters);
117
118
            } catch (Exception\RuntimeException $e) {
119
120
                if ($this->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION) {
121
                    throw new Exception\PDOException($e->getMessage(), $e->getCode());
122
                }
123
124
                if ($this->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_WARNING) {
125
                    trigger_error(sprintf('[%d] %s', $e->getCode(), $e->getMessage()), E_USER_WARNING);
126
                }
127
128
                // should probably wrap this in a error object ?
129
                return [
130
                    'code'    => $e->getCode(),
131
                    'message' => $e->getMessage()
132
                ];
133
            }
134
        };
135
    }
136
137
    /**
138
     * Extract host:port pairs out of the DSN string
139
     *
140
     * @param string $dsn The DSN string
141
     *
142
     * @return array An array of host:port strings
143
     */
144
    private static function parseDSN($dsn)
145
    {
146
        $matches = array();
147
148
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
149
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
150
        }
151
152
        return array_slice($matches, 1);
153
    }
154
155
    /**
156
     * Compute a URI for usage with the HTTP client
157
     *
158
     * @param string $server A host:port string
159
     *
160
     * @return string An URI which can be used by the HTTP client
161
     */
162
    private static function computeURI($server)
163
    {
164
        return 'http://' . $server . '/_sql';
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170
    public function prepare($statement, $options = null)
171
    {
172
        $options = ArrayUtils::toArray($options);
173
174
        if (isset($options[PDO::ATTR_CURSOR])) {
175
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
176
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method PDO::prepare of type PDOStatement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
177
        }
178
179
        return new PDOStatement($this, $this->request, $statement, $options);
0 ignored issues
show
Documentation introduced by
$this->request is of type callable, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function beginTransaction()
186
    {
187
        return true;
188
    }
189
190
    /**
191
     * {@inheritDoc}
192
     */
193
    public function commit()
194
    {
195
        return true;
196
    }
197
198
    /**
199
     * {@inheritDoc}
200
     */
201
    public function rollBack()
202
    {
203
        throw new Exception\UnsupportedException;
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209
    public function inTransaction()
210
    {
211
        return false;
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217
    public function exec($statement)
218
    {
219
        $statement = $this->prepare($statement);
220
        $result    = $statement->execute();
221
222
        return $result === false ? false : $statement->rowCount();
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228
    public function query($statement)
229
    {
230
        $statement = $this->prepare($statement);
231
        $result    = $statement->execute();
232
233
        return $result === false ? false : $statement;
0 ignored issues
show
Bug Compatibility introduced by
The expression $result === false ? false : $statement; of type boolean|Crate\PDO\PDOStatement adds the type boolean to the return on line 233 which is incompatible with the return type of the parent method PDO::query of type PDOStatement.
Loading history...
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239
    public function lastInsertId($name = null)
240
    {
241
        throw new Exception\UnsupportedException;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247
    public function errorCode()
248
    {
249
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255
    public function errorInfo()
256
    {
257
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263
    public function setAttribute($attribute, $value)
264
    {
265
        switch ($attribute) {
266
            case self::ATTR_DEFAULT_FETCH_MODE:
267
                $this->attributes['defaultFetchMode'] = $value;
268
                break;
269
270
            case self::ATTR_ERRMODE:
271
                $this->attributes['errorMode'] = $value;
272
                break;
273
274
            case self::ATTR_TIMEOUT:
275
                $this->attributes['timeout'] = (int)$value;
276
                if (is_object($this->client)) {
277
                    $this->client->setTimeout((int)$value);
278
                }
279
                break;
280
281
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
282
                $this->attributes['auth'] = $value;
283
                if (is_object($this->client) && is_array($value)) {
284
                    list($user, $password) = $value;
285
                    $this->client->setHttpBasicAuth($user, $password);
286
                }
287
                break;
288
289
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
290
                $this->attributes['defaultSchema'] = $value;
291
                if (is_object($this->client)) {
292
                    $this->client->setHttpHeader('default-schema', $value);
293
                }
294
                break;
295
296
            default:
297
                throw new Exception\PDOException('Unsupported driver attribute');
298
        }
299
    }
300
301
    /**
302
     * {@inheritDoc}
303
     */
304
    public function getAttribute($attribute)
305
    {
306
        switch ($attribute) {
307
            case PDO::ATTR_PERSISTENT:
308
                return false;
309
310
            case PDO::ATTR_PREFETCH:
311
                return false;
312
313
            case PDO::ATTR_CLIENT_VERSION:
314
                return self::VERSION;
315
316
            case PDO::ATTR_SERVER_VERSION:
317
                return $this->client->getServerVersion();
318
319
            case PDO::ATTR_SERVER_INFO:
320
                return $this->client->getServerInfo();
321
322
            case PDO::ATTR_TIMEOUT:
323
                return $this->attributes['timeout'];
324
325
            case PDO::CRATE_ATTR_HTTP_BASIC_AUTH:
326
                return $this->attributes['auth'];
327
328
            case PDO::ATTR_DEFAULT_FETCH_MODE:
329
                return $this->attributes['defaultFetchMode'];
330
331
            case PDO::ATTR_ERRMODE:
332
                return $this->attributes['errorMode'];
333
334
            case PDO::ATTR_DRIVER_NAME:
335
                return static::DRIVER_NAME;
336
337
            case PDO::ATTR_STATEMENT_CLASS:
338
                return [$this->attributes['statementClass']];
339
340
            case PDO::CRATE_ATTR_DEFAULT_SCHEMA:
341
                return [$this->attributes['defaultSchema']];
342
343
            default:
344
                // PHP Switch is a lose comparison
345
                if ($attribute === PDO::ATTR_AUTOCOMMIT) {
346
                    return true;
347
                }
348
349
                throw new Exception\PDOException('Unsupported driver attribute');
350
        }
351
    }
352
353
    /**
354
     * {@inheritDoc}
355
     */
356
    public function quote($string, $parameter_type = PDO::PARAM_STR)
357
    {
358
        switch ($parameter_type) {
359
            case PDO::PARAM_INT:
360
                return (int)$string;
361
362
            case PDO::PARAM_BOOL:
363
                return (bool)$string;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return (bool) $string; (boolean) is incompatible with the return type of the parent method PDO::quote of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
364
365
            case PDO::PARAM_NULL:
366
                return null;
367
368
            case PDO::PARAM_LOB:
369
                throw new Exception\UnsupportedException('This is not supported by crate.io');
370
371
            case PDO::PARAM_STR:
372
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
373
374
            default:
375
                throw new Exception\InvalidArgumentException('Unknown param type');
376
        }
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     */
382
    public static function getAvailableDrivers()
383
    {
384
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
385
    }
386
}
387