Completed
Push — master ( 15dd9f...8679ea )
by smiley
03:53
created

PDODriver::multi_callback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
/**
3
 *
4
 * @filesource   PDODriver.php
5
 * @created      04.11.2015
6
 * @package      chillerlan\Database\Drivers\PDO
7
 * @author       Smiley <[email protected]>
8
 * @copyright    2015 Smiley
9
 * @license      MIT
10
 */
11
12
namespace chillerlan\Database\Drivers\PDO;
13
14
use chillerlan\Database\DBException;
15
use chillerlan\Database\Drivers\DBDriverAbstract;
16
use PDO;
17
use PDOException;
18
use PDOStatement;
19
20
/**
21
 * Class PDODriver
22
 *
23
 * @see http://php.net/manual/pdo.constants.php
24
 */
25
class PDODriver extends DBDriverAbstract{
26
27
	/**
28
	 * Holds the database resource object
29
	 *
30
	 * @var PDO
31
	 */
32
	protected $db;
33
34
	/**
35
	 * Some basic PDO options
36
	 *
37
	 * @see http://php.net/manual/pdo.getattribute.php PDO::getAttribute
38
	 *
39
	 * @var array
40
	 */
41
	protected $pdo_options = [
42
		PDO::ATTR_CASE              => PDO::CASE_NATURAL,
43
		PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
44
		PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
45
		PDO::ATTR_STRINGIFY_FETCHES => false,
46
		PDO::ATTR_EMULATE_PREPARES  => false,
47
	];
48
49
	/**
50
	 * The PDO drivername which is being used in the DSN
51
	 *
52
	 * @var string
53
	 */
54
	protected $drivername;
55
56
	/**
57
	 * This array holds one or more key=>value pairs to set attribute values for the PDOStatement object that this method returns.
58
	 * You would most commonly use this to set the PDO::ATTR_CURSOR value to PDO::CURSOR_SCROLL to request a scrollable cursor.
59
	 *
60
	 * @var array
61
	 */
62
	protected $pdo_stmt_options = [];
63
64
	/**
65
	 * Returns a DSN string using the given options
66
	 * @link http://www.phpro.org/tutorials/Introduction-to-PHP-PDO.html#4.4
67
	 *
68
	 * @return string DSN
69
	 */
70
	protected function getDSN():string {
71
		$dsn = $this->drivername;
72
73
		if($this->options->socket){
74
			$dsn .= ':unix_socket='.$this->options->socket;
75
		}
76
		else{
77
			$dsn .= ':host='.$this->options->host;
78
			$dsn .= (bool)$this->options->port ? ';port='.$this->options->port : '';
79
		}
80
81
		$dsn .= ';dbname='.$this->options->database;
82
83
		return $dsn;
84
	}
85
86
	/**
87
	 * Establishes a database connection and returns the connection object
88
	 *
89
	 * @return \PDO the database resource object
90
	 * @throws \chillerlan\Database\DBException
91
	 */
92
	public function connect():PDO {
93
94
		if($this->db instanceof PDO){
95
			return $this->db;
96
		}
97
98
		if($this->options->use_ssl){
99
			$this->pdo_options += [
100
				PDO::MYSQL_ATTR_SSL_KEY    => $this->options->ssl_key,
101
				PDO::MYSQL_ATTR_SSL_CERT   => $this->options->ssl_cert,
102
				PDO::MYSQL_ATTR_SSL_CA     => $this->options->ssl_ca,
103
				PDO::MYSQL_ATTR_SSL_CAPATH => $this->options->ssl_capath,
104
				PDO::MYSQL_ATTR_SSL_CIPHER => $this->options->ssl_cipher,
105
			];
106
		}
107
108
		try{
109
			$this->db = new PDO($this->getDSN(), $this->options->username, $this->options->password, $this->pdo_options);
110
		}
111
		catch(PDOException $PDOException){
112
			throw new DBException($PDOException->getMessage());
113
		}
114
115
		return $this->db;
116
	}
117
118
	/**
119
	 * Closes a database connection
120
	 *
121
	 * @return bool
122
	 */
123
	public function disconnect():bool {
124
		$this->db = null;
125
126
		return true;
127
	}
128
129
	/**
130
	 * Returns info about the used php client
131
	 *
132
	 * @return string php's database client string
133
	 */
134
	public function getClientInfo():string {
135
		return $this->db->getAttribute(PDO::ATTR_CLIENT_VERSION);
136
	}
137
138
	/**
139
	 * Returns info about the database server
140
	 *
141
	 * @return string database's serverinfo string
142
	 */
143
	public function getServerInfo():string {
144
		return $this->db->getAttribute(PDO::ATTR_SERVER_INFO);
145
	}
146
147
	/**
148
	 * Sanitizer.
149
	 *
150
	 * Recursively escapes string values, optional htmlspecialchars()
151
	 *
152
	 * @param array|string $data         array or string to escape
153
	 * @param bool         $specialchars [optional] if true, it performs a htmlspecialchars() on each value given
154
	 *
155
	 * @return array|string array or string. escaped. obviously.
156
	 */
157 View Code Duplication
	public function escape($data, bool $specialchars = false){
0 ignored issues
show
Duplication introduced by
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...
158
159
		if(is_array($data)){
160
161
			foreach($data as $key => $value){
162
				$data[$key] = $this->escape($value, $specialchars);
163
			}
164
165
		}
166
		else if(is_object($data)){
167
168
			foreach($data as $key => $value){
169
				$data->{$key} = $this->escape($value, $specialchars);
170
			}
171
172
		}
173
		else{
174
175
			if($specialchars){
176
				$data = htmlspecialchars($data, ENT_HTML5, 'UTF-8', false);
177
			}
178
179
			$data = $this->db->quote($data);
180
		}
181
182
		return $data;
183
	}
184
185
	/**
186
	 * @param \PDOStatement $stmt
187
	 * @param string        $index
188
	 * @param bool          $assoc
189
	 * @param bool          $fetch_array
190
	 *
191
	 * @return array
192
	 * @internal
193
	 */
194
	protected function getResult(PDOStatement &$stmt, string $index, bool $assoc, bool $fetch_array):array {
195
		$out = [];
196
		$method = $assoc ? ($fetch_array ? PDO::FETCH_ASSOC : PDO::FETCH_OBJ) : PDO::FETCH_NUM;
197
		$i = 0 ;
198
199
		// ok, we have a result with one or more rows, loop out the rows and output as array
200 View Code Duplication
		while($row = $stmt->fetch($method)){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
201
			$key = $i;
202
203
			if($assoc && !empty($index)){
204
205
				if($fetch_array && isset($row[$index])){
206
					$key = $row[$index];
207
				}
208
				else if(isset($row->{$index})){
209
					$key = $row->{$index};
210
				}
211
212
			}
213
214
			$out[$key] = $row;
215
			$i++;
216
217
		}
218
219
		return $out;
220
	}
221
222
	/**
223
	 * @param \PDOStatement $stmt
224
	 * @param array         $values
225
	 * @return void
226
	 * @internal
227
	 */
228
	protected function bindParams(PDOStatement &$stmt, array $values){
229
		$param_no = 1;
230
231
		foreach($values as $v){
232
233
			switch(gettype($v)){
234
				case 'boolean': $type = PDO::PARAM_BOOL; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
235
				case 'integer': $type = PDO::PARAM_INT;  break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
236
				case 'NULL':    $type = PDO::PARAM_NULL; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
237
				default:        $type = PDO::PARAM_STR;  break;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
238
			}
239
240
			$stmt->bindValue($param_no, $v, $type);
241
			$param_no++;
242
		}
243
	}
244
245
	/**
246
	 * Returns the last insert id (if present)
247
	 *
248
	 * @link http://php.net/manual/pdo.lastinsertid.php
249
	 * @return string
250
	 */
251
	protected function insertID():string {
252
		return $this->db->lastInsertId();
253
	}
254
255
	/**
256
	 * Basic SQL query for non prepared statements
257
	 *
258
	 * There is no escaping in here, so make sure, your SQL is clean/escaped.
259
	 * Also, your SQL should NEVER contain user input, use prepared statements in this case.
260
	 *
261
	 * If the query was successful it returns either an array of results or true
262
	 * if it was a void query. On errors, a false will be returned, obviously.
263
	 *
264
	 * @param string $sql         The SQL statement
265
	 * @param string $index       [optional] an index column to assingn as the result's keys
0 ignored issues
show
Documentation introduced by
Should the type for parameter $index not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
266
	 * @param bool   $assoc       [optional] If true, the fields are named with the respective column names, otherwise numbered
267
	 * @param bool   $fetch_array [optional] fetch the vaues as array instead of object
268
	 *
269
	 * @return array|bool array with results, true on void query success, otherwise false.
270
	 * @throws \chillerlan\Database\DBException
271
	 */
272
	public function raw(string $sql, string $index = null, bool $assoc = true, bool $fetch_array = false){
273
274
		try{
275
			$stmt = $this->db->query($sql);
276
277
			$this->addStats([
278
				'affected_rows' => $stmt->rowCount(),
279
				'error'         => $stmt->errorInfo(),
280
				'insert_id'     => $this->insertID(),
281
				'sql'           => $sql,
282
				'index'         => $index,
283
				'assoc'         => $assoc,
284
				'fetch_array'   => $fetch_array,
285
			]);
286
287
			if(!is_bool($stmt)){
288
				return $this->getResult($stmt, $index, $assoc, $fetch_array);
289
			}
290
291
			// void result
292
			return $stmt;
293
		}
294
		catch(PDOException $PDOException){
295
			throw new DBException($PDOException->getMessage());
296
		}
297
298
	}
299
300
	/**
301
	 * Prepared statements wrapper
302
	 *
303
	 * Does everything for you: prepares the statement and fetches the results as an object or array
304
	 * just pass a query along with values and you're done. Not meant for multi-inserts.
305
	 *
306
	 * @param string $sql         The SQL statement to prepare
307
	 * @param array  $values      [optional] the value for each "?" in the statement - in the respective order, of course
308
	 * @param string $index       [optional] an index column to assingn as the result's keys
0 ignored issues
show
Documentation introduced by
Should the type for parameter $index not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
309
	 * @param bool   $assoc       [optional] If true, the fields are named with the respective column names, otherwise numbered
310
	 * @param bool   $fetch_array [optional] fetch the vaues as array instead of object
311
	 *
312
	 * @return array|bool Array with results, true on void query success, otherwise false
313
	 * @throws \chillerlan\Database\DBException
314
	 */
315
	public function prepared(string $sql, array $values = [], string $index = null, bool $assoc = true, bool $fetch_array = false){
316
317
		try{
318
			$stmt = $this->db->prepare($sql, $this->pdo_stmt_options);
319
320
			if(!empty($values)){
321
				$this->bindParams($stmt, $values);
322
			}
323
324
			$stmt->execute();
325
326
			$this->addStats([
327
				'affected_rows' => $stmt->rowCount(),
328
				'error'         => $stmt->errorInfo(),
329
				'insert_id'     => $this->insertID(),
330
				'sql'           => $sql,
331
				'values'        => $values,
332
				'index'         => $index,
333
				'assoc'         => $assoc,
334
				'fetch_array'   => $fetch_array,
335
			]);
336
337
			if(!is_bool($stmt)){
338
				return $this->getResult($stmt, $index, $assoc, $fetch_array);
339
			}
340
341
			return $stmt;
342
		}
343
		catch(PDOException $PDOException){
344
			throw new DBException($PDOException->getMessage());
345
		}
346
	}
347
348
	/**
349
	 * Prepared multi line insert
350
	 *
351
	 * Prepared statement multi insert/update
352
	 *
353
	 * @param string $sql    The SQL statement to prepare
354
	 * @param array  $values a multidimensional array with the values, each row represents one line to insert.
355
	 *
356
	 * @return bool true query success, otherwise false
357
	 */
358
	public function multi(string $sql, array $values){
359
360
		if(is_array($values) && count($values) > 0 && is_array($values[0]) && count($values[0]) > 0){
361
			$stmt = $this->db->prepare($sql, $this->pdo_stmt_options);
362
363
			foreach($values as $row){
364
				$this->bindParams($stmt, $row);
365
				$stmt->execute();
366
367
				$this->addStats([
368
					'affected_rows' => $stmt->rowCount(),
369
					'error'         => $stmt->errorInfo(),
370
					'insert_id'     => $this->insertID(),
371
					'sql'           => $sql,
372
					'values'        => $values,
373
				]);
374
375
			}
376
377
			$stmt = null;
0 ignored issues
show
Unused Code introduced by
$stmt is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
378
379
			return true;
380
		}
381
382
		return false;
383
	}
384
385
	/**
386
	 * Prepared multi line insert/update with callback
387
	 *
388
	 * @todo: multi treading
389
	 * @see https://gist.github.com/krakjoe/6437782
390
	 * @see https://gist.github.com/krakjoe/9384409
391
	 *
392
	 * @param string         $sql      The SQL statement to prepare
393
	 * @param array          $data     an array with the (raw) data to insert, each row represents one line to insert.
394
	 * @param callable|array $callback a callback that processes the values for each row.
395
	 *
396
	 * @return bool true query success, otherwise false
397
	 * @throws \chillerlan\Database\DBException
398
	 */
399
	public function multi_callback(string $sql, array $data, $callback){
400
		// TODO: Implement multi_callback() method.
401
		return false;
402
	}
403
}
404