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 | * This file is part of Yolk - Gamer Network's PHP Framework. |
||
4 | * |
||
5 | * Copyright (c) 2014 Gamer Network Ltd. |
||
6 | * |
||
7 | * Distributed under the MIT License, a copy of which is available in the |
||
8 | * LICENSE file that was bundled with this package, or online at: |
||
9 | * https://github.com/gamernetwork/yolk-database |
||
10 | */ |
||
11 | |||
12 | namespace yolk\database; |
||
13 | |||
14 | use yolk\contracts\database\DatabaseConnection; |
||
15 | use yolk\contracts\profiler\ProfilerAwareTrait; |
||
16 | use yolk\contracts\profiler\ProfilerAware; |
||
17 | use yolk\contracts\support\Dumpable; |
||
18 | |||
19 | use yolk\database\exceptions\DatabaseException; |
||
20 | use yolk\database\exceptions\ConnectionException; |
||
21 | use yolk\database\exceptions\NotConnectedException; |
||
22 | use yolk\database\exceptions\QueryException; |
||
23 | use yolk\database\exceptions\TransactionException; |
||
24 | |||
25 | /** |
||
26 | * A wrapper for PDO that provides some handy extra functions and streamlines everything else. |
||
27 | */ |
||
28 | abstract class BaseConnection implements DatabaseConnection, ProfilerAware, Dumpable { |
||
29 | |||
30 | use ProfilerAwareTrait; |
||
31 | |||
32 | /** |
||
33 | * Connection details. |
||
34 | * @var DSN |
||
35 | */ |
||
36 | protected $dsn = null; |
||
37 | |||
38 | /** |
||
39 | * Underlying PDO object. |
||
40 | * @var \PDO |
||
41 | */ |
||
42 | protected $pdo = null; |
||
43 | |||
44 | /** |
||
45 | * Prepared statement cache. |
||
46 | * @var array |
||
47 | */ |
||
48 | protected $statements = []; |
||
49 | |||
50 | /** |
||
51 | * Create a new database connection. |
||
52 | * |
||
53 | * @param DSN $dsn a DSN instance describing the database connection details |
||
54 | */ |
||
55 | public function __construct( DSN $dsn ) { |
||
56 | |||
57 | $this->dsn = $dsn; |
||
58 | |||
59 | // check for PDO extension |
||
60 | if( !extension_loaded('pdo') ) |
||
61 | throw new DatabaseException('The PDO extension is required but the extension is not loaded'); |
||
62 | |||
63 | // check the PDO driver is available |
||
64 | elseif( !in_array($this->dsn->type, \PDO::getAvailableDrivers()) ) |
||
65 | throw new DatabaseException("The {$this->dsn->type} PDO driver is not currently installed"); |
||
66 | |||
67 | } |
||
68 | |||
69 | public function connect() { |
||
70 | |||
71 | if( $this->pdo instanceof \PDO ) |
||
72 | return true; |
||
73 | |||
74 | try { |
||
75 | |||
76 | $this->pdo = new \PDO( |
||
77 | $this->dsn->getConnectionString(), |
||
78 | $this->dsn->user, |
||
79 | $this->dsn->pass, |
||
80 | $this->dsn->options |
||
81 | ); |
||
82 | |||
83 | $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); |
||
84 | $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); // always use exceptions |
||
85 | |||
86 | $this->setCharacterSet( |
||
87 | $this->getOption('charset', 'UTF8'), |
||
88 | $this->getOption('collation') |
||
89 | ); |
||
90 | |||
91 | } |
||
92 | catch( \PDOException $e ) { |
||
93 | throw new ConnectionException($e->getMessage(), $e->getCode(), $e); |
||
94 | } |
||
95 | |||
96 | return true; |
||
97 | |||
98 | } |
||
99 | |||
100 | public function disconnect() { |
||
101 | $this->pdo = null; |
||
102 | return true; |
||
103 | } |
||
104 | |||
105 | public function isConnected() { |
||
106 | return $this->pdo instanceof \PDO; |
||
107 | } |
||
108 | |||
109 | public function select() { |
||
110 | return new query\Select($this); |
||
0 ignored issues
–
show
|
|||
111 | } |
||
112 | |||
113 | public function insert() { |
||
114 | return new query\Insert($this); |
||
0 ignored issues
–
show
The return type of
return new \yolk\database\query\Insert($this); (yolk\database\query\Insert ) is incompatible with the return type declared by the interface yolk\contracts\database\DatabaseConnection::insert of type yolk\contracts\database\...s\database\query\Insert .
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 ![]() |
|||
115 | } |
||
116 | |||
117 | public function update() { |
||
118 | return new query\Update($this); |
||
0 ignored issues
–
show
The return type of
return new \yolk\database\query\Update($this); (yolk\database\query\Update ) is incompatible with the return type declared by the interface yolk\contracts\database\DatabaseConnection::update of type yolk\contracts\database\...s\database\query\Update .
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 ![]() |
|||
119 | } |
||
120 | |||
121 | public function replace() { |
||
122 | return new query\Replace($this); |
||
123 | } |
||
124 | |||
125 | public function delete() { |
||
126 | return new query\Delete($this); |
||
0 ignored issues
–
show
The return type of
return new \yolk\database\query\Delete($this); (yolk\database\query\Delete ) is incompatible with the return type declared by the interface yolk\contracts\database\DatabaseConnection::delete of type yolk\contracts\database\...s\database\query\Delete .
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 ![]() |
|||
127 | } |
||
128 | |||
129 | public function prepare( $statement ) { |
||
130 | |||
131 | if( ! $statement instanceof \PDOStatement ) { |
||
132 | |||
133 | $this->connect(); |
||
134 | |||
135 | $statement = trim($statement); |
||
136 | |||
137 | if( !isset($this->statements[$statement]) ) |
||
138 | $this->statements[$statement] = $this->pdo->prepare($statement); |
||
139 | |||
140 | $statement = $this->statements[$statement]; |
||
141 | |||
142 | } |
||
143 | |||
144 | return $statement; |
||
145 | |||
146 | } |
||
147 | |||
148 | public function query( $statement, $params = [] ) { |
||
149 | |||
150 | $this->connect(); |
||
151 | |||
152 | // TODO: profiler start |
||
153 | $this->profiler && $this->profiler->start('Query'); |
||
154 | |||
155 | try { |
||
156 | |||
157 | $statement = $this->prepare($statement); |
||
158 | |||
159 | // single parameters don't have to be passed in an array - do that here |
||
160 | if( !is_array($params) ) |
||
161 | $params = array($params); |
||
162 | |||
163 | $this->bindParams($statement, $params); |
||
164 | |||
165 | $start = microtime(true); |
||
166 | $statement->execute(); |
||
167 | $duration = microtime(true) - $start; |
||
168 | |||
169 | } |
||
170 | catch( \PDOException $e ) { |
||
171 | throw new QueryException($e->getMessage(), $e->getCode(), $e); |
||
172 | } |
||
173 | |||
174 | // TODO: profiler stop + record |
||
175 | if( $this->profiler ) { |
||
176 | $this->profiler->stop('Query'); |
||
177 | // remove all whitespace at start of lines |
||
178 | $this->profiler->query(preg_replace("/^\s*/m", "", trim($statement->queryString)), $params, $duration); |
||
179 | } |
||
180 | |||
181 | return $statement; |
||
182 | |||
183 | } |
||
184 | |||
185 | public function execute( $statement, $params = [] ) { |
||
186 | $statement = $this->query($statement, $params); |
||
187 | return $statement->rowCount(); |
||
188 | } |
||
189 | |||
190 | View Code Duplication | public function getAll( $statement, $params = [], $expires = 0, $key = '' ) { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
191 | return $this->getResult( |
||
192 | $statement, |
||
193 | $params, |
||
194 | $expires, |
||
195 | $key, |
||
196 | function( \PDOStatement $statement ) { |
||
197 | $result = $statement->fetchAll(); |
||
198 | if( $result === false ) |
||
199 | $result = []; |
||
200 | return $result; |
||
201 | } |
||
202 | ); |
||
203 | } |
||
204 | |||
205 | public function getAssoc( $statement, $params = [], $expires = 0, $key = '' ) { |
||
206 | return $this->getResult( |
||
207 | $statement, |
||
208 | $params, |
||
209 | $expires, |
||
210 | $key, |
||
211 | function( \PDOStatement $statement ) { |
||
212 | $result = []; |
||
213 | while( $row = $statement->fetch() ) { |
||
214 | $key = array_shift($row); |
||
215 | $result[$key] = count($row) == 1 ? array_shift($row) : $row; |
||
216 | } |
||
217 | return $result; |
||
218 | } |
||
219 | ); |
||
220 | } |
||
221 | |||
222 | public function getAssocMulti( $statement, $params = [], $expires = 0, $key = '' ) { |
||
223 | return $this->getResult( |
||
224 | $statement, |
||
225 | $params, |
||
226 | $expires, |
||
227 | $key, |
||
228 | function( \PDOStatement $statement ) { |
||
229 | $result = []; |
||
230 | while( $row = $statement->fetch() ) { |
||
231 | $k1 = array_shift($row); |
||
232 | $k2 = array_shift($row); |
||
233 | $v = count($row) == 1 ? array_shift($row) : $row; |
||
234 | if( !isset($result[$k1]) ) |
||
235 | $result[$k1] = []; |
||
236 | $result[$k1][$k2] = $v; |
||
237 | } |
||
238 | return $result; |
||
239 | } |
||
240 | ); |
||
241 | } |
||
242 | |||
243 | View Code Duplication | public function getRow( $statement, $params = [], $expires = 0, $key = '' ) { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
244 | return $this->getResult( |
||
245 | $statement, |
||
246 | $params, |
||
247 | $expires, |
||
248 | $key, |
||
249 | function( \PDOStatement $statement ) { |
||
250 | $result = $statement->fetch(); |
||
251 | if( $result === false ) |
||
252 | $result = []; |
||
253 | return $result; |
||
254 | } |
||
255 | ); |
||
256 | } |
||
257 | |||
258 | View Code Duplication | public function getCol( $statement, $params = [], $expires = 0, $key = '' ) { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
259 | return $this->getResult( |
||
260 | $statement, |
||
261 | $params, |
||
262 | $expires, |
||
263 | $key, |
||
264 | function( \PDOStatement $statement ) { |
||
265 | $result = []; |
||
266 | while( $row = $statement->fetch() ) { |
||
267 | $result[] = array_shift($row); |
||
268 | } |
||
269 | return $result; |
||
270 | } |
||
271 | ); |
||
272 | } |
||
273 | |||
274 | View Code Duplication | public function getOne( $statement, $params = [], $expires = 0, $key = '' ) { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
275 | return $this->getResult( |
||
276 | $statement, |
||
277 | $params, |
||
278 | $expires, |
||
279 | $key, |
||
280 | function( \PDOStatement $statement ) { |
||
281 | $result = $statement->fetchColumn(); |
||
282 | if( $result === false ) |
||
283 | $result = null; |
||
284 | return $result; |
||
285 | } |
||
286 | ); |
||
287 | } |
||
288 | |||
289 | public function begin() { |
||
290 | |||
291 | $this->connect(); |
||
292 | |||
293 | try { |
||
294 | return $this->pdo->beginTransaction(); |
||
295 | } |
||
296 | catch( \PDOException $e ) { |
||
297 | throw new TransactionException($e->getMessage(), $e->getCode(), $e); |
||
298 | } |
||
299 | |||
300 | } |
||
301 | |||
302 | View Code Duplication | public function commit() { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
303 | |||
304 | if( !$this->isConnected() ) |
||
305 | throw new NotConnectedException(); |
||
306 | |||
307 | try { |
||
308 | return $this->pdo->commit(); |
||
309 | } |
||
310 | catch( \PDOException $e ) { |
||
311 | throw new TransactionException($e->getMessage(), $e->getCode(), $e); |
||
312 | } |
||
313 | |||
314 | } |
||
315 | |||
316 | View Code Duplication | public function rollback() { |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
317 | |||
318 | if( !$this->isConnected() ) |
||
319 | throw new NotConnectedException(); |
||
320 | |||
321 | try { |
||
322 | return $this->pdo->rollBack(); |
||
323 | } |
||
324 | catch( \PDOException $e ) { |
||
325 | throw new TransactionException($e->getMessage(), $e->getCode(), $e); |
||
326 | } |
||
327 | |||
328 | } |
||
329 | |||
330 | public function inTransaction() { |
||
331 | return $this->isConnected() ? $this->pdo->inTransaction() : false; |
||
332 | } |
||
333 | |||
334 | public function insertId( $name = '' ) { |
||
335 | if( !$this->isConnected() ) |
||
336 | throw new NotConnectedException(); |
||
337 | return $this->pdo->lastInsertId($name); |
||
338 | } |
||
339 | |||
340 | public function quote( $value, $type = \PDO::PARAM_STR ) { |
||
341 | $this->connect(); |
||
342 | return $this->pdo->quote($value, $type); |
||
343 | } |
||
344 | |||
345 | public function quoteIdentifier( $name ) { |
||
346 | |||
347 | $name = trim($name); |
||
348 | |||
349 | if( $name == '*' ) |
||
350 | return $name; |
||
351 | |||
352 | // ANSI-SQL (everything else) says to use double quotes to quote identifiers |
||
353 | $char = '"'; |
||
354 | |||
355 | // MySQL uses backticks cos it's special |
||
356 | if( $this->dsn->isMySQL() ) |
||
357 | $char = '`'; |
||
358 | |||
359 | return $char. $name. $char; |
||
360 | |||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Execute a raw SQL string and return the number of affected rows. |
||
365 | * Primarily used for DDL queries. Do not use this with: |
||
366 | * - Anything (data/parameters/etc) that comes from userland |
||
367 | * - Select queries - the answer will always be 0 as no rows are affected. |
||
368 | * - Everyday queries - use query() or execute() |
||
369 | * @param string $sql the SQL statement to exexcute |
||
370 | * @return integer the number of rows affected by the statement |
||
371 | */ |
||
372 | public function rawExec( $sql ) { |
||
373 | |||
374 | $this->connect(); |
||
375 | |||
376 | try { |
||
377 | return $this->pdo->exec($sql); |
||
378 | } |
||
379 | catch( \PDOException $e ) { |
||
380 | throw new QueryException($e->getMessage(), $e->getCode(), $e); |
||
381 | } |
||
382 | |||
383 | } |
||
384 | |||
385 | public function dump( $dumper = '\\yolk\\debug\\TextDumper', $depth = 1 ) { |
||
386 | if( $depth > 1 ) |
||
387 | return $this->dsn->toString(); |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Bind named and positional parameters to a PDOStatement. |
||
392 | * @param PDOStatement $statement |
||
393 | * @param array $params |
||
394 | * @return void |
||
395 | */ |
||
396 | protected function bindParams( \PDOStatement $statement, array $params ) { |
||
397 | foreach( $params as $name => $value ) { |
||
398 | |||
399 | if( is_int($value) ) { |
||
400 | $type = \PDO::PARAM_INT; |
||
401 | } |
||
402 | else { |
||
403 | $type = \PDO::PARAM_STR; |
||
404 | } |
||
405 | |||
406 | // handle positional (?) and named (:name) parameters |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
37% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
407 | $name = is_numeric($name) ? (int) $name + 1 : ":{$name}"; |
||
408 | |||
409 | $statement->bindValue($name, $value, $type); |
||
410 | |||
411 | } |
||
412 | } |
||
413 | |||
414 | /** |
||
415 | * Perform a select query and use a callback to extract a result. |
||
416 | * @param \PDOStatement|string $statement an existing PDOStatement object or a SQL string. |
||
417 | * @param array $params an array of parameters to pass into the query. |
||
418 | * @param integer $expires number of seconds to cache the result for if caching is enabled |
||
419 | * @param string $key cache key used to store the result |
||
420 | * @param \Closure $callback function to yield a result from the executed statement |
||
421 | * @return array |
||
422 | */ |
||
423 | protected function getResult( $statement, $params, $expires, $key, \Closure $callback ) { |
||
0 ignored issues
–
show
|
|||
424 | |||
425 | // TODO: check cache |
||
426 | |||
427 | $statement = $this->query($statement, $params); |
||
428 | |||
429 | $result = $callback($statement); |
||
430 | |||
431 | // TODO: store in cache |
||
432 | |||
433 | return $result; |
||
434 | |||
435 | } |
||
436 | |||
437 | protected function getOption( $option, $default = null ) { |
||
438 | return isset($this->dsn->options[$option]) ? $this->dsn->options[$option] : $default; |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * Make sure the connection is using the correct character set |
||
443 | * |
||
444 | * @param string $charset the character set to use for the connection |
||
445 | * @param string $collation the collation method to use for the connection |
||
446 | * @return self |
||
447 | */ |
||
448 | protected function setCharacterSet( $charset, $collation = '' ) { |
||
449 | |||
450 | if( !$charset ) |
||
451 | throw new DatabaseException('No character set specified'); |
||
452 | |||
453 | $sql = 'SET NAMES '. $this->pdo->quote($charset); |
||
454 | |||
455 | if( $collation ) |
||
456 | $sql .= ' COLLATE '. $this->pdo->quote($collation); |
||
457 | |||
458 | $this->pdo->exec($sql); |
||
459 | |||
460 | return $this; |
||
461 | |||
462 | } |
||
463 | |||
464 | } |
||
465 | |||
466 | // EOF |
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.