GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( f500e6...8bb85a )
by Andreas
02:52
created

Db   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 377
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 76.74%

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 2
dl 0
loc 377
ccs 99
cts 129
cp 0.7674
rs 8.3673
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 4
C connect() 0 38 8
A isConnectionExceptionCausedByConnection() 0 9 1
A disconnect() 0 4 1
A reconnect() 0 5 1
A isConnected() 0 4 1
A getPdo() 0 4 1
A executeQuery() 0 14 2
A exec() 0 6 1
A fetchRow() 0 6 2
A fetchRows() 0 6 2
A fetchValue() 0 6 1
A quote() 0 6 1
A getLastInsertId() 0 6 1
A beginTransaction() 0 13 3
A commit() 0 7 1
A rollBack() 0 7 1
A hasActiveTransaction() 0 4 1
B prepareParameters() 0 17 6
A insert() 0 12 3
A update() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like Db 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 Db, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Starlit Db.
4
 *
5
 * @copyright Copyright (c) 2016 Starweb AB
6
 * @license   BSD 3-Clause
7
 */
8
9
namespace Starlit\Db;
10
11
use Starlit\Db\Exception\ConnectionException;
12
use Starlit\Db\Exception\QueryException;
13
use \PDO;
14
use \PDOException;
15
use \PDOStatement;
16
17
/**
18
 * Extended PDO database wrapper.
19
 *
20
 * @author Andreas Nilsson <http://github.com/jandreasn>
21
 */
