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 declare(strict_types=1); |
||
2 | /** |
||
3 | * Query |
||
4 | * |
||
5 | * SQL Query Builder / Database Abstraction Layer |
||
6 | * |
||
7 | * PHP version 7.1 |
||
8 | * |
||
9 | * @package Query |
||
10 | * @author Timothy J. Warren <[email protected]> |
||
11 | * @copyright 2012 - 2018 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 | namespace Query; |
||
16 | |||
17 | use function regexInArray; |
||
18 | |||
19 | use BadMethodCallException; |
||
20 | use PDOStatement; |
||
21 | use Query\Drivers\DriverInterface; |
||
22 | |||
23 | /** |
||
24 | * Convenience class for creating sql queries |
||
25 | */ |
||
26 | class QueryBuilder implements QueryBuilderInterface { |
||
27 | |||
28 | /** |
||
29 | * Convenience property for connection management |
||
30 | * @var string |
||
31 | */ |
||
32 | public $connName = ''; |
||
33 | |||
34 | /** |
||
35 | * List of queries executed |
||
36 | * @var array |
||
37 | */ |
||
38 | public $queries = [ |
||
39 | 'total_time' => 0 |
||
40 | ]; |
||
41 | |||
42 | /** |
||
43 | * Whether to do only an explain on the query |
||
44 | * @var boolean |
||
45 | */ |
||
46 | protected $explain = FALSE; |
||
47 | |||
48 | /** |
||
49 | * The current database driver |
||
50 | * @var DriverInterface |
||
51 | */ |
||
52 | public $driver; |
||
53 | |||
54 | /** |
||
55 | * Query parser class instance |
||
56 | * @var QueryParser |
||
57 | */ |
||
58 | protected $parser; |
||
59 | |||
60 | /** |
||
61 | * Query Builder state |
||
62 | * @var State |
||
63 | */ |
||
64 | protected $state; |
||
65 | |||
66 | // -------------------------------------------------------------------------- |
||
67 | // ! Methods |
||
68 | // -------------------------------------------------------------------------- |
||
69 | |||
70 | /** |
||
71 | * Constructor |
||
72 | * |
||
73 | * @param DriverInterface $driver |
||
74 | * @param QueryParser $parser |
||
75 | */ |
||
76 | public function __construct(DriverInterface $driver, QueryParser $parser) |
||
77 | { |
||
78 | // Inject driver and parser |
||
79 | $this->driver = $driver; |
||
80 | $this->parser = $parser; |
||
81 | |||
82 | // Create new State object |
||
83 | $this->state = new State(); |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Destructor |
||
88 | * @codeCoverageIgnore |
||
89 | */ |
||
90 | public function __destruct() |
||
91 | { |
||
92 | $this->driver = NULL; |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Calls a function further down the inheritance chain |
||
97 | * |
||
98 | * @param string $name |
||
99 | * @param array $params |
||
100 | * @return mixed |
||
101 | * @throws BadMethodCallException |
||
102 | */ |
||
103 | public function __call(string $name, array $params) |
||
104 | { |
||
105 | if (method_exists($this->driver, $name)) |
||
106 | { |
||
107 | return \call_user_func_array([$this->driver, $name], $params); |
||
108 | } |
||
109 | |||
110 | throw new BadMethodCallException('Method does not exist'); |
||
111 | } |
||
112 | |||
113 | // -------------------------------------------------------------------------- |
||
114 | // ! Select Queries |
||
115 | // -------------------------------------------------------------------------- |
||
116 | |||
117 | /** |
||
118 | * Specifies rows to select in a query |
||
119 | * |
||
120 | * @param string $fields |
||
121 | * @return QueryBuilderInterface |
||
122 | */ |
||
123 | public function select(string $fields): QueryBuilderInterface |
||
124 | { |
||
125 | // Split fields by comma |
||
126 | $fieldsArray = explode(',', $fields); |
||
127 | $fieldsArray = array_map('mb_trim', $fieldsArray); |
||
128 | |||
129 | // Split on 'As' |
||
130 | foreach ($fieldsArray as $key => $field) |
||
131 | { |
||
132 | if (stripos($field, 'as') !== FALSE) |
||
133 | { |
||
134 | $fieldsArray[$key] = preg_split('` as `i', $field); |
||
135 | $fieldsArray[$key] = array_map('mb_trim', $fieldsArray[$key]); |
||
136 | } |
||
137 | } |
||
138 | |||
139 | // Quote the identifiers |
||
140 | $safeArray = $this->driver->quoteIdent($fieldsArray); |
||
141 | |||
142 | unset($fieldsArray); |
||
143 | |||
144 | // Join the strings back together |
||
145 | for($i = 0, $c = count($safeArray); $i < $c; $i++) |
||
146 | { |
||
147 | if (\is_array($safeArray[$i])) |
||
148 | { |
||
149 | $safeArray[$i] = implode(' AS ', $safeArray[$i]); |
||
150 | } |
||
151 | } |
||
152 | |||
153 | $this->state->appendSelectString(implode(', ', $safeArray)); |
||
154 | |||
155 | return $this; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Selects the maximum value of a field from a query |
||
160 | * |
||
161 | * @param string $field |
||
162 | * @param string|bool $as |
||
163 | * @return QueryBuilderInterface |
||
164 | */ |
||
165 | public function selectMax(string $field, $as=FALSE): QueryBuilderInterface |
||
166 | { |
||
167 | // Create the select string |
||
168 | $this->state->appendSelectString(' MAX'.$this->_select($field, $as)); |
||
169 | return $this; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Selects the minimum value of a field from a query |
||
174 | * |
||
175 | * @param string $field |
||
176 | * @param string|bool $as |
||
177 | * @return QueryBuilderInterface |
||
178 | */ |
||
179 | public function selectMin(string $field, $as=FALSE): QueryBuilderInterface |
||
180 | { |
||
181 | // Create the select string |
||
182 | $this->state->appendSelectString(' MIN'.$this->_select($field, $as)); |
||
183 | return $this; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Selects the average value of a field from a query |
||
188 | * |
||
189 | * @param string $field |
||
190 | * @param string|bool $as |
||
191 | * @return QueryBuilderInterface |
||
192 | */ |
||
193 | public function selectAvg(string $field, $as=FALSE): QueryBuilderInterface |
||
194 | { |
||
195 | // Create the select string |
||
196 | $this->state->appendSelectString(' AVG'.$this->_select($field, $as)); |
||
197 | return $this; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Selects the sum of a field from a query |
||
202 | * |
||
203 | * @param string $field |
||
204 | * @param string|bool $as |
||
205 | * @return QueryBuilderInterface |
||
206 | */ |
||
207 | public function selectSum(string $field, $as=FALSE): QueryBuilderInterface |
||
208 | { |
||
209 | // Create the select string |
||
210 | $this->state->appendSelectString(' SUM'.$this->_select($field, $as)); |
||
211 | return $this; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Adds the 'distinct' keyword to a query |
||
216 | * |
||
217 | * @return QueryBuilderInterface |
||
218 | */ |
||
219 | public function distinct(): QueryBuilderInterface |
||
220 | { |
||
221 | // Prepend the keyword to the select string |
||
222 | $this->state->setSelectString(' DISTINCT' . $this->state->getSelectString()); |
||
223 | return $this; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Tell the database to give you the query plan instead of result set |
||
228 | * |
||
229 | * @return QueryBuilderInterface |
||
230 | */ |
||
231 | public function explain(): QueryBuilderInterface |
||
232 | { |
||
233 | $this->explain = TRUE; |
||
234 | return $this; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Specify the database table to select from |
||
239 | * |
||
240 | * @param string $tblname |
||
241 | * @return QueryBuilderInterface |
||
242 | */ |
||
243 | public function from(string $tblname): QueryBuilderInterface |
||
244 | { |
||
245 | // Split identifiers on spaces |
||
246 | $identArray = explode(' ', \mb_trim($tblname)); |
||
247 | $identArray = array_map('\\mb_trim', $identArray); |
||
248 | |||
249 | // Quote the identifiers |
||
250 | $identArray[0] = $this->driver->quoteTable($identArray[0]); |
||
251 | $identArray = $this->driver->quoteIdent($identArray); |
||
252 | |||
253 | // Paste it back together |
||
254 | $this->state->setFromString(implode(' ', $identArray)); |
||
255 | |||
256 | return $this; |
||
257 | } |
||
258 | |||
259 | // -------------------------------------------------------------------------- |
||
260 | // ! 'Like' methods |
||
261 | // -------------------------------------------------------------------------- |
||
262 | |||
263 | /** |
||
264 | * Creates a Like clause in the sql statement |
||
265 | * |
||
266 | * @param string $field |
||
267 | * @param mixed $val |
||
268 | * @param string $pos |
||
269 | * @return QueryBuilderInterface |
||
270 | */ |
||
271 | public function like(string $field, $val, string $pos='both'): QueryBuilderInterface |
||
272 | { |
||
273 | return $this->_like($field, $val, $pos); |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Generates an OR Like clause |
||
278 | * |
||
279 | * @param string $field |
||
280 | * @param mixed $val |
||
281 | * @param string $pos |
||
282 | * @return QueryBuilderInterface |
||
283 | */ |
||
284 | public function orLike(string $field, $val, string $pos='both'): QueryBuilderInterface |
||
285 | { |
||
286 | return $this->_like($field, $val, $pos, 'LIKE', 'OR'); |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Generates a NOT LIKE clause |
||
291 | * |
||
292 | * @param string $field |
||
293 | * @param mixed $val |
||
294 | * @param string $pos |
||
295 | * @return QueryBuilderInterface |
||
296 | */ |
||
297 | public function notLike(string $field, $val, string $pos='both'): QueryBuilderInterface |
||
298 | { |
||
299 | return $this->_like($field, $val, $pos, 'NOT LIKE'); |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Generates a OR NOT LIKE clause |
||
304 | * |
||
305 | * @param string $field |
||
306 | * @param mixed $val |
||
307 | * @param string $pos |
||
308 | * @return QueryBuilderInterface |
||
309 | */ |
||
310 | public function orNotLike(string $field, $val, string $pos='both'): QueryBuilderInterface |
||
311 | { |
||
312 | return $this->_like($field, $val, $pos, 'NOT LIKE', 'OR'); |
||
313 | } |
||
314 | |||
315 | // -------------------------------------------------------------------------- |
||
316 | // ! Having methods |
||
317 | // -------------------------------------------------------------------------- |
||
318 | |||
319 | /** |
||
320 | * Generates a 'Having' clause |
||
321 | * |
||
322 | * @param mixed $key |
||
323 | * @param mixed $val |
||
324 | * @return QueryBuilderInterface |
||
325 | */ |
||
326 | public function having($key, $val=[]): QueryBuilderInterface |
||
327 | { |
||
328 | return $this->_having($key, $val); |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * Generates a 'Having' clause prefixed with 'OR' |
||
333 | * |
||
334 | * @param mixed $key |
||
335 | * @param mixed $val |
||
336 | * @return QueryBuilderInterface |
||
337 | */ |
||
338 | public function orHaving($key, $val=[]): QueryBuilderInterface |
||
339 | { |
||
340 | return $this->_having($key, $val, 'OR'); |
||
341 | } |
||
342 | |||
343 | // -------------------------------------------------------------------------- |
||
344 | // ! 'Where' methods |
||
345 | // -------------------------------------------------------------------------- |
||
346 | |||
347 | /** |
||
348 | * Specify condition(s) in the where clause of a query |
||
349 | * Note: this function works with key / value, or a |
||
350 | * passed array with key / value pairs |
||
351 | * |
||
352 | * @param mixed $key |
||
353 | * @param mixed $val |
||
354 | * @param mixed $escape |
||
355 | * @return QueryBuilderInterface |
||
356 | */ |
||
357 | public function where($key, $val=[], $escape=NULL): QueryBuilderInterface |
||
358 | { |
||
359 | return $this->_whereString($key, $val); |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Where clause prefixed with "OR" |
||
364 | * |
||
365 | * @param string $key |
||
366 | * @param mixed $val |
||
367 | * @return QueryBuilderInterface |
||
368 | */ |
||
369 | public function orWhere($key, $val=[]): QueryBuilderInterface |
||
370 | { |
||
371 | return $this->_whereString($key, $val, 'OR'); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Where clause with 'IN' statement |
||
376 | * |
||
377 | * @param mixed $field |
||
378 | * @param mixed $val |
||
379 | * @return QueryBuilderInterface |
||
380 | */ |
||
381 | public function whereIn($field, $val=[]): QueryBuilderInterface |
||
382 | { |
||
383 | return $this->_whereIn($field, $val); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Where in statement prefixed with "or" |
||
388 | * |
||
389 | * @param string $field |
||
390 | * @param mixed $val |
||
391 | * @return QueryBuilderInterface |
||
392 | */ |
||
393 | public function orWhereIn($field, $val=[]): QueryBuilderInterface |
||
394 | { |
||
395 | return $this->_whereIn($field, $val, 'IN', 'OR'); |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * WHERE NOT IN (FOO) clause |
||
400 | * |
||
401 | * @param string $field |
||
402 | * @param mixed $val |
||
403 | * @return QueryBuilderInterface |
||
404 | */ |
||
405 | public function whereNotIn($field, $val=[]): QueryBuilderInterface |
||
406 | { |
||
407 | return $this->_whereIn($field, $val, 'NOT IN'); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * OR WHERE NOT IN (FOO) clause |
||
412 | * |
||
413 | * @param string $field |
||
414 | * @param mixed $val |
||
415 | * @return QueryBuilderInterface |
||
416 | */ |
||
417 | public function orWhereNotIn($field, $val=[]): QueryBuilderInterface |
||
418 | { |
||
419 | return $this->_whereIn($field, $val, 'NOT IN', 'OR'); |
||
420 | } |
||
421 | |||
422 | // -------------------------------------------------------------------------- |
||
423 | // ! Other Query Modifier methods |
||
424 | // -------------------------------------------------------------------------- |
||
425 | |||
426 | /** |
||
427 | * Sets values for inserts / updates / deletes |
||
428 | * |
||
429 | * @param mixed $key |
||
430 | * @param mixed $val |
||
431 | * @return QueryBuilderInterface |
||
432 | */ |
||
433 | public function set($key, $val = NULL): QueryBuilderInterface |
||
434 | { |
||
435 | if (is_scalar($key)) |
||
436 | { |
||
437 | $pairs = [$key => $val]; |
||
438 | } |
||
439 | else |
||
440 | { |
||
441 | $pairs = $key; |
||
442 | } |
||
443 | |||
444 | $keys = array_keys($pairs); |
||
445 | $values = array_values($pairs); |
||
446 | |||
447 | $this->state->appendSetArrayKeys($keys); |
||
448 | $this->state->appendValues($values); |
||
449 | |||
450 | // Use the keys of the array to make the insert/update string |
||
451 | // Escape the field names |
||
452 | $this->state->setSetArrayKeys( |
||
453 | array_map([$this->driver, '_quote'], $this->state->getSetArrayKeys()) |
||
454 | ); |
||
455 | |||
456 | // Generate the "set" string |
||
457 | $setString = implode('=?,', $this->state->getSetArrayKeys()); |
||
458 | $setString .= '=?'; |
||
459 | |||
460 | $this->state->setSetString($setString); |
||
461 | |||
462 | return $this; |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Creates a join phrase in a compiled query |
||
467 | * |
||
468 | * @param string $table |
||
469 | * @param string $condition |
||
470 | * @param string $type |
||
471 | * @return QueryBuilderInterface |
||
472 | */ |
||
473 | public function join(string $table, string $condition, string $type=''): QueryBuilderInterface |
||
474 | { |
||
475 | // Prefix and quote table name |
||
476 | $table = explode(' ', mb_trim($table)); |
||
477 | $table[0] = $this->driver->quoteTable($table[0]); |
||
478 | $table = $this->driver->quoteIdent($table); |
||
479 | $table = implode(' ', $table); |
||
480 | |||
481 | // Parse out the join condition |
||
482 | $parsedCondition = $this->parser->compileJoin($condition); |
||
483 | $condition = $table . ' ON ' . $parsedCondition; |
||
484 | |||
485 | $this->state->appendMap("\n" . strtoupper($type) . ' JOIN ', $condition, 'join'); |
||
486 | |||
487 | return $this; |
||
488 | } |
||
489 | |||
490 | /** |
||
491 | * Group the results by the selected field(s) |
||
492 | * |
||
493 | * @param mixed $field |
||
494 | * @return QueryBuilderInterface |
||
495 | */ |
||
496 | public function groupBy($field): QueryBuilderInterface |
||
497 | { |
||
498 | if ( ! is_scalar($field)) |
||
499 | { |
||
500 | $newGroupArray = array_map([$this->driver, 'quoteIdent'], $field); |
||
501 | $this->state->setGroupArray( |
||
502 | array_merge($this->state->getGroupArray(), $newGroupArray) |
||
503 | ); |
||
504 | } |
||
505 | else |
||
506 | { |
||
507 | $this->state->appendGroupArray($this->driver->quoteIdent($field)); |
||
0 ignored issues
–
show
|
|||
508 | } |
||
509 | |||
510 | $this->state->setGroupString(' GROUP BY ' . implode(',', $this->state->getGroupArray())); |
||
511 | |||
512 | return $this; |
||
513 | } |
||
514 | |||
515 | /** |
||
516 | * Order the results by the selected field(s) |
||
517 | * |
||
518 | * @param string $field |
||
519 | * @param string $type |
||
520 | * @return QueryBuilderInterface |
||
521 | */ |
||
522 | public function orderBy(string $field, string $type=''): QueryBuilderInterface |
||
523 | { |
||
524 | // When ordering by random, do an ascending order if the driver |
||
525 | // doesn't support random ordering |
||
526 | if (stripos($type, 'rand') !== FALSE) |
||
527 | { |
||
528 | $rand = $this->driver->getSql()->random(); |
||
529 | $type = $rand ?? 'ASC'; |
||
530 | } |
||
531 | |||
532 | // Set fields for later manipulation |
||
533 | $field = $this->driver->quoteIdent($field); |
||
534 | $this->state->setOrderArray($field, $type); |
||
0 ignored issues
–
show
It seems like
$field defined by $this->driver->quoteIdent($field) on line 533 can also be of type array ; however, Query\State::setOrderArray() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
535 | |||
536 | $orderClauses = []; |
||
537 | |||
538 | // Flatten key/val pairs into an array of space-separated pairs |
||
539 | foreach($this->state->getOrderArray() as $k => $v) |
||
540 | { |
||
541 | $orderClauses[] = $k . ' ' . strtoupper($v); |
||
542 | } |
||
543 | |||
544 | // Set the final string |
||
545 | $orderString = ! isset($rand) |
||
546 | ? "\nORDER BY ".implode(', ', $orderClauses) |
||
547 | : "\nORDER BY".$rand; |
||
548 | |||
549 | $this->state->setOrderString($orderString); |
||
550 | |||
551 | return $this; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * Set a limit on the current sql statement |
||
556 | * |
||
557 | * @param int $limit |
||
558 | * @param int|bool $offset |
||
559 | * @return QueryBuilderInterface |
||
560 | */ |
||
561 | public function limit(int $limit, $offset=FALSE): QueryBuilderInterface |
||
562 | { |
||
563 | $this->state->setLimit($limit); |
||
564 | $this->state->setOffset($offset); |
||
0 ignored issues
–
show
$offset is of type integer|boolean , but the function expects a string|false .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
565 | |||
566 | return $this; |
||
567 | } |
||
568 | |||
569 | // -------------------------------------------------------------------------- |
||
570 | // ! Query Grouping Methods |
||
571 | // -------------------------------------------------------------------------- |
||
572 | |||
573 | /** |
||
574 | * Adds a paren to the current query for query grouping |
||
575 | * |
||
576 | * @return QueryBuilderInterface |
||
577 | */ |
||
578 | public function groupStart(): QueryBuilderInterface |
||
579 | { |
||
580 | $conj = empty($this->state->getQueryMap()) ? ' WHERE ' : ' '; |
||
581 | |||
582 | $this->state->appendMap($conj, '(', 'group_start'); |
||
583 | |||
584 | return $this; |
||
585 | } |
||
586 | |||
587 | /** |
||
588 | * Adds a paren to the current query for query grouping, |
||
589 | * prefixed with 'NOT' |
||
590 | * |
||
591 | * @return QueryBuilderInterface |
||
592 | */ |
||
593 | public function notGroupStart(): QueryBuilderInterface |
||
594 | { |
||
595 | $conj = empty($this->state->getQueryMap()) ? ' WHERE ' : ' AND '; |
||
596 | |||
597 | $this->state->appendMap($conj, ' NOT (', 'group_start'); |
||
598 | |||
599 | return $this; |
||
600 | } |
||
601 | |||
602 | /** |
||
603 | * Adds a paren to the current query for query grouping, |
||
604 | * prefixed with 'OR' |
||
605 | * |
||
606 | * @return QueryBuilderInterface |
||
607 | */ |
||
608 | public function orGroupStart(): QueryBuilderInterface |
||
609 | { |
||
610 | $this->state->appendMap('', ' OR (', 'group_start'); |
||
611 | |||
612 | return $this; |
||
613 | } |
||
614 | |||
615 | /** |
||
616 | * Adds a paren to the current query for query grouping, |
||
617 | * prefixed with 'OR NOT' |
||
618 | * |
||
619 | * @return QueryBuilderInterface |
||
620 | */ |
||
621 | public function orNotGroupStart(): QueryBuilderInterface |
||
622 | { |
||
623 | $this->state->appendMap('', ' OR NOT (', 'group_start'); |
||
624 | |||
625 | return $this; |
||
626 | } |
||
627 | |||
628 | /** |
||
629 | * Ends a query group |
||
630 | * |
||
631 | * @return QueryBuilderInterface |
||
632 | */ |
||
633 | public function groupEnd(): QueryBuilderInterface |
||
634 | { |
||
635 | $this->state->appendMap('', ')', 'group_end'); |
||
636 | |||
637 | return $this; |
||
638 | } |
||
639 | |||
640 | // -------------------------------------------------------------------------- |
||
641 | // ! Query execution methods |
||
642 | // -------------------------------------------------------------------------- |
||
643 | |||
644 | /** |
||
645 | * Select and retrieve all records from the current table, and/or |
||
646 | * execute current compiled query |
||
647 | * |
||
648 | * @param string $table |
||
649 | * @param int|bool $limit |
||
650 | * @param int|bool $offset |
||
651 | * @return PDOStatement |
||
652 | */ |
||
653 | public function get(string $table='', $limit=FALSE, $offset=FALSE): PDOStatement |
||
654 | { |
||
655 | // Set the table |
||
656 | if ( ! empty($table)) |
||
657 | { |
||
658 | $this->from($table); |
||
659 | } |
||
660 | |||
661 | // Set the limit, if it exists |
||
662 | if (\is_int($limit)) |
||
663 | { |
||
664 | $this->limit($limit, $offset); |
||
665 | } |
||
666 | |||
667 | return $this->_run('get', $table); |
||
668 | } |
||
669 | |||
670 | /** |
||
671 | * Convenience method for get() with a where clause |
||
672 | * |
||
673 | * @param string $table |
||
674 | * @param mixed $where |
||
675 | * @param int|bool $limit |
||
676 | * @param int|bool $offset |
||
677 | * @return PDOStatement |
||
678 | */ |
||
679 | public function getWhere(string $table, $where=[], $limit=FALSE, $offset=FALSE): PDOStatement |
||
680 | { |
||
681 | // Create the where clause |
||
682 | $this->where($where); |
||
683 | |||
684 | // Return the result |
||
685 | return $this->get($table, $limit, $offset); |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Retrieve the number of rows in the selected table |
||
690 | * |
||
691 | * @param string $table |
||
692 | * @return int |
||
693 | */ |
||
694 | public function countAll(string $table): int |
||
695 | { |
||
696 | $sql = 'SELECT * FROM '.$this->driver->quoteTable($table); |
||
697 | $res = $this->driver->query($sql); |
||
698 | return (int) count($res->fetchAll()); |
||
699 | } |
||
700 | |||
701 | /** |
||
702 | * Retrieve the number of results for the generated query - used |
||
703 | * in place of the get() method |
||
704 | * |
||
705 | * @param string $table |
||
706 | * @param boolean $reset |
||
707 | * @return int |
||
708 | */ |
||
709 | public function countAllResults(string $table='', bool $reset = TRUE): int |
||
710 | { |
||
711 | // Set the table |
||
712 | if ( ! empty($table)) |
||
713 | { |
||
714 | $this->from($table); |
||
715 | } |
||
716 | |||
717 | $result = $this->_run('get', $table, NULL, NULL, $reset); |
||
718 | $rows = $result->fetchAll(); |
||
719 | |||
720 | return (int) count($rows); |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * Creates an insert clause, and executes it |
||
725 | * |
||
726 | * @param string $table |
||
727 | * @param mixed $data |
||
728 | * @return PDOStatement |
||
729 | */ |
||
730 | public function insert(string $table, $data=[]): PDOStatement |
||
731 | { |
||
732 | if ( ! empty($data)) |
||
733 | { |
||
734 | $this->set($data); |
||
735 | } |
||
736 | |||
737 | return $this->_run('insert', $table); |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * Creates and executes a batch insertion query |
||
742 | * |
||
743 | * @param string $table |
||
744 | * @param array $data |
||
745 | * @return PDOStatement |
||
746 | */ |
||
747 | public function insertBatch(string $table, $data=[]): PDOStatement |
||
748 | { |
||
749 | // Get the generated values and sql string |
||
750 | [$sql, $data] = $this->driver->insertBatch($table, $data); |
||
0 ignored issues
–
show
|
|||
751 | |||
752 | return $sql !== NULL |
||
753 | ? $this->_run('', $table, $sql, $data) |
||
754 | : NULL; |
||
755 | } |
||
756 | |||
757 | /** |
||
758 | * Creates an update clause, and executes it |
||
759 | * |
||
760 | * @param string $table |
||
761 | * @param mixed $data |
||
762 | * @return PDOStatement |
||
763 | */ |
||
764 | public function update(string $table, $data=[]): PDOStatement |
||
765 | { |
||
766 | if ( ! empty($data)) |
||
767 | { |
||
768 | $this->set($data); |
||
769 | } |
||
770 | |||
771 | return $this->_run('update', $table); |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * Creates a batch update, and executes it. |
||
776 | * Returns the number of affected rows |
||
777 | * |
||
778 | * @param string $table |
||
779 | * @param array $data |
||
780 | * @param string $where |
||
781 | * @return int|null |
||
782 | */ |
||
783 | public function updateBatch(string $table, array $data, string $where): ?int |
||
784 | { |
||
785 | if (empty($table) || empty($data) || empty($where)) |
||
786 | { |
||
787 | return NULL; |
||
788 | } |
||
789 | |||
790 | // Get the generated values and sql string |
||
791 | [$sql, $data, $affectedRows] = $this->driver->updateBatch($table, $data, $where); |
||
0 ignored issues
–
show
The variable
$affectedRows does not exist. Did you forget to declare it?
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug. ![]() |
|||
792 | |||
793 | $this->_run('', $table, $sql, $data); |
||
794 | return $affectedRows; |
||
795 | } |
||
796 | |||
797 | /** |
||
798 | * Deletes data from a table |
||
799 | * |
||
800 | * @param string $table |
||
801 | * @param mixed $where |
||
802 | * @return PDOStatement |
||
803 | */ |
||
804 | public function delete(string $table, $where=''): PDOStatement |
||
805 | { |
||
806 | // Set the where clause |
||
807 | if ( ! empty($where)) |
||
808 | { |
||
809 | $this->where($where); |
||
810 | } |
||
811 | |||
812 | return $this->_run('delete', $table); |
||
813 | } |
||
814 | |||
815 | // -------------------------------------------------------------------------- |
||
816 | // ! SQL Returning Methods |
||
817 | // -------------------------------------------------------------------------- |
||
818 | |||
819 | /** |
||
820 | * Returns the generated 'select' sql query |
||
821 | * |
||
822 | * @param string $table |
||
823 | * @param bool $reset |
||
824 | * @return string |
||
825 | */ |
||
826 | public function getCompiledSelect(string $table='', bool $reset=TRUE): string |
||
827 | { |
||
828 | // Set the table |
||
829 | if ( ! empty($table)) |
||
830 | { |
||
831 | $this->from($table); |
||
832 | } |
||
833 | |||
834 | return $this->_getCompile('select', $table, $reset); |
||
835 | } |
||
836 | |||
837 | /** |
||
838 | * Returns the generated 'insert' sql query |
||
839 | * |
||
840 | * @param string $table |
||
841 | * @param bool $reset |
||
842 | * @return string |
||
843 | */ |
||
844 | public function getCompiledInsert(string $table, bool $reset=TRUE): string |
||
845 | { |
||
846 | return $this->_getCompile('insert', $table, $reset); |
||
847 | } |
||
848 | |||
849 | /** |
||
850 | * Returns the generated 'update' sql query |
||
851 | * |
||
852 | * @param string $table |
||
853 | * @param bool $reset |
||
854 | * @return string |
||
855 | */ |
||
856 | public function getCompiledUpdate(string $table='', bool $reset=TRUE): string |
||
857 | { |
||
858 | return $this->_getCompile('update', $table, $reset); |
||
859 | } |
||
860 | |||
861 | /** |
||
862 | * Returns the generated 'delete' sql query |
||
863 | * |
||
864 | * @param string $table |
||
865 | * @param bool $reset |
||
866 | * @return string |
||
867 | */ |
||
868 | public function getCompiledDelete(string $table='', bool $reset=TRUE): string |
||
869 | { |
||
870 | return $this->_getCompile('delete', $table, $reset); |
||
871 | } |
||
872 | |||
873 | // -------------------------------------------------------------------------- |
||
874 | // ! Miscellaneous Methods |
||
875 | // -------------------------------------------------------------------------- |
||
876 | |||
877 | /** |
||
878 | * Clear out the class variables, so the next query can be run |
||
879 | * |
||
880 | * @return void |
||
881 | */ |
||
882 | public function resetQuery(): void |
||
883 | { |
||
884 | $this->state = new State(); |
||
885 | $this->explain = FALSE; |
||
886 | } |
||
887 | |||
888 | /** |
||
889 | * Method to simplify select_ methods |
||
890 | * |
||
891 | * @param string $field |
||
892 | * @param string|bool $as |
||
893 | * @return string |
||
894 | */ |
||
895 | protected function _select(string $field, $as = FALSE): string |
||
896 | { |
||
897 | // Escape the identifiers |
||
898 | $field = $this->driver->quoteIdent($field); |
||
899 | |||
900 | if ( ! \is_string($as)) |
||
901 | { |
||
902 | // @codeCoverageIgnoreStart |
||
903 | return $field; |
||
904 | // @codeCoverageIgnoreEnd |
||
905 | } |
||
906 | |||
907 | $as = $this->driver->quoteIdent($as); |
||
908 | return "({$field}) AS {$as} "; |
||
909 | } |
||
910 | |||
911 | /** |
||
912 | * Helper function for returning sql strings |
||
913 | * |
||
914 | * @param string $type |
||
915 | * @param string $table |
||
916 | * @param bool $reset |
||
917 | * @return string |
||
918 | */ |
||
919 | protected function _getCompile(string $type, string $table, bool $reset): string |
||
920 | { |
||
921 | $sql = $this->_compile($type, $table); |
||
922 | |||
923 | // Reset the query builder for the next query |
||
924 | if ($reset) |
||
925 | { |
||
926 | $this->resetQuery(); |
||
927 | } |
||
928 | |||
929 | return $sql; |
||
930 | } |
||
931 | |||
932 | /** |
||
933 | * Simplify 'like' methods |
||
934 | * |
||
935 | * @param string $field |
||
936 | * @param mixed $val |
||
937 | * @param string $pos |
||
938 | * @param string $like |
||
939 | * @param string $conj |
||
940 | * @return self |
||
941 | */ |
||
942 | protected function _like(string $field, $val, string $pos, string $like='LIKE', string $conj='AND'): self |
||
943 | { |
||
944 | $field = $this->driver->quoteIdent($field); |
||
945 | |||
946 | // Add the like string into the order map |
||
947 | $like = $field. " {$like} ?"; |
||
948 | |||
949 | if ($pos === 'before') |
||
950 | { |
||
951 | $val = "%{$val}"; |
||
952 | } |
||
953 | elseif ($pos === 'after') |
||
954 | { |
||
955 | $val = "{$val}%"; |
||
956 | } |
||
957 | else |
||
958 | { |
||
959 | $val = "%{$val}%"; |
||
960 | } |
||
961 | |||
962 | $conj = empty($this->state->getQueryMap()) ? ' WHERE ' : " {$conj} "; |
||
963 | $this->state->appendMap($conj, $like, 'like'); |
||
964 | |||
965 | // Add to the values array |
||
966 | $this->state->appendWhereValues($val); |
||
967 | |||
968 | return $this; |
||
969 | } |
||
970 | |||
971 | /** |
||
972 | * Simplify building having clauses |
||
973 | * |
||
974 | * @param mixed $key |
||
975 | * @param mixed $values |
||
976 | * @param string $conj |
||
977 | * @return self |
||
978 | */ |
||
979 | protected function _having($key, $values=[], string $conj='AND'): self |
||
980 | { |
||
981 | $where = $this->_where($key, $values); |
||
982 | |||
983 | // Create key/value placeholders |
||
984 | foreach($where as $f => $val) |
||
985 | { |
||
986 | // Split each key by spaces, in case there |
||
987 | // is an operator such as >, <, !=, etc. |
||
988 | $fArray = explode(' ', trim($f)); |
||
989 | |||
990 | $item = $this->driver->quoteIdent($fArray[0]); |
||
991 | |||
992 | // Simple key value, or an operator |
||
993 | $item .= (count($fArray) === 1) ? '=?' : " {$fArray[1]} ?"; |
||
994 | |||
995 | // Put in the having map |
||
996 | $this->state->appendHavingMap([ |
||
997 | 'conjunction' => empty($this->state->getHavingMap()) |
||
998 | ? ' HAVING ' |
||
999 | : " {$conj} ", |
||
1000 | 'string' => $item |
||
1001 | ]); |
||
1002 | } |
||
1003 | |||
1004 | return $this; |
||
1005 | } |
||
1006 | |||
1007 | /** |
||
1008 | * Do all the redundant stuff for where/having type methods |
||
1009 | * |
||
1010 | * @param mixed $key |
||
1011 | * @param mixed $val |
||
1012 | * @return array |
||
1013 | */ |
||
1014 | protected function _where($key, $val=[]): array |
||
1015 | { |
||
1016 | $where = []; |
||
1017 | $pairs = []; |
||
1018 | |||
1019 | if (is_scalar($key)) |
||
1020 | { |
||
1021 | $pairs[$key] = $val; |
||
1022 | } |
||
1023 | else |
||
1024 | { |
||
1025 | $pairs = $key; |
||
1026 | } |
||
1027 | |||
1028 | foreach($pairs as $k => $v) |
||
0 ignored issues
–
show
The expression
$pairs of type object|array|null is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
![]() |
|||
1029 | { |
||
1030 | $where[$k] = $v; |
||
1031 | $this->state->appendWhereValues($v); |
||
1032 | } |
||
1033 | |||
1034 | return $where; |
||
1035 | } |
||
1036 | |||
1037 | /** |
||
1038 | * Simplify generating where string |
||
1039 | * |
||
1040 | * @param mixed $key |
||
1041 | * @param mixed $values |
||
1042 | * @param string $defaultConj |
||
1043 | * @return self |
||
1044 | */ |
||
1045 | protected function _whereString($key, $values=[], string $defaultConj='AND'): self |
||
1046 | { |
||
1047 | // Create key/value placeholders |
||
1048 | foreach($this->_where($key, $values) as $f => $val) |
||
1049 | { |
||
1050 | $queryMap = $this->state->getQueryMap(); |
||
1051 | |||
1052 | // Split each key by spaces, in case there |
||
1053 | // is an operator such as >, <, !=, etc. |
||
1054 | $fArray = explode(' ', trim($f)); |
||
1055 | |||
1056 | $item = $this->driver->quoteIdent($fArray[0]); |
||
1057 | |||
1058 | // Simple key value, or an operator |
||
1059 | $item .= (count($fArray) === 1) ? '=?' : " {$fArray[1]} ?"; |
||
1060 | $lastItem = end($queryMap); |
||
1061 | |||
1062 | // Determine the correct conjunction |
||
1063 | $conjunctionList = array_column($queryMap, 'conjunction'); |
||
1064 | if (empty($queryMap) || ( ! regexInArray($conjunctionList, "/^ ?\n?WHERE/i"))) |
||
1065 | { |
||
1066 | $conj = "\nWHERE "; |
||
1067 | } |
||
1068 | elseif ($lastItem['type'] === 'group_start') |
||
1069 | { |
||
1070 | $conj = ''; |
||
1071 | } |
||
1072 | else |
||
1073 | { |
||
1074 | $conj = " {$defaultConj} "; |
||
1075 | } |
||
1076 | |||
1077 | $this->state->appendMap($conj, $item, 'where'); |
||
1078 | } |
||
1079 | |||
1080 | return $this; |
||
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * Simplify where_in methods |
||
1085 | * |
||
1086 | * @param mixed $key |
||
1087 | * @param mixed $val |
||
1088 | * @param string $in - The (not) in fragment |
||
1089 | * @param string $conj - The where in conjunction |
||
1090 | * @return self |
||
1091 | */ |
||
1092 | protected function _whereIn($key, $val=[], string $in='IN', string $conj='AND'): self |
||
1093 | { |
||
1094 | $key = $this->driver->quoteIdent($key); |
||
1095 | $params = array_fill(0, count($val), '?'); |
||
1096 | $this->state->appendWhereValues($val); |
||
1097 | |||
1098 | $conjunction = empty($this->state->getQueryMap()) ? ' WHERE ' : " {$conj} "; |
||
1099 | $str = $key . " {$in} (".implode(',', $params).') '; |
||
1100 | |||
1101 | $this->state->appendMap($conjunction, $str, 'where_in'); |
||
1102 | |||
1103 | return $this; |
||
1104 | } |
||
1105 | |||
1106 | /** |
||
1107 | * Executes the compiled query |
||
1108 | * |
||
1109 | * @param string $type |
||
1110 | * @param string $table |
||
1111 | * @param string $sql |
||
1112 | * @param array|null $vals |
||
1113 | * @param boolean $reset |
||
1114 | * @return PDOStatement |
||
1115 | */ |
||
1116 | protected function _run(string $type, string $table, string $sql=NULL, array $vals=NULL, bool $reset=TRUE): PDOStatement |
||
1117 | { |
||
1118 | if ($sql === NULL) |
||
1119 | { |
||
1120 | $sql = $this->_compile($type, $table); |
||
1121 | } |
||
1122 | |||
1123 | if ($vals === NULL) |
||
1124 | { |
||
1125 | $vals = array_merge($this->state->getValues(), $this->state->getWhereValues()); |
||
1126 | } |
||
1127 | |||
1128 | $startTime = microtime(TRUE); |
||
1129 | |||
1130 | $res = empty($vals) |
||
1131 | ? $this->driver->query($sql) |
||
1132 | : $this->driver->prepareExecute($sql, $vals); |
||
1133 | |||
1134 | $endTime = microtime(TRUE); |
||
1135 | $totalTime = number_format($endTime - $startTime, 5); |
||
1136 | |||
1137 | // Add this query to the list of executed queries |
||
1138 | $this->_appendQuery($vals, $sql, (int) $totalTime); |
||
1139 | |||
1140 | // Reset class state for next query |
||
1141 | if ($reset) |
||
1142 | { |
||
1143 | $this->resetQuery(); |
||
1144 | } |
||
1145 | |||
1146 | return $res; |
||
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * Convert the prepared statement into readable sql |
||
1151 | * |
||
1152 | * @param array $values |
||
1153 | * @param string $sql |
||
1154 | * @param int $totalTime |
||
1155 | * @return void |
||
1156 | */ |
||
1157 | protected function _appendQuery(array $values, string $sql, int $totalTime): void |
||
1158 | { |
||
1159 | $evals = \is_array($values) ? $values : []; |
||
1160 | $esql = str_replace('?', '%s', $sql); |
||
1161 | |||
1162 | // Quote string values |
||
1163 | foreach($evals as &$v) |
||
1164 | { |
||
1165 | $v = ( ! is_numeric($v)) |
||
1166 | ? htmlentities($this->driver->quote($v), ENT_NOQUOTES, 'utf-8') |
||
1167 | : $v; |
||
1168 | } |
||
1169 | |||
1170 | // Add the query onto the array of values to pass |
||
1171 | // as arguments to sprintf |
||
1172 | array_unshift($evals, $esql); |
||
1173 | |||
1174 | // Add the interpreted query to the list of executed queries |
||
1175 | $this->queries[] = [ |
||
1176 | 'time' => $totalTime, |
||
1177 | 'sql' => sprintf(...$evals) |
||
1178 | ]; |
||
1179 | |||
1180 | $this->queries['total_time'] += $totalTime; |
||
1181 | |||
1182 | // Set the last query to get rowcounts properly |
||
1183 | $this->driver->setLastQuery($sql); |
||
1184 | } |
||
1185 | |||
1186 | /** |
||
1187 | * Sub-method for generating sql strings |
||
1188 | * |
||
1189 | * @codeCoverageIgnore |
||
1190 | * @param string $type |
||
1191 | * @param string $table |
||
1192 | * @return string |
||
1193 | */ |
||
1194 | protected function _compileType(string $type='', string $table=''): string |
||
1195 | { |
||
1196 | $setArrayKeys = $this->state->getSetArrayKeys(); |
||
1197 | switch($type) |
||
1198 | { |
||
1199 | case 'insert': |
||
1200 | $paramCount = count($setArrayKeys); |
||
1201 | $params = array_fill(0, $paramCount, '?'); |
||
1202 | $sql = "INSERT INTO {$table} (" |
||
1203 | . implode(',', $setArrayKeys) |
||
1204 | . ")\nVALUES (".implode(',', $params).')'; |
||
1205 | break; |
||
1206 | |||
1207 | case 'update': |
||
1208 | $setString = $this->state->getSetString(); |
||
1209 | $sql = "UPDATE {$table}\nSET {$setString}"; |
||
1210 | break; |
||
1211 | |||
1212 | case 'delete': |
||
1213 | $sql = "DELETE FROM {$table}"; |
||
1214 | break; |
||
1215 | |||
1216 | // Get queries |
||
1217 | default: |
||
1218 | $fromString = $this->state->getFromString(); |
||
1219 | $selectString = $this->state->getSelectString(); |
||
1220 | |||
1221 | $sql = "SELECT * \nFROM {$fromString}"; |
||
1222 | |||
1223 | // Set the select string |
||
1224 | if ( ! empty($selectString)) |
||
1225 | { |
||
1226 | // Replace the star with the selected fields |
||
1227 | $sql = str_replace('*', $selectString, $sql); |
||
1228 | } |
||
1229 | break; |
||
1230 | } |
||
1231 | |||
1232 | return $sql; |
||
1233 | } |
||
1234 | |||
1235 | /** |
||
1236 | * String together the sql statements for sending to the db |
||
1237 | * |
||
1238 | * @param string $type |
||
1239 | * @param string $table |
||
1240 | * @return string |
||
1241 | */ |
||
1242 | protected function _compile(string $type='', string $table=''): string |
||
1243 | { |
||
1244 | // Get the base clause for the query |
||
1245 | $sql = $this->_compileType($type, $this->driver->quoteTable($table)); |
||
0 ignored issues
–
show
It seems like
$this->driver->quoteTable($table) targeting Query\Drivers\DriverInterface::quoteTable() can also be of type array ; however, Query\QueryBuilder::_compileType() does only seem to accept string , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
1246 | |||
1247 | $clauses = [ |
||
1248 | 'queryMap', |
||
1249 | 'groupString', |
||
1250 | 'orderString', |
||
1251 | 'havingMap', |
||
1252 | ]; |
||
1253 | |||
1254 | // Set each type of subclause |
||
1255 | foreach($clauses as $clause) |
||
1256 | { |
||
1257 | $func = 'get' . ucFirst($clause); |
||
1258 | $param = $this->state->$func(); |
||
1259 | if (\is_array($param)) |
||
1260 | { |
||
1261 | foreach($param as $q) |
||
1262 | { |
||
1263 | $sql .= $q['conjunction'] . $q['string']; |
||
1264 | } |
||
1265 | } |
||
1266 | else |
||
1267 | { |
||
1268 | $sql .= $param; |
||
1269 | } |
||
1270 | } |
||
1271 | |||
1272 | // Set the limit via the class variables |
||
1273 | $limit = $this->state->getLimit(); |
||
1274 | if (is_numeric($limit)) |
||
1275 | { |
||
1276 | $sql = $this->driver->getSql()->limit($sql, $limit, $this->state->getOffset()); |
||
0 ignored issues
–
show
It seems like
$this->state->getOffset() targeting Query\State::getOffset() can also be of type string ; however, Query\Drivers\SQLInterface::limit() does only seem to accept integer|boolean , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
1277 | } |
||
1278 | |||
1279 | // See if the query plan, rather than the |
||
1280 | // query data should be returned |
||
1281 | if ($this->explain === TRUE) |
||
1282 | { |
||
1283 | $sql = $this->driver->getSql()->explain($sql); |
||
1284 | } |
||
1285 | |||
1286 | return $sql; |
||
1287 | } |
||
1288 | } |
||
1289 |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.