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

PDO::setAttribute()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 10.4632

Importance

Changes 8
Bugs 1 Features 1
Metric Value
c 8
b 1
f 1
dl 0
loc 37
ccs 25
cts 30
cp 0.8333
rs 4.8197
cc 10
eloc 26
nc 9
nop 2
crap 10.4632

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 since version 0.4
39
     * @deprecated
40
     */
41
    const ATTR_HTTP_BASIC_AUTH          = CRATE_ATTR_HTTP_BASIC_AUTH;
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 3
    public function __construct($dsn, $username, $passwd, $options)
88
    {
89 3
        foreach (ArrayUtils::toArray($options) as $attribute => $value) {
90 1
            $this->setAttribute($attribute, $value);
91 3
        }
92
93 3
        $dsnParts = self::parseDSN($dsn);
94 3
        $uri     = self::computeURI($dsnParts[0]);
95
96 3
        $this->client = new Http\Client($uri, [
97 3
            'timeout' => $this->attributes['timeout']
98 3
        ]);
99
100 3
        if (!empty($username) && !empty($passwd)) {
101 1
            $this->setAttribute(PDO::CRATE_ATTR_HTTP_BASIC_AUTH, [$username, $passwd]);
102 1
        }
103
104 3
        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 3
    }
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 33
    private static function parseDSN($dsn)
145
    {
146 33
        $matches = array();
147
148 33
        if (!preg_match(static::DSN_REGEX, $dsn, $matches)) {
149 4
            throw new PDOException(sprintf('Invalid DSN %s', $dsn));
150
        }
151
152 29
        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 27
    private static function computeURI($server)
163
    {
164 27
        return 'http://' . $server . '/_sql';
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 1
    public function prepare($statement, $options = null)
171
    {
172 1
        $options = ArrayUtils::toArray($options);
173
174 1
        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 1
        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 1
    public function beginTransaction()
186
    {
187 1
        return true;
188
    }
189
190
    /**
191
     * {@inheritDoc}
192
     */
193 1
    public function commit()
194
    {
195 1
        return true;
196
    }
197
198
    /**
199
     * {@inheritDoc}
200
     */
201 1
    public function rollBack()
202
    {
203 1
        throw new Exception\UnsupportedException;
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 1
    public function inTransaction()
210
    {
211 1
        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 1
    public function lastInsertId($name = null)
240
    {
241 1
        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 5
    public function setAttribute($attribute, $value)
264
    {
265
        switch ($attribute) {
266 5
            case self::ATTR_DEFAULT_FETCH_MODE:
267 1
                $this->attributes['defaultFetchMode'] = $value;
268 1
                break;
269
270 4
            case self::ATTR_ERRMODE:
271 1
                $this->attributes['errorMode'] = $value;
272 1
                break;
273
274 3
            case self::ATTR_TIMEOUT:
275 1
                $this->attributes['timeout'] = (int)$value;
276 1
                if (is_object($this->client)) {
277 1
                    $this->client->setTimeout((int)$value);
278 1
                }
279 1
                break;
280
281 2
            case self::CRATE_ATTR_HTTP_BASIC_AUTH:
282 1
                $this->attributes['auth'] = $value;
283 1
                if (is_object($this->client) && is_array($value)) {
284 1
                    list($user, $password) = $value;
285 1
                    $this->client->setHttpBasicAuth($user, $password);
286 1
                }
287 1
                break;
288
289 1
            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 1
            default:
297 1
                throw new Exception\PDOException('Unsupported driver attribute');
298 1
        }
299 4
    }
300
301
    /**
302
     * {@inheritDoc}
303
     */
304 9
    public function getAttribute($attribute)
305
    {
306
        switch ($attribute) {
307 9
            case PDO::ATTR_PERSISTENT:
308 1
                return false;
309
310 8
            case PDO::ATTR_PREFETCH:
311 1
                return false;
312
313 7
            case PDO::ATTR_CLIENT_VERSION:
314 1
                return self::VERSION;
315
316 6
            case PDO::ATTR_SERVER_VERSION:
317
                return $this->client->getServerVersion();
318
319 6
            case PDO::ATTR_SERVER_INFO:
320
                return $this->client->getServerInfo();
321
322 6
            case PDO::ATTR_TIMEOUT:
323 1
                return $this->attributes['timeout'];
324
325 5
            case PDO::CRATE_ATTR_HTTP_BASIC_AUTH:
326
                return $this->attributes['auth'];
327
328 5
            case PDO::ATTR_DEFAULT_FETCH_MODE:
329 1
                return $this->attributes['defaultFetchMode'];
330
331 4
            case PDO::ATTR_ERRMODE:
332 1
                return $this->attributes['errorMode'];
333
334 3
            case PDO::ATTR_DRIVER_NAME:
335 1
                return static::DRIVER_NAME;
336
337 2
            case PDO::ATTR_STATEMENT_CLASS:
338
                return [$this->attributes['statementClass']];
339
340 2
            case PDO::CRATE_ATTR_DEFAULT_SCHEMA:
341
                return [$this->attributes['defaultSchema']];
342
343 2
            default:
344
                // PHP Switch is a lose comparison
345 2
                if ($attribute === PDO::ATTR_AUTOCOMMIT) {
346 1
                    return true;
347
                }
348
349 1
                throw new Exception\PDOException('Unsupported driver attribute');
350 2
        }
351
    }
352
353
    /**
354
     * {@inheritDoc}
355
     */
356 4
    public function quote($string, $parameter_type = PDO::PARAM_STR)
357
    {
358
        switch ($parameter_type) {
359 4
            case PDO::PARAM_INT:
360 1
                return (int)$string;
361
362 4
            case PDO::PARAM_BOOL:
363 1
                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 4
            case PDO::PARAM_NULL:
366 1
                return null;
367
368 3
            case PDO::PARAM_LOB:
369 1
                throw new Exception\UnsupportedException('This is not supported by crate.io');
370
371 2
            case PDO::PARAM_STR:
372 1
                throw new Exception\UnsupportedException('This is not supported, please use prepared statements.');
373
374 1
            default:
375 1
                throw new Exception\InvalidArgumentException('Unknown param type');
376 1
        }
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     */
382 1
    public static function getAvailableDrivers()
383
    {
384 1
        return array_merge(parent::getAvailableDrivers(), [static::DRIVER_NAME]);
385
    }
386
}
387