Issues (42)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/BaseConnection.php (13 issues)

Upgrade to new PHP Analysis Engine

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
Bug Best Practice introduced by
The return type of return new \yolk\database\query\Select($this); (yolk\database\query\Select) is incompatible with the return type declared by the interface yolk\contracts\database\DatabaseConnection::select of type yolk\contracts\database\...s\database\query\Select.

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 my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
111
	}
112
113
	public function insert() {
114
		return new query\Insert($this);
0 ignored issues
show
Bug Best Practice introduced by
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 my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
115
	}
116
117
	public function update() {
118
		return new query\Update($this);
0 ignored issues
show
Bug Best Practice introduced by
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 my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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
Bug Best Practice introduced by
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 my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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.

Loading history...
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
The parameter $expires is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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