22
class Db
23
{
24
    /**
25
     * Database handle/connection.
26
     *
27
     * @var PDO
28
     */
29
    protected $pdo;
30
31
    /**
32
     * @var string
33
     */
34
    protected $dsn;
35
36
    /**
37
     * @var string
38
     */
39
    protected $username;
40
41
    /**
42
     * @var string
43
     */
44
    protected $password;
45
46
    /**
47
     * @var array
48
     */
49
    protected $options;
50
51
    /**
52
     * @var bool
53
     */
54
    protected $hasActiveTransaction = false;
55
56
    /**
57
     * Constructor.
58
     *
59
     * @param string|PDO  $hostDsnOrPdo A MySQL host, a dsn or an existing PDO instance.
60
     * @param string|null $username
61
     * @param string|null $password
62
     * @param string|null $database
63
     * @param array       $options
64
     */
65 21
    public function __construct(
66
        $hostDsnOrPdo,
67
        $username = null,
68
        $password = null,
69
        $database = null,
70
        array $options = []
71
    ) {
72 21
        if ($hostDsnOrPdo instanceof PDO) {
73 21
            $this->pdo = $hostDsnOrPdo;
74 21
        } elseif (strpos($hostDsnOrPdo, ':') !== false) {
75
            $this->dsn = $hostDsnOrPdo;
76
        } else {
77
            $this->dsn = "mysql:host={$hostDsnOrPdo}" . ($database ? ";dbname={$database}" : '');
78
        }
79
80 21
        $this->username = $username;
81 21
        $this->password = $password;
82 21
        $this->options = $options;
83 21
    }
84
85
    /**
86
     */
87 15
    public function connect()
88
    {
89 15
        if ($this->isConnected()) {
90 15
            return;
91
        }
92
93
        $retries = isset($this->options['connectRetries'])? $this->options['connectRetries'] : 0;
94
        do {
95
            try {
96
                $defaultPdoOptions = [
97
                    PDO::ATTR_TIMEOUT            => 5,
98
                    // We want emulation by default (faster for single queries). Disable if you want to
99
                    // use proper native prepared statements
100
                    PDO::ATTR_EMULATE_PREPARES   => true,
101
                    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
102
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
103
                ];
104
                $pdoOptions = $defaultPdoOptions + (isset($this->options['pdo']) ? $this->options['pdo'] : []);
105
106
                $this->pdo = new PDO(
107
                    $this->dsn,
108
                    $this->username,
109
                    $this->password,
110
                    $pdoOptions
111
                );
112
113
                return;
114
            } catch (PDOException $e) {
115
                if ($this->isConnectionExceptionCausedByConnection($e) && $retries > 0) {
116
                    // Sleep for 100 - 500 ms until next retry
117
                    usleep(rand(100000, 500000));
118
                } else {
119
                    throw new ConnectionException($e);
120
                }
121
            }
122
123
        } while ($retries-- > 0);
124
    }
125
126
    /**
127
     * @param PDOException $exception
128
     * @return bool
129
     */
130
    private function isConnectionExceptionCausedByConnection(PDOException $exception)
131
    {
132
        return in_array($exception->getCode(), [
133
            2002, // Can't connect to MySQL server (Socket)
134
            2003, // Can't connect to MySQL server (TCP)
135
            2006, // MySQL server has gone away
136
            2013, // Lost connection to MySQL server during query
137
        ]);
138
    }
139
140
    /**
141
     * Close the database connection.
142
     */
143 1
    public function disconnect()
144
    {
145 1
        $this->pdo = null;
146 1
    }
147
148
    /**
149
     */
150 1
    public function reconnect()
151
    {
152 1
        $this->disconnect();
153 1
        $this->connect();
154 1
    }
155
156
    /**
157
     * Check if database connection is open.
158
     *
159
     * @return bool
160
     */
161 16
    public function isConnected()
162
    {
163 16
        return ($this->pdo instanceof PDO);
164
    }
165
166
    /**
167
     * Returns the PDO handle.
168
     *
169
     * Can be used to gain access to any special PDO methods.
170
     *
171
     * @return PDO
172
     */
173 2
    public function getPdo()
174
    {
175 2
        return $this->pdo;
176
    }
177
178
    /**
179
     * Creates and executes a PDO statement.
180
     *
181
     * @param string $sql
182
     * @param array  $parameters
183
     * @return PDOStatement
184
     */
185 7
    protected function executeQuery($sql, array $parameters = [])
186
    {
187 7
        $this->connect();
188
189 7
        $dbParameters = $this->prepareParameters($parameters);
190
        try {
191 6
            $pdoStatement = $this->pdo->prepare($sql);
192 5
            $pdoStatement->execute($dbParameters);
193
194 5
            return $pdoStatement;
195 1
        } catch (PDOException $e) {
196 1
            throw new QueryException($e, $sql, $dbParameters);
197
        }
198
    }
199
200
    /**
201
     * Execute an SQL statement and return the number of affected rows.
202
     *
203
     * @param string $sql
204
     * @param array  $parameters
205
     * @return int The number of rows affected
206
     */
207 4
    public function exec($sql, array $parameters = [])
208
    {
209 4
        $statement = $this->executeQuery($sql, $parameters);
210
211 2
        return $statement->rowCount();
212
    }
213
214
    /**
215
     * Execute an SQL statement and return the first row.
216
     *
217
     * @param string $sql
218
     * @param array  $parameters
219
     * @param bool   $indexedKeys
220
     * @return array|false
221
     */
222 1
    public function fetchRow($sql, array $parameters = [], $indexedKeys = false)
223
    {
224 1
        $statement = $this->executeQuery($sql, $parameters);
225
226 1
        return $statement->fetch($indexedKeys ? PDO::FETCH_NUM : PDO::FETCH_ASSOC);
227
    }
228
229
    /**
230
     * Execute an SQL statement and return all rows as an array.
231
     *
232
     * @param string $sql
233
     * @param array  $parameters
234
     * @param bool   $indexedKeys
235
     * @return array
236
     */
237 1
    public function fetchRows($sql, array $parameters = [], $indexedKeys = false)
238
    {
239 1
        $statement = $this->executeQuery($sql, $parameters);
240
241 1
        return $statement->fetchAll($indexedKeys ? PDO::FETCH_NUM : PDO::FETCH_ASSOC);
242
    }
243
244
    /**
245
     * Execute an SQL statement and return the first column of the first row.
246
     *
247
     * @param string $sql
248
     * @param array  $parameters
249
     * @return string|false
250
     */
251 1
    public function fetchValue($sql, array $parameters = [])
252
    {
253 1
        $statement = $this->executeQuery($sql, $parameters);
254
255 1
        return $statement->fetchColumn(0);
256
    }
257
258
    /**
259
     * Quote a value for a safe use in query (eg. bla'bla -> 'bla''bla').
260
     *
261
     * @param mixed $value
262
     * @return string
263
     */
264 1
    public function quote($value)
265
    {
266 1
        $this->connect();
267
268 1
        return $this->pdo->quote($value);
269
    }
270
271
    /**
272
     * Get id of the last inserted row.
273
     *
274
     * @return int
275
     */
276 1
    public function getLastInsertId()
277
    {
278 1
        $this->connect();
279
280 1
        return (int) $this->pdo->lastInsertId();
281
    }
282
283
    /**
284
     * Prepare parameters for database use.
285
     *
286
     * @param array $parameters
287
     * @return array
288
     */
289 7
    protected function prepareParameters(array $parameters = [])
290
    {
291 7
        foreach ($parameters as &$parameterValue) {
292 6
            if (is_bool($parameterValue)) {
293 1
                $parameterValue = (int) $parameterValue;
294 6
            } elseif ($parameterValue instanceof \DateTimeInterface) {
295 1
                $parameterValue = $parameterValue->format('Y-m-d H:i:s');
296 6
            } elseif (!is_scalar($parameterValue) && $parameterValue !== null) {
297 1
                throw new \InvalidArgumentException(
298 1
                    sprintf('Invalid db parameter type "%s"', gettype($parameterValue))
299 1
                );
300
            }
301 7
        }
302 6
        unset($parameterValue);
303
304 6
        return $parameters;
305
    }
306
307
    /**
308
     * Begin transaction (turns off autocommit mode).
309
     *
310
     * @param bool $onlyIfNoActiveTransaction
311
     * @return bool
312
     */
313 4
    public function beginTransaction($onlyIfNoActiveTransaction = false)
314
    {
315 4
        $this->connect();
316
317 4
        if ($onlyIfNoActiveTransaction && $this->hasActiveTransaction()) {
318 1
            return false;
319
        }
320
321 4
        $this->pdo->beginTransaction();
322 4
        $this->hasActiveTransaction = true;
323
324 4
        return true;
325
    }
326
327
    /**
328
     * Commits current active transaction (restores autocommit mode).
329
     */
330 2
    public function commit()
331
    {
332 2
        $this->connect();
333
334 2
        $this->pdo->commit();
335 2
        $this->hasActiveTransaction = false;
336 2
    }
337
338
    /**
339
     * Rolls back current active transaction (restores autocommit mode).
340
     */
341 2
    public function rollBack()
342
    {
343 2
        $this->connect();
344
345 2
        $this->pdo->rollBack();
346 2
        $this->hasActiveTransaction = false;
347 2
    }
348
349
    /**
350
     * @return bool
351
     */
352 3
    public function hasActiveTransaction()
353
    {
354 3
        return $this->hasActiveTransaction;
355
    }
356
357
    /**
358
     * @param string $table
359
     * @param array $data
360
     * @return int The number of rows affected
361
     */
362 1
    public function insert($table, array $data)
363
    {
364 1
        $fields = array_keys($data);
365 1
        $valuePlaceholders = implode(', ', array_fill(0, count($fields), '?'));
366
367 1
        $sql = 'INSERT INTO `' . $table . '` '
368 1
            . ($fields ? '(`' . implode('`, `', $fields) . '`)' : '') . "\n"
369 1
            . ($fields ? 'VALUES (' . $valuePlaceholders . ')' : 'VALUES ()')
370 1
        ;
371
372 1
        return $this->exec($sql, array_values($data));
373
    }
374
375
    /**
376
     * @param string $table
377
     * @param array  $data
378
     * @param string $whereSql
379
     * @param array  $whereParameters
380
     * @return int The number of rows affected
381
     */
382 1
    public function update($table, array $data, $whereSql = '', array $whereParameters = [])
383
    {
384 1
        $setStrings = [];
385 1
        foreach (array_keys($data) as $field) {
386 1
            $setStrings[] = '`' . $field . '` = ?';
387 1
        }
388
389 1
        $sql = 'UPDATE `' . $table . "`\n"
390 1
            . 'SET ' . implode(', ', $setStrings)
391 1
            . ($whereSql ? "\nWHERE " . $whereSql : '')
392 1
        ;
393
394 1
        $parameters = array_merge(array_values($data), $whereParameters);
395
396 1
        return $this->exec($sql, $parameters);
397
    }
398
}
399