Completed
Pull Request — master (#33)
by
unknown
37:28 queued 27:21
created

PDO   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 8

Test Coverage

Coverage 76.56%

Importance

Changes 28
Bugs 3 Features 4
Metric Value
wmc 57
c 28
b 3
f 4
lcom 3
cbo 8
dl 0
loc 358
ccs 98
cts 128
cp 0.7656
rs 6.4331

17 Methods

Rating   Name   Duplication   Size   Complexity  
A exec() 0 7 2
C __construct() 0 49 8
A parseDSN() 0 10 2
A computeURI() 0 4 1
A prepare() 0 11 2
A beginTransaction() 0 4 1
A commit() 0 4 1
A rollBack() 0 4 1
A inTransaction() 0 4 1
A query() 0 7 2
A lastInsertId() 0 4 1
A errorCode() 0 4 2
A errorInfo() 0 4 2
D setAttribute() 0 37 10
C getAttribute() 0 48 14
B quote() 0 22 6
A getAvailableDrivers() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like PDO often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PDO, and based on these observations, apply Extract Interface, too.

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          = self::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 4
     * {@inheritDoc}
81
     *
82 4
     * @param string     $dsn      The HTTP endpoint to call
83 1
     * @param null       $username Unused
84 4
     * @param null       $passwd   Unused
85
     * @param null|array $options  Attributes to set on the PDO
86 4
     */
87 4
    public function __construct($dsn, $username, $passwd, $options)
88
    {
89 4
        foreach (ArrayUtils::toArray($options) as $attribute => $value) {
90 4
            $this->setAttribute($attribute, $value);
91 4
        }
92
93 4
        $dsnParts = self::parseDSN($dsn);
94 1
        $uri     = self::computeURI($dsnParts[0]);
95 1
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 4
                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 31
            }
134
        };
135 31
    }
136
137 31
    /**
138 3
     * Extract host:port pairs out of the DSN string
139
     *
140
     * @param string $dsn The DSN string
141 28
     *
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 27
152
        return array_slice($matches, 1);
153 27
    }
154
155
    /**
156
     * Compute a URI for usage with the HTTP client
157
     *
158
     * @param string $server A host:port string
159 1
     *
160
     * @return string An URI which can be used by the HTTP client
161 1
     */
162
    private static function computeURI($server)
163 1
    {
164
        return 'http://' . $server . '/_sql';
165
    }
166
167
    /**
168 1
     * {@inheritDoc}
169
     */
170
    public function prepare($statement, $options = null)
171
    {
172
        $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 1
            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 1
    /**
183
     * {@inheritDoc}
184 1
     */
185
    public function beginTransaction()
186
    {
187
        return true;
188
    }
189
190 1
    /**
191
     * {@inheritDoc}
192 1
     */
193
    public function commit()
194
    {
195
        return true;
196
    }
197
198 1
    /**
199
     * {@inheritDoc}
200 1
     */
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 1
    public function query($statement)
229
    {
230 1
        $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 5
    /**
253
     * {@inheritDoc}
254
     */
255 5
    public function errorInfo()
256 1
    {
257 1
        return $this->lastStatement === null ? null : $this->lastStatement->errorInfo();
258
    }
259 4
260 1
    /**
261 1
     * {@inheritDoc}
262
     */
263 3
    public function setAttribute($attribute, $value)
264 1
    {
265 1
        switch ($attribute) {
266 1
            case self::ATTR_DEFAULT_FETCH_MODE:
267 1
                $this->attributes['defaultFetchMode'] = $value;
268 1
                break;
269
270 2
            case self::ATTR_ERRMODE:
271 1
                $this->attributes['errorMode'] = $value;
272 1
                break;
273 1
274 1
            case self::ATTR_TIMEOUT:
275 1
                $this->attributes['timeout'] = (int)$value;
276 1
                if (is_object($this->client)) {
277
                    $this->client->setTimeout((int)$value);
278 1
                }
279 1
                break;
280 1
281 4
            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 9
                }
287
                break;
288
289 9
            case self::CRATE_ATTR_DEFAULT_SCHEMA:
290 1
                $this->attributes['defaultSchema'] = $value;
291
                if (is_object($this->client)) {
292 8
                    $this->client->setHttpHeader('default-schema', $value);
293 1
                }
294
                break;
295 7
296 1
            default:
297
                throw new Exception\PDOException('Unsupported driver attribute');
298 6
        }
299
    }
300
301 6
    /**
302
     * {@inheritDoc}
303
     */
304 6
    public function getAttribute($attribute)
305 1
    {
306
        switch ($attribute) {
307 5
            case PDO::ATTR_PERSISTENT:
308
                return false;
309
310 5
            case PDO::ATTR_PREFETCH:
311 1
                return false;
312
313 4
            case PDO::ATTR_CLIENT_VERSION:
314 1
                return self::VERSION;
315
316 3
            case PDO::ATTR_SERVER_VERSION:
317 1
                return $this->client->getServerVersion();
318
319 2
            case PDO::ATTR_SERVER_INFO:
320
                return $this->client->getServerInfo();
321
322 2
            case PDO::ATTR_TIMEOUT:
323
                return $this->attributes['timeout'];
324 2
325 1
            case PDO::CRATE_ATTR_HTTP_BASIC_AUTH:
326
                return $this->attributes['auth'];
327
328 1
            case PDO::ATTR_DEFAULT_FETCH_MODE:
329 2
                return $this->attributes['defaultFetchMode'];
330
331
            case PDO::ATTR_ERRMODE:
332
                return $this->attributes['errorMode'];
333
334
            case PDO::ATTR_DRIVER_NAME:
335 4
                return static::DRIVER_NAME;
336
337
            case PDO::ATTR_STATEMENT_CLASS:
338 4
                return [$this->attributes['statementClass']];
339 1
340
            case PDO::CRATE_ATTR_DEFAULT_SCHEMA:
341 4
                return $this->attributes['defaultSchema'];
342 1
343
            default:
344 4
                // PHP Switch is a lose comparison
345 1
                if ($attribute === PDO::ATTR_AUTOCOMMIT) {
346
                    return true;
347 3
                }
348 1
349
                throw new Exception\PDOException('Unsupported driver attribute');
350 2
        }
351 1
    }
352
353 1
    /**
354 1
     * {@inheritDoc}
355 1
     */
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 1
362
            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
            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