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 ( 8cd4da...b79008 )
by Felix
12s
created

Db::getLastInsertId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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