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
Pull Request — master (#15)
by Patrick
02:18
created

Db::rollBack()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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
     * @var PdoFactoryInterface
58
     */
59
    private $pdoFactory;
60
61
    /**
62
     * Constructor.
63
     *
64
     * @param string|PDO            $hostDsnOrPdo A MySQL host, a dsn or an existing PDO instance.
65
     * @param string|null           $username
66
     * @param string|null           $password
67
     * @param string|null           $database
68
     * @param array                 $options
69
     * @param PdoFactoryInterface   $pdoFactory
70
     *
71
     * @deprecated  The signature of the constructor will change in version 1.0.0 and accept
72
     *              only a PDO dsn string as first parameter dropping support to inject a PDO instance or host string
73
     */
74 28
    public function __construct(
75
        $hostDsnOrPdo,
76
        $username = null,
77
        $password = null,
78
        $database = null,
79
        array $options = [],
80
        PdoFactoryInterface $pdoFactory = null
81
    ) {
82 28
        if ($hostDsnOrPdo instanceof PDO) {
83 28
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
84
                'Support to pass a PDO instance to the constructor is deprecated and will be removed in version 1.0.0.'
85 28
                . ' You need to pass in a PDO dsn in the future as first parameter.',
86 28
                E_USER_DEPRECATED
87
            );
88
89 28
            $this->pdo = $hostDsnOrPdo;
90 4
        } elseif (strpos($hostDsnOrPdo, ':') !== false) {
91 3
            $this->dsn = $hostDsnOrPdo;
92
        } else {
93 1
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
94
                'Support to pass a host and database to the constructor is deprecated and will be removed in version '
95 1
                . '1.0.0. You need to pass in a PDO dsn in the future as first parameter.',
96 1
                E_USER_DEPRECATED
97
            );
98 1
            $this->dsn = "mysql:host={$hostDsnOrPdo}" . ($database ? ";dbname={$database}" : '');
99
        }
100
101 28
        $this->username = $username;
102 28
        $this->password = $password;
103 28
        $this->options = $options;
104 28
        $this->pdoFactory = $pdoFactory ?? new PdoFactory();
105 28
    }
106
107
    /**
108
     */
109 18
    public function connect()
110
    {
111 18
        if ($this->isConnected()) {
112 15
            return;
113
        }
114
115 3
        $retries = isset($this->options['connectRetries']) ? $this->options['connectRetries'] : 0;
116
        do {
117
            try {
118 3
                $this->pdo = $this->pdoFactory->createPdo($this->dsn, $this->username, $this->password, $this->options);
119
120 2
                return;
121 2
            } catch (PDOException $e) {
122 2
                if ($this->isConnectionExceptionCausedByConnection($e) && $retries > 0) {
123
                    // Sleep for 100 - 500 ms until next retry
124 2
                    usleep(rand(100000, 500000));
125
                } else {
126 1
                    throw new ConnectionException($e);
127
                }
128
            }
129
130 2
        } while ($retries-- > 0);
131
    }
132
133
    /**
134
     * @param PDOException $exception
135
     * @return bool
136
     */
137 2
    private function isConnectionExceptionCausedByConnection(PDOException $exception)
138
    {
139 2
        return in_array($exception->getCode(), [
140 2
            2002, // Can't connect to MySQL server (Socket)
141
            2003, // Can't connect to MySQL server (TCP)
142
            2006, // MySQL server has gone away
143
            2013, // Lost connection to MySQL server during query
144
        ]);
145
    }
146
147
    /**
148
     * Close the database connection.
149
     */
150 1
    public function disconnect()
151
    {
152 1
        $this->pdo = null;
153 1
    }
154
155
    /**
156
     */
157 1
    public function reconnect()
158
    {
159 1
        $this->disconnect();
160 1
        $this->connect();
161 1
    }
162
163
    /**
164
     * Check if database connection is open.
165
     *
166
     * @return bool
167
     */
168 19
    public function isConnected()
169
    {
170 19
        return ($this->pdo instanceof PDO);
171
    }
