Completed
Pull Request — default-schema (#32)
by
unknown
78:30 queued 63:39
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
    const CRATE_ATTR_DEFAULT_SCHEMA     = 1001;
38
39
    const PARAM_FLOAT       = 6;
40
    const PARAM_DOUBLE      = 7;
41
    const PARAM_LONG        = 8;
42
    const PARAM_ARRAY       = 9;
43
    const PARAM_OBJECT      = 10;
44
    const PARAM_TIMESTAMP   = 11;
45
    const PARAM_IP          = 12;
46
47
    /**
48
     * @var array
49
     */
50
    private $attributes = [
51
        'defaultFetchMode' => self::FETCH_BOTH,
52
        'errorMode'        => self::ERRMODE_SILENT,
53
        'statementClass'   => 'Crate\PDO\PDOStatement',
54
        'timeout'          => 5.0,
55
        'auth'             => [],
56
        'defaultSchema'    => 'doc'
57
    ];
58
59
    /**
60
     * @var Http\ClientInterface
61
     */
62
    private $client;
63
64
    /**
65
     * @var PDOStatement|null
66
     */
67
    private $lastStatement;
68
69
    /**
70
     * @var callable
71
     */
72
    private $request;
73
74
    /**
75
     * {@inheritDoc}
76
     *
77
     * @param string     $dsn      The HTTP endpoint to call
78
     * @param null       $username Unused
79
     * @param null       $passwd   Unused
80
     * @param null|array $options  Attributes to set on the PDO
81
     */
82
    public function __construct($dsn, $username, $passwd, $options)
83
    {
84
        foreach (ArrayUtils::toArray($options) as $attribute => $value) {
85
            $this->setAttribute($attribute, $value);
86
        }
87
88
        $dsnParts = self::parseDSN($dsn);
89
        $uri     = self::computeURI($dsnParts[0]);
90
91
        $this->client = new Http\Client($uri, [
92
            'timeout' => $this->attributes['timeout']
93
        ]);
94
95
        if (!empty($username) && !empty($passwd)) {
96
            $this->setAttribute(PDO::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
97
        }
98
99
        if (!empty($dsnParts[1])) {
100
            $this->setAttribute(PDO::CRATE_ATTR_DEFAULT_SCHEMA, $dsnParts[1]);
101
        }
102
103
        // Define a callback that will be used in the PDOStatements
104
        // This way we don't expose this as a public api to the end users.
105
        $this->request = function (PDOStatement $statement, $sql, array $parameters) {
106
107
            $this->lastStatement = $statement;
108
109
            try {
110
111
                return $this->client->execute($sql, $parameters);
112
113
            } catch (Exception\RuntimeException $e) {
114
115
                if ($this->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION) {
116
                    throw new Exception\PDOException($e->getMessage(), $e->getCode());
117
                }
118
119
                if ($this->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_WARNING) {
120
                    trigger_error(sprintf('[%d] %s', $e->getCode(), $e->getMessage()), E_USER_WARNING);
121
                }
122
123
                // should probably wrap this in a error object ?
124
                return [
125
                    'code'    => $e->getCode(),
126
                    'message' => $e->getMessage()
127
                ];
128
            }
129
        };
130
    }
131
132
    /**
133
     * Extract host:port pairs out of the DSN string
134
     *
135
     * @param string $dsn The DSN string
136
     *
137
     * @return array An array of host:port strings
138
     */
139
    private static function parseDSN($dsn)
140
    {
141
        $matches = array();
142
143
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
144
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
145
        }
146
147
        return array_slice($matches, 1);
148
    }
149
150
    /**
151
     * Compute a URI for usage with the HTTP client
152
     *
153
     * @param string $server A host:port string
154
     *
155
     * @return string An URI which can be used by the HTTP client
156
     */
157
    private static function computeURI($server)
158
    {
159
        return 'http://' . $server . '/_sql';
160
    }
161
162
    /**
163
     * {@inheritDoc}
164
     */
165
    public function prepare($statement, $options = null)
166
    {
167
        $options = ArrayUtils::toArray($options);
168
169
        if (isset($options[PDO::ATTR_CURSOR])) {
170
            trigger_error(sprintf('%s not supported', __METHOD__), E_USER_WARNING);
171
            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...
172
        }
173
174
        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...
175
    }
176
177
    /**
178
     * {@inheritDoc}
179
     */
180
    public function beginTransaction()
181
    {
182
        return true;
183
    }
184
185
    /**
186
     * {@inheritDoc}
187
     */
188
    public function commit()
189
    {
190
        return true;
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196
    public function rollBack()
197
    {
198
        throw new Exception\UnsupportedException;
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     */
204
    public function inTransaction()
205
    {
206
        return false;
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212
    public function exec($statement)
213
    {
214
        $statement = $this->prepare($statement);
215
        $result    = $statement->execute();
216
217
        return $result === false ? false : $statement->rowCount();
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223
    public function query($statement)
224
    {
225
        $statement = $this->prepare($statement);
226
        $result    = $statement->execute();
227
228
        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 228 which is incompatible with the return type of the parent method PDO::query of type PDOStatement.
Loading history...
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234
    public function lastInsertId($name = null)
235
    {
236
        throw new Exception\UnsupportedException;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242
    public function errorCode()
243
    {
244
        return $this->lastStatement === null ? null : $this->lastStatement->errorCode();
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250
    public function errorInfo()
251
    {
252
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258
    public function setAttribute($attribute, $value)
259
    {
260
        switch ($attribute) {
261
            case self::ATTR_DEFAULT_FETCH_MODE:
262
                $this->attributes['defaultFetchMode'] = $value;
263
                break;
264
265
            case self::ATTR_ERRMODE:
266
                $this->attributes['errorMode'] = $value;
267
                break;
268
269
            case self::ATTR_TIMEOUT:
270
                $this->attributes['timeout'] = (int)$value;
271
                if (is_object($this->client)) {
272
                    $this->client->setTimeout((int)$value);
273
                }
274
                break;
275
276
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
277
                $this->attributes['auth'] = $value;
278
                if (is_object($this->client) && is_array($value)) {
279
                    list($user, $password) = $value;
280
                    $this->client->setHttpBasicAuth($user, $password);
281
                }
282
                break;
283
284
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
285
                $this->attributes['defaultSchema'] = $value;
286
                if (is_object($this->client)) {
287
                    $this->client->setHttpHeader('Default-Schema', $value);
288
                }
289
                break;
290
291
            default:
292
                throw new Exception\PDOException('Unsupported driver attribute');
293
        }
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299
    public function getAttribute($attribute)
300
    {
301
        switch ($attribute) {
302
            case PDO::ATTR_PERSISTENT:
303
                return false;
304
305
            case PDO::ATTR_PREFETCH:
306
                return false;
307
308
            case PDO::ATTR_CLIENT_VERSION:
309
                return self::VERSION;
310
311
            case PDO::ATTR_SERVER_VERSION:
312
                return $this->client->getServerVersion();
313
314
            case PDO::ATTR_SERVER_INFO:
315
                return $this->client->getServerInfo();
316
317
            case PDO::ATTR_TIMEOUT:
318
                return $this->attributes['timeout'];
319
320
            case PDO::CRATE_ATTR_HTTP_BASIC_AUTH:
321
                return $this->attributes['auth'];
322
323
            case PDO::ATTR_DEFAULT_FETCH_MODE:
324
                return $this->attributes['defaultFetchMode'];
325
326
            case PDO::ATTR_ERRMODE:
327
                return $this->attributes['errorMode'];
328
329
            case PDO::ATTR_DRIVER_NAME:
330
                return static::DRIVER_NAME;
331
332
            case PDO::ATTR_STATEMENT_CLASS:
333
                return [$this->attributes['statementClass']];
334
335
            case PDO::CRATE_ATTR_DEFAULT_SCHEMA:
336
                return [$this->attributes['defaultSchema']];
337
338
            default:
339
                // PHP Switch is a lose comparison
340
                if ($attribute === PDO::ATTR_AUTOCOMMIT) {
341
                    return true;
342
                }
343
344
                throw new Exception\PDOException('Unsupported driver attribute');
345
        }
346
    }
347
348
    /**
349
     * {@inheritDoc}
350
     */
351
    public function quote($string, $parameter_type = PDO::PARAM_STR)
352
    {
353
        switch ($parameter_type) {
354
            case PDO::PARAM_INT:
355
                return (int)$string;
356
357
            case PDO::PARAM_BOOL:
358
                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...
359
360
            case PDO::PARAM_NULL:
361
                return null;
362
363
            case PDO::PARAM_LOB:
364
                throw new Exception\UnsupportedException('This is not supported by crate.io');
365
366
            case PDO::PARAM_STR:
367
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
368
369
            default:
370
                throw new Exception\InvalidArgumentException('Unknown param type');
371
        }
372
    }
373
374
    /**
375
     * {@inheritDoc}
376
     */
377
    public static function getAvailableDrivers()
378
    {
379
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
380
    }
381
}
382