These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Query |
||
4 | * |
||
5 | * SQL Query Builder / Database Abstraction Layer |
||
6 | * |
||
7 | * PHP version 5.4 |
||
8 | * |
||
9 | * @package Query |
||
10 | * @author Timothy J. Warren <[email protected]> |
||
11 | * @copyright 2012 - 2015 Timothy J. Warren |
||
12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License |
||
13 | * @link https://git.timshomepage.net/aviat4ion/Query |
||
14 | */ |
||
15 | |||
16 | namespace Query; |
||
17 | |||
18 | /** |
||
19 | * Abstract Class for internal implementation methods of the Query Builder |
||
20 | * @package Query |
||
21 | */ |
||
22 | abstract class AbstractQueryBuilder { |
||
23 | |||
24 | // -------------------------------------------------------------------------- |
||
25 | // ! Constants |
||
26 | // -------------------------------------------------------------------------- |
||
27 | |||
28 | const KEY = 0; |
||
29 | const VALUE = 1; |
||
30 | const BOTH = 2; |
||
31 | |||
32 | |||
33 | // -------------------------------------------------------------------------- |
||
34 | // ! SQL Clause Strings |
||
35 | // -------------------------------------------------------------------------- |
||
36 | |||
37 | /** |
||
38 | * Compiled 'select' clause |
||
39 | * @var string |
||
40 | */ |
||
41 | protected $select_string = ''; |
||
42 | |||
43 | /** |
||
44 | * Compiled 'from' clause |
||
45 | * @var string |
||
46 | */ |
||
47 | protected $from_string; |
||
48 | |||
49 | /** |
||
50 | * Compiled arguments for insert / update |
||
51 | * @var string |
||
52 | */ |
||
53 | protected $set_string; |
||
54 | |||
55 | /** |
||
56 | * Order by clause |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $order_string; |
||
60 | |||
61 | /** |
||
62 | * Group by clause |
||
63 | * @var string |
||
64 | */ |
||
65 | protected $group_string; |
||
66 | |||
67 | // -------------------------------------------------------------------------- |
||
68 | // ! SQL Clause Arrays |
||
69 | // -------------------------------------------------------------------------- |
||
70 | |||
71 | /** |
||
72 | * Keys for insert/update statement |
||
73 | * @var array |
||
74 | */ |
||
75 | protected $set_array_keys = []; |
||
76 | |||
77 | /** |
||
78 | * Key/val pairs for order by clause |
||
79 | * @var array |
||
80 | */ |
||
81 | protected $order_array = []; |
||
82 | |||
83 | /** |
||
84 | * Key/val pairs for group by clause |
||
85 | * @var array |
||
86 | */ |
||
87 | protected $group_array = []; |
||
88 | |||
89 | // -------------------------------------------------------------------------- |
||
90 | // ! Other Class vars |
||
91 | // -------------------------------------------------------------------------- |
||
92 | |||
93 | /** |
||
94 | * Values to apply to prepared statements |
||
95 | * @var array |
||
96 | */ |
||
97 | protected $values = []; |
||
98 | |||
99 | /** |
||
100 | * Values to apply to where clauses in prepared statements |
||
101 | * @var array |
||
102 | */ |
||
103 | protected $where_values = []; |
||
104 | |||
105 | /** |
||
106 | * Value for limit string |
||
107 | * @var string |
||
108 | */ |
||
109 | protected $limit; |
||
110 | |||
111 | /** |
||
112 | * Value for offset in limit string |
||
113 | * @var integer |
||
114 | */ |
||
115 | protected $offset; |
||
116 | |||
117 | /** |
||
118 | * Query component order mapping |
||
119 | * for complex select queries |
||
120 | * |
||
121 | * Format: |
||
122 | * array( |
||
123 | * 'type' => 'where', |
||
124 | * 'conjunction' => ' AND ', |
||
125 | * 'string' => 'k=?' |
||
126 | * ) |
||
127 | * |
||
128 | * @var array |
||
129 | */ |
||
130 | protected $query_map = []; |
||
131 | |||
132 | /** |
||
133 | * Map for having clause |
||
134 | * @var array |
||
135 | */ |
||
136 | protected $having_map; |
||
137 | |||
138 | /** |
||
139 | * Convenience property for connection management |
||
140 | * @var string |
||
141 | */ |
||
142 | public $conn_name = ""; |
||
143 | |||
144 | /** |
||
145 | * List of queries executed |
||
146 | * @var array |
||
147 | */ |
||
148 | public $queries; |
||
149 | |||
150 | /** |
||
151 | * Whether to do only an explain on the query |
||
152 | * @var boolean |
||
153 | */ |
||
154 | protected $explain; |
||
155 | |||
156 | /** |
||
157 | * The current database driver |
||
158 | * @var \Query\Drivers\DriverInterface |
||
159 | */ |
||
160 | public $db; |
||
161 | |||
162 | /** |
||
163 | * Query parser class instance |
||
164 | * @var QueryParser |
||
165 | */ |
||
166 | public $parser; |
||
167 | |||
168 | /** |
||
169 | * Alias to driver util class |
||
170 | * @var \Query\Drivers\AbstractUtil |
||
171 | */ |
||
172 | public $util; |
||
173 | |||
174 | /** |
||
175 | * Alias to driver sql class |
||
176 | * @var \Query\Drivers\SQLInterface |
||
177 | */ |
||
178 | public $sql; |
||
179 | |||
180 | // -------------------------------------------------------------------------- |
||
181 | // Methods |
||
182 | // -------------------------------------------------------------------------- |
||
183 | |||
184 | /** |
||
185 | * Set values in the class, with either an array or key value pair |
||
186 | * |
||
187 | * @param array $var |
||
188 | * @param mixed $key |
||
189 | * @param mixed $val |
||
190 | * @param int $val_type |
||
191 | * @return array |
||
192 | */ |
||
193 | protected function _mixed_set(&$var, $key, $val=NULL, $val_type=self::BOTH) |
||
194 | { |
||
195 | $arg = (is_scalar($key) && is_scalar($val)) |
||
196 | ? [$key => $val] |
||
197 | : $key; |
||
198 | |||
199 | foreach($arg as $k => $v) |
||
200 | { |
||
201 | if (in_array($val_type, [self::KEY, self::VALUE])) |
||
202 | { |
||
203 | $var[] = ($val_type === self::KEY) |
||
204 | ? $k |
||
205 | : $v; |
||
206 | } |
||
207 | else |
||
208 | { |
||
209 | $var[$k] = $v; |
||
210 | } |
||
211 | } |
||
212 | |||
213 | return $var; |
||
214 | } |
||
215 | |||
216 | // -------------------------------------------------------------------------- |
||
217 | |||
218 | /** |
||
219 | * Method to simplify select_ methods |
||
220 | * |
||
221 | * @param string $field |
||
222 | * @param string|bool $as |
||
223 | * @return string |
||
224 | */ |
||
225 | protected function _select($field, $as = FALSE) |
||
226 | { |
||
227 | // Escape the identifiers |
||
228 | $field = $this->db->quote_ident($field); |
||
0 ignored issues
–
show
Bug
Compatibility
introduced
by
Loading history...
|
|||
229 | |||
230 | if ( ! is_string($as)) |
||
231 | { |
||
232 | return $field; |
||
233 | } |
||
234 | |||
235 | |||
236 | $as = $this->db->quote_ident($as); |
||
237 | return "({$field}) AS {$as} "; |
||
238 | } |
||
239 | |||
240 | // -------------------------------------------------------------------------- |
||
241 | |||
242 | /** |
||
243 | * Helper function for returning sql strings |
||
244 | * |
||
245 | * @param string $type |
||
246 | * @param string $table |
||
247 | * @param bool $reset |
||
248 | * @return string |
||
249 | */ |
||
250 | protected function _get_compile($type, $table, $reset) |
||
251 | { |
||
252 | $sql = $this->_compile($type, $table); |
||
253 | |||
254 | // Reset the query builder for the next query |
||
255 | if ($reset) |
||
256 | { |
||
257 | $this->reset_query(); |
||
258 | } |
||
259 | |||
260 | return $sql; |
||
261 | } |
||
262 | |||
263 | // -------------------------------------------------------------------------- |
||
264 | |||
265 | /** |
||
266 | * Simplify 'like' methods |
||
267 | * |
||
268 | * @param string $field |
||
269 | * @param mixed $val |
||
270 | * @param string $pos |
||
271 | * @param string $like |
||
272 | * @param string $conj |
||
273 | * @return QueryBuilder |
||
274 | */ |
||
275 | protected function _like($field, $val, $pos, $like='LIKE', $conj='AND') |
||
276 | { |
||
277 | $field = $this->db->quote_ident($field); |
||
278 | |||
279 | // Add the like string into the order map |
||
280 | $like = $field. " {$like} ?"; |
||
281 | |||
282 | if ($pos == 'before') |
||
283 | { |
||
284 | $val = "%{$val}"; |
||
285 | } |
||
286 | elseif ($pos == 'after') |
||
287 | { |
||
288 | $val = "{$val}%"; |
||
289 | } |
||
290 | else |
||
291 | { |
||
292 | $val = "%{$val}%"; |
||
293 | } |
||
294 | |||
295 | $conj = (empty($this->query_map)) ? ' WHERE ' : " {$conj} "; |
||
296 | $this->_append_map($conj, $like, 'like'); |
||
297 | |||
298 | // Add to the values array |
||
299 | $this->where_values[] = $val; |
||
300 | |||
301 | return $this; |
||
302 | } |
||
303 | |||
304 | // -------------------------------------------------------------------------- |
||
305 | |||
306 | /** |
||
307 | * Simplify building having clauses |
||
308 | * |
||
309 | * @param mixed $key |
||
310 | * @param mixed $val |
||
311 | * @param string $conj |
||
312 | * @return QueryBuilder |
||
313 | */ |
||
314 | protected function _having($key, $val=[], $conj='AND') |
||
315 | { |
||
316 | $where = $this->_where($key, $val); |
||
317 | |||
318 | // Create key/value placeholders |
||
319 | foreach($where as $f => $val) |
||
320 | { |
||
321 | // Split each key by spaces, in case there |
||
322 | // is an operator such as >, <, !=, etc. |
||
323 | $f_array = explode(' ', trim($f)); |
||
324 | |||
325 | $item = $this->db->quote_ident($f_array[0]); |
||
326 | |||
327 | // Simple key value, or an operator |
||
328 | $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; |
||
329 | |||
330 | // Put in the having map |
||
331 | $this->having_map[] = [ |
||
332 | 'conjunction' => ( ! empty($this->having_map)) ? " {$conj} " : ' HAVING ', |
||
333 | 'string' => $item |
||
334 | ]; |
||
335 | } |
||
336 | |||
337 | return $this; |
||
338 | } |
||
339 | |||
340 | // -------------------------------------------------------------------------- |
||
341 | |||
342 | /** |
||
343 | * Do all the redundant stuff for where/having type methods |
||
344 | * |
||
345 | * @param mixed $key |
||
346 | * @param mixed $val |
||
347 | * @return array |
||
348 | */ |
||
349 | protected function _where($key, $val=[]) |
||
350 | { |
||
351 | $where = []; |
||
352 | $this->_mixed_set($where, $key, $val, self::BOTH); |
||
353 | $this->_mixed_set($this->where_values, $key, $val, self::VALUE); |
||
354 | return $where; |
||
355 | } |
||
356 | |||
357 | // -------------------------------------------------------------------------- |
||
358 | |||
359 | /** |
||
360 | * Simplify generating where string |
||
361 | * |
||
362 | * @param mixed $key |
||
363 | * @param mixed $val |
||
364 | * @param string $defaultConj |
||
365 | * @return QueryBuilder |
||
366 | */ |
||
367 | protected function _where_string($key, $val=[], $defaultConj='AND') |
||
368 | { |
||
369 | // Create key/value placeholders |
||
370 | foreach($this->_where($key, $val) as $f => $val) |
||
371 | { |
||
372 | // Split each key by spaces, in case there |
||
373 | // is an operator such as >, <, !=, etc. |
||
374 | $f_array = explode(' ', trim($f)); |
||
375 | |||
376 | $item = $this->db->quote_ident($f_array[0]); |
||
377 | |||
378 | // Simple key value, or an operator |
||
379 | $item .= (count($f_array) === 1) ? '=?' : " {$f_array[1]} ?"; |
||
380 | $last_item = end($this->query_map); |
||
381 | |||
382 | // Determine the correct conjunction |
||
383 | $conjunctionList = array_column($this->query_map, 'conjunction'); |
||
384 | if (empty($this->query_map) || ( ! regex_in_array($conjunctionList, "/^ ?\n?WHERE/i"))) |
||
385 | { |
||
386 | $conj = "\nWHERE "; |
||
387 | } |
||
388 | elseif ($last_item['type'] === 'group_start') |
||
389 | { |
||
390 | $conj = ''; |
||
391 | } |
||
392 | else |
||
393 | { |
||
394 | $conj = " {$defaultConj} "; |
||
395 | } |
||
396 | |||
397 | $this->_append_map($conj, $item, 'where'); |
||
398 | } |
||
399 | |||
400 | return $this; |
||
401 | } |
||
402 | |||
403 | // -------------------------------------------------------------------------- |
||
404 | |||
405 | /** |
||
406 | * Simplify where_in methods |
||
407 | * |
||
408 | * @param mixed $key |
||
409 | * @param mixed $val |
||
410 | * @param string $in - The (not) in fragment |
||
411 | * @param string $conj - The where in conjunction |
||
412 | * @return QueryBuilder |
||
413 | */ |
||
414 | protected function _where_in($key, $val=[], $in='IN', $conj='AND') |
||
415 | { |
||
416 | $key = $this->db->quote_ident($key); |
||
417 | $params = array_fill(0, count($val), '?'); |
||
418 | |||
419 | foreach($val as $v) |
||
420 | { |
||
421 | $this->where_values[] = $v; |
||
422 | } |
||
423 | |||
424 | $conjunction = ( ! empty($this->query_map)) ? " {$conj} " : ' WHERE '; |
||
425 | $str = $key . " {$in} (".implode(',', $params).') '; |
||
426 | |||
427 | $this->_append_map($conjunction, $str, 'where_in'); |
||
428 | |||
429 | return $this; |
||
430 | } |
||
431 | |||
432 | // -------------------------------------------------------------------------- |
||
433 | |||
434 | /** |
||
435 | * Executes the compiled query |
||
436 | * |
||
437 | * @param string $type |
||
438 | * @param string $table |
||
439 | * @param string $sql |
||
440 | * @param array|null $vals |
||
441 | * @param boolean $reset |
||
442 | * @return \PDOStatement |
||
443 | */ |
||
444 | protected function _run($type, $table, $sql=NULL, $vals=NULL, $reset=TRUE) |
||
445 | { |
||
446 | if (is_null($sql)) |
||
447 | { |
||
448 | $sql = $this->_compile($type, $table); |
||
449 | } |
||
450 | |||
451 | if (is_null($vals)) |
||
452 | { |
||
453 | $vals = array_merge($this->values, (array) $this->where_values); |
||
454 | } |
||
455 | |||
456 | $start_time = microtime(TRUE); |
||
457 | |||
458 | $res = (empty($vals)) |
||
459 | ? $this->db->query($sql) |
||
0 ignored issues
–
show
The call to
DriverInterface::query() has too many arguments starting with $sql .
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. In this case you can add the
Loading history...
|
|||
460 | : $this->db->prepare_execute($sql, $vals); |
||
461 | |||
462 | $end_time = microtime(TRUE); |
||
463 | $total_time = number_format($end_time - $start_time, 5); |
||
464 | |||
465 | // Add this query to the list of executed queries |
||
466 | $this->_append_query($vals, $sql, $total_time); |
||
467 | |||
468 | // Reset class state for next query |
||
469 | if ($reset) |
||
470 | { |
||
471 | $this->reset_query(); |
||
472 | } |
||
473 | |||
474 | return $res; |
||
475 | } |
||
476 | |||
477 | // -------------------------------------------------------------------------- |
||
478 | |||
479 | /** |
||
480 | * Add an additional set of mapping pairs to a internal map |
||
481 | * |
||
482 | * @param string $conjunction |
||
483 | * @param string $string |
||
484 | * @param string $type |
||
485 | * @return void |
||
486 | */ |
||
487 | protected function _append_map($conjunction = '', $string = '', $type = '') |
||
488 | { |
||
489 | array_push($this->query_map, [ |
||
490 | 'type' => $type, |
||
491 | 'conjunction' => $conjunction, |
||
492 | 'string' => $string |
||
493 | ]); |
||
494 | } |
||
495 | |||
496 | // -------------------------------------------------------------------------- |
||
497 | |||
498 | /** |
||
499 | * Convert the prepared statement into readable sql |
||
500 | * |
||
501 | * @param array $vals |
||
502 | * @param string $sql |
||
503 | * @param int $total_time |
||
504 | * @return void |
||
505 | */ |
||
506 | protected function _append_query($vals, $sql, $total_time) |
||
507 | { |
||
508 | $evals = (is_array($vals)) ? $vals : []; |
||
509 | $esql = str_replace('?', "%s", $sql); |
||
510 | |||
511 | // Quote string values |
||
512 | foreach($evals as &$v) |
||
513 | { |
||
514 | $v = ( ! is_numeric($v)) ? htmlentities($this->db->quote($v), ENT_NOQUOTES, 'utf-8') : $v; |
||
515 | } |
||
516 | |||
517 | // Add the query onto the array of values to pass |
||
518 | // as arguments to sprintf |
||
519 | array_unshift($evals, $esql); |
||
520 | |||
521 | // Add the interpreted query to the list of executed queries |
||
522 | $this->queries[] = [ |
||
523 | 'time' => $total_time, |
||
524 | 'sql' => call_user_func_array('sprintf', $evals), |
||
525 | ]; |
||
526 | |||
527 | $this->queries['total_time'] += (int) $total_time; |
||
528 | |||
529 | // Set the last query to get rowcounts properly |
||
530 | $this->db->set_last_query($sql); |
||
531 | } |
||
532 | |||
533 | // -------------------------------------------------------------------------- |
||
534 | |||
535 | /** |
||
536 | * Sub-method for generating sql strings |
||
537 | * |
||
538 | * @param string $type |
||
539 | * @param string $table |
||
540 | * @return string |
||
541 | */ |
||
542 | protected function _compile_type($type='', $table='') |
||
543 | { |
||
544 | switch($type) |
||
545 | { |
||
546 | case "insert": |
||
547 | $param_count = count($this->set_array_keys); |
||
548 | $params = array_fill(0, $param_count, '?'); |
||
549 | $sql = "INSERT INTO {$table} (" |
||
550 | . implode(',', $this->set_array_keys) |
||
551 | . ")\nVALUES (".implode(',', $params).')'; |
||
552 | break; |
||
553 | |||
554 | case "update": |
||
555 | $sql = "UPDATE {$table}\nSET {$this->set_string}"; |
||
556 | break; |
||
557 | |||
558 | case "replace": |
||
559 | // @TODO implement |
||
560 | $sql = ""; |
||
561 | break; |
||
562 | |||
563 | case "delete": |
||
564 | $sql = "DELETE FROM {$table}"; |
||
565 | break; |
||
566 | |||
567 | // Get queries |
||
568 | default: |
||
569 | $sql = "SELECT * \nFROM {$this->from_string}"; |
||
570 | |||
571 | // Set the select string |
||
572 | if ( ! empty($this->select_string)) |
||
573 | { |
||
574 | // Replace the star with the selected fields |
||
575 | $sql = str_replace('*', $this->select_string, $sql); |
||
576 | } |
||
577 | break; |
||
578 | } |
||
579 | |||
580 | return $sql; |
||
581 | } |
||
582 | |||
583 | // -------------------------------------------------------------------------- |
||
584 | |||
585 | /** |
||
586 | * String together the sql statements for sending to the db |
||
587 | * |
||
588 | * @param string $type |
||
589 | * @param string $table |
||
590 | * @return string |
||
591 | */ |
||
592 | protected function _compile($type='', $table='') |
||
593 | { |
||
594 | // Get the base clause for the query |
||
595 | $sql = $this->_compile_type($type, $this->db->quote_table($table)); |
||
596 | |||
597 | $clauses = [ |
||
598 | 'query_map', |
||
599 | 'group_string', |
||
600 | 'order_string', |
||
601 | 'having_map', |
||
602 | ]; |
||
603 | |||
604 | // Set each type of subclause |
||
605 | foreach($clauses as $clause) |
||
606 | { |
||
607 | $param = $this->$clause; |
||
608 | if (is_array($param)) |
||
609 | { |
||
610 | foreach($param as $q) |
||
611 | { |
||
612 | $sql .= $q['conjunction'] . $q['string']; |
||
613 | } |
||
614 | } |
||
615 | else |
||
616 | { |
||
617 | $sql .= $param; |
||
618 | } |
||
619 | } |
||
620 | |||
621 | // Set the limit via the class variables |
||
622 | if (is_numeric($this->limit)) |
||
623 | { |
||
624 | $sql = $this->sql->limit($sql, $this->limit, $this->offset); |
||
625 | } |
||
626 | |||
627 | // See if the query plan, rather than the |
||
628 | // query data should be returned |
||
629 | if ($this->explain === TRUE) |
||
630 | { |
||
631 | $sql = $this->sql->explain($sql); |
||
632 | } |
||
633 | |||
634 | return $sql; |
||
635 | } |
||
636 | } |
||
637 | |||
638 | // End of abstract_QueryBuilder.php |