172
173
    /**
174
     * Returns the PDO handle.
175
     *
176
     * Can be used to gain access to any special PDO methods.
177
     *
178
     * @return PDO
179
     */
180 4
    public function getPdo()
181
    {
182 4
        return $this->pdo;
183
    }
184
185
    /**
186
     * Creates and executes a PDO statement.
187
     *
188
     * @param string $sql
189
     * @param array  $parameters
190
     * @return PDOStatement
191
     */
192 7
    protected function executeQuery($sql, array $parameters = [])
193
    {
194 7
        $this->connect();
195
196 7
        $dbParameters = $this->prepareParameters($parameters);
197
        try {
198 6
            $pdoStatement = $this->pdo->prepare($sql);
199 5
            $pdoStatement->execute($dbParameters);
200
201 5
            return $pdoStatement;
202 1
        } catch (PDOException $e) {
203 1
            throw new QueryException($e, $sql, $dbParameters);
204
        }
205
    }
206
207
    /**
208
     * Execute an SQL statement and return the number of affected rows.
209
     *
210
     * @param string $sql
211
     * @param array  $parameters
212
     * @return int The number of rows affected
213
     */
214 4
    public function exec($sql, array $parameters = [])
215
    {
216 4
        $statement = $this->executeQuery($sql, $parameters);
217
218 2
        return $statement->rowCount();
219
    }
220
221
    /**
222
     * Execute an SQL statement and return the first row.
223
     *
224
     * @param string $sql
225
     * @param array  $parameters
226
     * @param bool   $indexedKeys
227
     * @return array|false
228
     */
229 1
    public function fetchRow($sql, array $parameters = [], $indexedKeys = false)
230
    {
231 1
        $statement = $this->executeQuery($sql, $parameters);
232
233 1
        return $statement->fetch($indexedKeys ? PDO::FETCH_NUM : PDO::FETCH_ASSOC);
234
    }
235
236
    /**
237
     * Execute an SQL statement and return all rows as an array.
238
     *
239
     * @param string $sql
240
     * @param array  $parameters
241
     * @param bool   $indexedKeys
242
     * @return array
243
     */
244 1
    public function fetchRows($sql, array $parameters = [], $indexedKeys = false)
245
    {
246 1
        $statement = $this->executeQuery($sql, $parameters);
247
248 1
        return $statement->fetchAll($indexedKeys ? PDO::FETCH_NUM : PDO::FETCH_ASSOC);
249
    }
250
251
    /**
252
     * Execute an SQL statement and return the first column of the first row.
253
     *
254
     * @param string $sql
255
     * @param array  $parameters
256
     * @return string|false
257
     */
258 1
    public function fetchValue($sql, array $parameters = [])
259
    {
260 1
        $statement = $this->executeQuery($sql, $parameters);
261
262 1
        return $statement->fetchColumn(0);
263
    }
264
265
    /**
266
     * Quote a value for a safe use in query (eg. bla'bla -> 'bla''bla').
267
     *
268
     * @param mixed $value
269
     * @return string
270
     */
271 1
    public function quote($value)
272
    {
273 1
        $this->connect();
274
275 1
        return $this->pdo->quote($value);
276
    }
277
278
    /**
279
     * Get id of the last inserted row.
280
     *
281
     * @return int
282
     */
283 1
    public function getLastInsertId()
284
    {
285 1
        $this->connect();
286
287 1
        return (int) $this->pdo->lastInsertId();
288
    }
289
290
    /**
291
     * Prepare parameters for database use.
292
     *
293
     * @param array $parameters
294
     * @return array
295
     */
296 7
    protected function prepareParameters(array $parameters = [])
297
    {
298 7
        foreach ($parameters as &$parameterValue) {
299 6
            if (is_bool($parameterValue)) {
300 1
                $parameterValue = (int) $parameterValue;
301 6
            } elseif ($parameterValue instanceof \DateTimeInterface) {
302 1
                $parameterValue = $parameterValue->format('Y-m-d H:i:s');
303 5
            } elseif (!is_scalar($parameterValue) && $parameterValue !== null) {
304 1
                throw new \InvalidArgumentException(
305 6
                    sprintf('Invalid db parameter type "%s"', gettype($parameterValue))
306
                );
307
            }
308
        }
309 6
        unset($parameterValue);
310
311 6
        return $parameters;
312
    }
