This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
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
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.');
}
![]() |
|||
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 |
If you suppress an error, we recommend checking for the error condition explicitly: