Completed
Push — m/develop-ide-guide ( 66eae7...9a28d3 )
by
unknown
04:28
created

PDO::setAttribute()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

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