313
314
    /**
315
     * Begin transaction (turns off autocommit mode).
316
     *
317
     * @param bool $onlyIfNoActiveTransaction
318
     * @return bool
319
     */
320 4
    public function beginTransaction($onlyIfNoActiveTransaction = false)
321
    {
322 4
        $this->connect();
323
324 4
        if ($onlyIfNoActiveTransaction && $this->hasActiveTransaction()) {
325 1
            return false;
326
        }
327
328 4
        $this->pdo->beginTransaction();
329 4
        $this->hasActiveTransaction = true;
330
331 4
        return true;
332
    }
333
334
    /**
335
     * Commits current active transaction (restores autocommit mode).
336
     */
337 2
    public function commit()
338
    {
339 2
        $this->connect();
340
341 2
        $this->pdo->commit();
342 2
        $this->hasActiveTransaction = false;
343 2
    }
344
345
    /**
346
     * Rolls back current active transaction (restores autocommit mode).
347
     */
348 2
    public function rollBack()
349
    {
350 2
        $this->connect();
351
352 2
        $this->pdo->rollBack();
353 2
        $this->hasActiveTransaction = false;
354 2
    }
355
356
    /**
357
     * @return bool
358
     */
359 3
    public function hasActiveTransaction()
360
    {
361 3
        return $this->hasActiveTransaction;
362
    }
363
364
    /**
365
     * @param string $table
366
     * @param array $data
367
     * @param bool $updateOnDuplicateKey
368
     * @return int The number of rows affected
369
     */
370 3
    public function insert($table, array $data, $updateOnDuplicateKey = false)
371
    {
372 3
        $sql = 'INSERT INTO `' . $table . '`';
373 3
        if (empty($data)) {
374 1
            $sql .= "\nVALUES ()";
375 1
            $parameters = [];
376
        } else {
377 2
            $fields = array_keys($data);
378 2
            $fieldsSql = '`' . implode('`, `', $fields) . '`';
379 2
            $placeholdersSql = implode(', ', array_fill(0, count($fields), '?'));
380
381 2
            $sql .= ' (' . $fieldsSql . ")\n";
382 2
            $sql .= 'VALUES (' . $placeholdersSql . ")\n";
383
384 2
            $parameters = array_values($data);
385
386 2
            if ($updateOnDuplicateKey) {
387 1
                $assignmentSql = $this->getAssignmentSql(array_fill_keys($fields, '?'));
388 1
                $sql .= 'ON DUPLICATE KEY UPDATE ' . $assignmentSql;
389 1
                $parameters = array_merge($parameters, $parameters);
390
            }
391
        }
392
393 3
        return $this->exec($sql, $parameters);
394
    }
395
396
    /**
397
     * @param array $assignmentData
398
     * @return string
399
     */
400 2
    protected function getAssignmentSql(array $assignmentData)
401
    {
402 2
        $assignments = [];
403 2
        foreach ($assignmentData as $field => $value) {
404 2
            $assignments[] = sprintf('`%s` = %s', $field, $value);
405
        }
406
407 2
        return implode(', ', $assignments);
408
    }
409
410
    /**
411
     * @param string $table
412
     * @param array  $data
413
     * @param string $whereSql
414
     * @param array  $whereParameters
415
     * @return int The number of rows affected
416
     */
417 1
    public function update($table, array $data, $whereSql = '', array $whereParameters = [])
418
    {
419 1
        $fields = array_keys($data);
420 1
        $assignmentSql = $this->getAssignmentSql(array_fill_keys($fields, '?'));
421
422 1
        $sql = 'UPDATE `' . $table . "`\n"
423 1
            . 'SET ' . $assignmentSql
424 1
            . ($whereSql ? "\nWHERE " . $whereSql : '')
425
        ;
426
427 1
        $parameters = array_merge(array_values($data), $whereParameters);
428
429 1
        return $this->exec($sql, $parameters);
430
    }
431
}
432