Completed
Push — master ( dabf55...764bc6 )
by Nazar
14:29
created

_Abstract::prepare_query_and_parameters()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 2
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\DB;
9
use
10
	Exception;
11
12
abstract class _Abstract {
13
	/**
14
	 * Is connection established
15
	 *
16
	 * @var bool
17
	 */
18
	protected $connected = false;
19
	/**
20
	 * DB type, may be used for constructing requests, accounting particular features of current DB (lowercase name)
21
	 *
22
	 * @var string
23
	 */
24
	protected $db_type = '';
25
	/**
26
	 * Current DB
27
	 *
28
	 * @var string
29
	 */
30
	protected $database;
31
	/**
32
	 * Current prefix
33
	 *
34
	 * @var string
35
	 */
36
	protected $prefix;
37
	/**
38
	 * Total time of requests execution
39
	 *
40
	 * @var float
41
	 */
42
	protected $time;
43
	/**
44
	 * Array for storing of data of the last executed request
45
	 *
46
	 * @var array
47
	 */
48
	protected $query = [
49
		'time' => 0,
50
		'text' => ''
51
	];
52
	/**
53
	 * Array for storing data of all executed requests
54
	 *
55
	 * @var array
56
	 */
57
	protected $queries = [
58
		'num'  => 0,
59
		'time' => [],
60
		'text' => []
61
	];
62
	/**
63
	 * Connection time
64
	 *
65
	 * @var float
66
	 */
67
	protected $connecting_time;
68
	/**
69
	 * @var bool
70
	 */
71
	protected $in_transaction = false;
72
	/**
73
	 * Connecting to the DB
74
	 *
75
	 * @param string $database
76
	 * @param string $user
77
	 * @param string $password
78
	 * @param string $host
79
	 * @param string $prefix
80
	 */
81
	abstract public function __construct ($database, $user = '', $password = '', $host = 'localhost', $prefix = '');
1 ignored issue
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
82
	/**
83
	 * Query
84
	 *
85
	 * SQL request into DB
86
	 *
87
	 * @abstract
88
	 *
89
	 * @param string|string[] $query      SQL query string or array, may be a format string in accordance with the first parameter of sprintf() function or may
90
	 *                                    contain markers for prepared statements (but not both at the same time)
91
	 * @param array           $parameters There might be arbitrary number of parameters for formatting SQL statement or for using in prepared statements.<br>
1 ignored issue
show
Documentation introduced by
Consider making the type for parameter $parameters a bit more specific; maybe use array[].
Loading history...
92
	 *                                    If an array provided as second argument - its items will be used, so that you can either specify parameters as an
93
	 *                                    array, or in line.
94
	 *
95
	 * @return bool|object|resource
96
	 */
97 44
	public function q ($query, ...$parameters) {
98 44
		$normalized = $this->normalize_parameters($query, $parameters);
99 44
		if (!$normalized) {
100 2
			return false;
101
		}
102 44
		list($query, $parameters) = $normalized;
103
		/**
104
		 * Executing multiple queries
105
		 */
106 44
		if (is_array($query)) {
107 12
			return $this->execute_multiple($query, $parameters);
108
		}
109 44
		return $this->execute_single($query, $parameters);
110
	}
111
	/**
112
	 * @param string|string[] $query
113
	 * @param array           $parameters
114
	 *
115
	 * @return array|false
1 ignored issue
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
116
	 */
117 44
	protected function normalize_parameters ($query, $parameters) {
118 44
		if (!$query) {
119 2
			return false;
120
		}
121 44
		$query = str_replace('[prefix]', $this->prefix, $query);
122
		/** @noinspection NotOptimalIfConditionsInspection */
123 44
		if (count($parameters) == 1 && is_array($parameters[0])) {
124 38
			$parameters = $parameters[0];
125
		}
126
		return [
127 44
			$query,
128 44
			$parameters
129
		];
130
	}
131
	/**
132
	 * @param string[] $queries
133
	 * @param string[] $parameters
134
	 *
135
	 * @return bool
136
	 */
137 12
	protected function execute_multiple ($queries, $parameters) {
138 12
		$time_from         = microtime(true);
139 12
		$parameters_server = [];
140 12
		foreach ($queries as &$q) {
141 12
			$q = $this->prepare_query_and_parameters($q, $parameters);
142 12
			if ($q[1]) {
143
				$q                 = $q[0];
144
				$parameters_server = $parameters;
145
				break;
146
			}
147 12
			$q = $q[0];
148
		}
149 12
		unset($q);
150 12
		$this->queries['num'] += count($queries);
151 12
		$result = $this->q_multi_internal($queries, $parameters_server);
152 12
		$this->time += round(microtime(true) - $time_from, 6);
153 12
		return $result;
154
	}
155
	/**
156
	 * @param string   $query
157
	 * @param string[] $parameters
158
	 *
159
	 * @return array
1 ignored issue
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
160
	 */
161 44
	protected function prepare_query_and_parameters ($query, $parameters) {
162 44
		if (!$parameters || strpos($query, '?') !== false) {
163 40
			return [$query, $parameters];
164
		}
165 44
		foreach ($parameters as &$parameter) {
166 44
			$parameter = $this->s($parameter, false);
167
		}
168 44
		return [vsprintf($query, $parameters), []];
169
	}
170
	/**
171
	 * @param string   $query
172
	 * @param string[] $parameters
173
	 *
174
	 * @return false|object|resource
175
	 */
176 44
	protected function execute_single ($query, $parameters) {
177 44
		$time_from = microtime(true);
178 44
		list($query, $parameters) = $this->prepare_query_and_parameters($query, $parameters);
179 44
		$this->query['text'] = $query[0];
180 44
		if (DEBUG) {
181 1
			$this->queries['text'][] = $this->query['text'];
182
		}
183 44
		$result              = $this->q_internal($query, $parameters);
184 44
		$this->query['time'] = round(microtime(true) - $time_from, 6);
185 44
		$this->time += $this->query['time'];
186 44
		if (DEBUG) {
187 1
			$this->queries['time'][] = $this->query['time'];
188
		}
189 44
		++$this->queries['num'];
190 44
		return $result;
191
	}
192
	/**
193
	 * SQL request to DB
194
	 *
195
	 * @abstract
196
	 *
197
	 * @param string   $query
198
	 * @param string[] $parameters If not empty, than server-side prepared statements should be used
199
	 *
200
	 * @return false|object|resource
201
	 */
202
	abstract protected function q_internal ($query, $parameters = []);
203
	/**
204
	 * Multiple SQL request to DB
205
	 *
206
	 * @abstract
207
	 *
208
	 * @param string[] $query
209
	 * @param string[] $parameters If not empty, than server-side prepared statements should be used
210
	 *
211
	 * @return bool
212
	 */
213 12
	protected function q_multi_internal ($query, $parameters = []) {
214 12
		$result = true;
215 12
		foreach ($query as $q) {
216 12
			$result = $result && $this->q_internal($q, $parameters);
217
		}
218 12
		return $result;
219
	}
220
	/**
221
	 * Fetch
222
	 *
223
	 * Fetch a result row as an associative array
224
	 *
225
	 * @abstract
226
	 *
227
	 * @param false|object|resource $query_result
228
	 * @param bool                  $single_column If <b>true</b> function will return not array with one element, but directly its value
229
	 * @param bool                  $array         If <b>true</b> returns array of associative arrays of all fetched rows
230
	 * @param bool                  $indexed       If <b>false</b> - associative array will be returned
231
	 *
232
	 * @return array[]|false|int|int[]|string|string[]
233
	 */
234
	abstract public function f ($query_result, $single_column = false, $array = false, $indexed = false);
235
	/**
236
	 * Query, Fetch
237
	 *
238
	 * Short for `::f(::q())`, arguments are exactly the same as in `::q()`
239
	 *
240
	 * @param string[] $query
241
	 *
242
	 * @return array|false
243
	 */
244 38
	public function qf (...$query) {
245 38
		return $this->f($this->q(...$query));
246
	}
247
	/**
248
	 * Query, Fetch, Array
249
	 *
250
	 * Short for `::f(::q(), false, true)`, arguments are exactly the same as in `::q()`
251
	 *
252
	 * @param string[] $query
253
	 *
254
	 * @return array[]|false
255
	 */
256 19
	public function qfa (...$query) {
257 19
		return $this->f($this->q(...$query), false, true);
258
	}
259
	/**
260
	 * Query, Fetch, Single
261
	 *
262
	 * Short for `::f(::q(), true)`, arguments are exactly the same as in `::q()`
263
	 *
264
	 * @param string[] $query
265
	 *
266
	 * @return false|int|string
267
	 */
268 26
	public function qfs (...$query) {
269 26
		return $this->f($this->q(...$query), true);
270
	}
271
	/**
272
	 * Query, Fetch, Array, Single
273
	 *
274
	 * Short for `::f(::q(), true, true)`, arguments are exactly the same as in `::q()`
275
	 *
276
	 * @param string[] $query
277
	 *
278
	 * @return false|int[]|string[]
279
	 */
280 31
	public function qfas (...$query) {
281 31
		return $this->f($this->q(...$query), true, true);
282
	}
283
	/**
284
	 * Method for simplified inserting of several rows
285
	 *
286
	 * @param string        $query
287
	 * @param array|array[] $parameters Array of array of parameters for inserting
288
	 * @param bool          $join       If true - inserting of several rows will be combined in one query. For this, be sure, that your query has keyword
289
	 *                                  <i>VALUES</i> in uppercase. Part of query after this keyword will be multiplied with coma separator.
290
	 *
291
	 * @return bool
292
	 */
293 26
	public function insert ($query, $parameters, $join = true) {
294 26
		if (!$query || !$parameters) {
295 2
			return false;
296
		}
297 26
		if ($join) {
298 26
			$query    = explode('VALUES', $query, 2);
299 26
			$query[1] = explode(')', $query[1], 2);
300
			$query    = [
301 26
				$query[0],
302 26
				$query[1][0].')',
303 26
				$query[1][1]
304
			];
305 26
			$query[1] .= str_repeat(",$query[1]", count($parameters) - 1);
306 26
			$query = $query[0].'VALUES'.$query[1].$query[2];
307 26
			return (bool)$this->q(
308
				$query,
309 26
				array_merge(...array_map('array_values', _array($parameters)))
310
			);
311
		} else {
312 2
			$result = true;
313 2
			foreach ($parameters as $p) {
314 2
				$result = $result && (bool)$this->q($query, $p);
315
			}
316 2
			return $result;
317
		}
318
	}
319
	/**
320
	 * Id
321
	 *
322
	 * Get id of last inserted row
323
	 *
324
	 * @abstract
325
	 *
326
	 * @return int
327
	 */
328
	abstract public function id ();
329
	/**
330
	 * Affected
331
	 *
332
	 * Get number of affected rows during last query
333
	 *
334
	 * @abstract
335
	 *
336
	 * @return int
337
	 */
338
	abstract public function affected ();
339
	/**
340
	 * Execute transaction
341
	 *
342
	 * All queries done inside callback will be within single transaction, throwing any exception or returning boolean `false` from callback will cause
343
	 * rollback. Nested transaction calls will be wrapped into single big outer transaction, so you might call it safely if needed.
344
	 *
345
	 * @param callable $callback This instance will be used as single argument
346
	 *
347
	 * @return bool
348
	 *
349
	 * @throws Exception Re-throws exception thrown inside callback
350
	 */
351 38
	public function transaction ($callback) {
352 38
		$start_transaction = !$this->in_transaction;
353 38
		if ($start_transaction) {
354 38
			$this->in_transaction = true;
355 38
			if (!$this->q_internal('BEGIN')) {
356
				return false;
357
			}
358
		}
359
		try {
360 38
			$result = $callback($this);
361 2
		} catch (Exception $e) {
362 2
			$this->transaction_rollback();
363 2
			throw $e;
364
		}
365 38
		if ($result === false) {
366 2
			$this->transaction_rollback();
367 2
			return false;
368 38
		} elseif ($start_transaction) {
369 38
			$this->in_transaction = false;
370 38
			return (bool)$this->q_internal('COMMIT');
371
		}
372 2
		return true;
373
	}
374 2
	protected function transaction_rollback () {
375 2
		if ($this->in_transaction) {
376 2
			$this->in_transaction = false;
377 2
			$this->q_internal('ROLLBACK');
378
		}
379 2
	}
380
	/**
381
	 * Free result memory
382
	 *
383
	 * @abstract
384
	 *
385
	 * @param false|object|resource $query_result
386
	 */
387
	abstract public function free ($query_result);
1 ignored issue
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
388
	/**
389
	 * Get columns list of table
390
	 *
391
	 * @param string       $table
392
	 * @param false|string $like
393
	 *
394
	 * @return string[]
395
	 */
396
	abstract public function columns ($table, $like = false);
397
	/**
398
	 * Get tables list
399
	 *
400
	 * @param false|string $like
401
	 *
402
	 * @return string[]
403
	 */
404
	abstract public function tables ($like = false);
405
	/**
406
	 * Safe
407
	 *
408
	 * Preparing string for using in SQL query
409
	 * SQL Injection Protection
410
	 *
411
	 * @param string|string[] $string
412
	 * @param bool            $single_quotes_around
413
	 *
414
	 * @return string|string[]
415
	 */
416 44
	public function s ($string, $single_quotes_around = true) {
417 44
		if (is_array($string)) {
418 6
			foreach ($string as &$s) {
419 6
				$s = $this->s_internal($s, $single_quotes_around);
420
			}
421 6
			return $string;
422
		}
423 44
		return $this->s_internal($string, $single_quotes_around);
424
	}
425
	/**
426
	 * Preparing string for using in SQL query
427
	 * SQL Injection Protection
428
	 *
429
	 * @param string $string
430
	 * @param bool   $single_quotes_around
431
	 *
432
	 * @return string
433
	 */
434
	abstract protected function s_internal ($string, $single_quotes_around);
435
	/**
436
	 * Get information about server
437
	 *
438
	 * @return string
439
	 */
440
	abstract public function server ();
441
	/**
442
	 * Connection state
443
	 *
444
	 * @return bool
445
	 */
446 46
	public function connected () {
447 46
		return $this->connected;
448
	}
449
	/**
450
	 * Database type (lowercase, for example <i>mysql</i>)
451
	 *
452
	 * @return string
453
	 */
454 2
	public function db_type () {
455 2
		return $this->db_type;
456
	}
457
	/**
458
	 * Database name
459
	 *
460
	 * @return string
461
	 */
462 4
	public function database () {
463 4
		return $this->database;
464
	}
465
	/**
466
	 * Queries array, has 3 properties:<ul>
467
	 * <li>num - total number of performed queries
468
	 * <li>time - array with time of each query execution
469
	 * <li>text - array with text text of each query
470
	 *
471
	 * @return array
472
	 */
473 4
	public function queries () {
474 4
		return $this->queries;
475
	}
476
	/**
477
	 * Last query information, has 2 properties:<ul>
478
	 * <li>time - execution time
479
	 * <li>text - query text
480
	 *
481
	 * @return array
482
	 */
483 2
	public function query () {
484 2
		return $this->query;
485
	}
486
	/**
487
	 * Total working time (including connection, queries execution and other delays)
488
	 *
489
	 * @return float
490
	 */
491 4
	public function time () {
492 4
		return $this->time;
493
	}
494
	/**
495
	 * Connecting time
496
	 *
497
	 * @return float
498
	 */
499 4
	public function connecting_time () {
500 4
		return $this->connecting_time;
501
	}
502
	/**
503
	 * Cloning restriction
504
	 *
505
	 * @final
506
	 */
507
	final protected function __clone () {
508
	}
509
	/**
510
	 * Disconnecting from DB
511
	 */
512
	abstract public function __destruct ();
1 ignored issue
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
513
}
514