1 | <?php |
||||
2 | |||||
3 | namespace Anax\DatabaseQueryBuilder; |
||||
4 | |||||
5 | use Anax\DatabaseQueryBuilder\Exception\BuildException; |
||||
6 | |||||
7 | /** |
||||
8 | * Trait to implement building SQL queries by method calling. |
||||
9 | */ |
||||
10 | trait QueryBuilderTrait |
||||
11 | { |
||||
12 | /** |
||||
13 | * @var $dialect current database dialect used |
||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||
14 | * @var $sql the query built |
||||
15 | * @var $prefix prefix to attach to all table names |
||||
16 | */ |
||||
17 | private $dialect; |
||||
18 | private $sql; |
||||
19 | private $prefix; |
||||
20 | |||||
21 | /** |
||||
22 | * @var $start first line of the sql query |
||||
0 ignored issues
–
show
|
|||||
23 | * @var $from from part |
||||
24 | * @var $join join part |
||||
25 | * @var $set set part for a update |
||||
26 | * @var $where where part |
||||
27 | * @var $groupby group part |
||||
28 | * @var $orderby order part |
||||
29 | * @var $limit limit part |
||||
30 | * @var $offset offset part |
||||
31 | */ |
||||
32 | private $start; |
||||
33 | private $from; |
||||
34 | private $join; |
||||
35 | private $set; |
||||
36 | private $where; |
||||
37 | private $groupby; |
||||
38 | private $orderby; |
||||
39 | private $limit; |
||||
40 | private $offset; |
||||
41 | |||||
42 | |||||
43 | |||||
44 | /** |
||||
45 | * Get SQL. |
||||
46 | * |
||||
47 | * @return string with the built sql-query |
||||
48 | */ |
||||
49 | public function getSQL() |
||||
50 | { |
||||
51 | if ($this->sql) { |
||||
52 | return $this->sql; |
||||
53 | } |
||||
54 | return $this->build(); |
||||
55 | } |
||||
56 | |||||
57 | |||||
58 | |||||
59 | /** |
||||
60 | * Build the SQL query from its parts. |
||||
61 | * |
||||
62 | * @return string as SQL query |
||||
63 | */ |
||||
64 | protected function build() |
||||
65 | { |
||||
66 | $sql = $this->start . "\n" |
||||
67 | . ($this->from ? $this->from . "\n" : null) |
||||
68 | . ($this->join ? $this->join : null) |
||||
69 | . ($this->set ? $this->set . "\n" : null) |
||||
70 | . ($this->where ? $this->where . "\n" : null) |
||||
71 | . ($this->groupby ? $this->groupby . "\n" : null) |
||||
72 | . ($this->orderby ? $this->orderby . "\n" : null) |
||||
73 | . ($this->limit ? $this->limit . "\n" : null) |
||||
74 | . ($this->offset ? $this->offset . "\n" : null) |
||||
75 | . ";"; |
||||
76 | |||||
77 | return $sql; |
||||
78 | } |
||||
79 | |||||
80 | |||||
81 | |||||
82 | /** |
||||
83 | * Clear all previous sql-code. |
||||
84 | * |
||||
85 | * @return void |
||||
86 | */ |
||||
87 | protected function clear() |
||||
88 | { |
||||
89 | $this->sql = null; |
||||
90 | $this->start = null; |
||||
91 | $this->from = null; |
||||
92 | $this->join = null; |
||||
93 | $this->set = null; |
||||
94 | $this->where = null; |
||||
95 | $this->groupby = null; |
||||
96 | $this->orderby = null; |
||||
97 | $this->limit = null; |
||||
98 | $this->offset = null; |
||||
99 | } |
||||
100 | |||||
101 | |||||
102 | |||||
103 | /** |
||||
104 | * Set database type/dialect to consider when generating SQL. |
||||
105 | * |
||||
106 | * @param string $dialect representing database type. |
||||
107 | * |
||||
108 | * @return self |
||||
109 | */ |
||||
110 | public function setSQLDialect($dialect) |
||||
111 | { |
||||
112 | $this->dialect = $dialect; |
||||
113 | return $this; |
||||
114 | } |
||||
115 | |||||
116 | |||||
117 | |||||
118 | /** |
||||
119 | * Set a table prefix. |
||||
120 | * |
||||
121 | * @param string $prefix to use in front of all tables. |
||||
122 | * |
||||
123 | * @return self |
||||
124 | */ |
||||
125 | public function setTablePrefix($prefix) |
||||
126 | { |
||||
127 | $this->prefix = $prefix; |
||||
128 | return $this; |
||||
129 | } |
||||
130 | |||||
131 | |||||
132 | |||||
133 | /** |
||||
134 | * Create a table. |
||||
135 | * |
||||
136 | * @param string $name the table name. |
||||
137 | * @param array $columns the columns in the table. |
||||
138 | * |
||||
139 | * @return $this |
||||
140 | */ |
||||
141 | public function createTable($name, $columns) |
||||
142 | { |
||||
143 | $cols = null; |
||||
144 | |||||
145 | foreach ($columns as $col => $options) { |
||||
146 | $cols .= "\t" . $col . ' ' . implode(' ', $options) . ",\n"; |
||||
147 | } |
||||
148 | $cols = substr($cols, 0, -2); |
||||
0 ignored issues
–
show
It seems like
$cols can also be of type null ; however, parameter $string of substr() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
149 | |||||
150 | $this->sql = "CREATE TABLE " |
||||
151 | . $this->prefix |
||||
152 | . $name |
||||
153 | . "\n(\n" |
||||
154 | . $cols |
||||
155 | . "\n);\n"; |
||||
156 | |||||
157 | if ($this->dialect == 'sqlite') { |
||||
158 | $this->sql = str_replace('auto_increment', '', $this->sql); |
||||
159 | } |
||||
160 | |||||
161 | return $this; |
||||
162 | } |
||||
163 | |||||
164 | |||||
165 | |||||
166 | /** |
||||
167 | * Drop a table. |
||||
168 | * |
||||
169 | * @param string $name the table name. |
||||
170 | * |
||||
171 | * @return $this |
||||
172 | */ |
||||
173 | public function dropTable($name) |
||||
174 | { |
||||
175 | $this->sql = "DROP TABLE " |
||||
176 | . $this->prefix |
||||
177 | . $name |
||||
178 | . ";\n"; |
||||
179 | |||||
180 | return $this; |
||||
181 | } |
||||
182 | |||||
183 | |||||
184 | |||||
185 | /** |
||||
186 | * Drop a table if it exists. |
||||
187 | * |
||||
188 | * @param string $name the table name. |
||||
189 | * |
||||
190 | * @return $this |
||||
191 | */ |
||||
192 | public function dropTableIfExists($name) |
||||
193 | { |
||||
194 | $this->sql = "DROP TABLE IF EXISTS " |
||||
195 | . $this->prefix |
||||
196 | . $name |
||||
197 | . ";\n"; |
||||
198 | |||||
199 | return $this; |
||||
200 | } |
||||
201 | |||||
202 | |||||
203 | |||||
204 | /** |
||||
205 | * Build a insert-query. |
||||
206 | * |
||||
207 | * @param string $table the table name. |
||||
208 | * @param array $columns to insert och key=>value with columns and values. |
||||
209 | * @param array $values to insert or empty if $columns has both |
||||
210 | * columns and values. |
||||
211 | * |
||||
212 | * @throws \Anax\Database\BuildException |
||||
213 | * |
||||
214 | * @return self for chaining |
||||
215 | */ |
||||
216 | public function insert($table, $columns, $values = null) |
||||
217 | { |
||||
218 | list($columns, $values) = $this->mapColumnsWithValues($columns, $values); |
||||
219 | |||||
220 | if (count($columns) !== count($values)) { |
||||
221 | throw new BuildException("Columns does not match values, not equal items."); |
||||
222 | } |
||||
223 | |||||
224 | $cols = null; |
||||
225 | $vals = null; |
||||
226 | |||||
227 | $max = count($columns); |
||||
228 | for ($i = 0; $i < $max; $i++) { |
||||
229 | $cols .= $columns[$i] . ', '; |
||||
230 | |||||
231 | $val = $values[$i]; |
||||
232 | |||||
233 | if ($val == '?') { |
||||
234 | $vals .= $val . ', '; |
||||
235 | } else { |
||||
236 | $vals .= (is_string($val) |
||||
237 | ? "'$val'" |
||||
238 | : $val) |
||||
239 | . ', '; |
||||
240 | } |
||||
241 | } |
||||
242 | |||||
243 | $cols = substr($cols, 0, -2); |
||||
0 ignored issues
–
show
It seems like
$cols can also be of type null ; however, parameter $string of substr() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
244 | $vals = substr($vals, 0, -2); |
||||
245 | |||||
246 | $this->sql = "INSERT INTO " |
||||
247 | . $this->prefix |
||||
248 | . $table |
||||
249 | . "\n\t(" |
||||
250 | . $cols |
||||
251 | . ")\n" |
||||
252 | . "\tVALUES\n\t(" |
||||
253 | . $vals |
||||
254 | . ");\n"; |
||||
255 | |||||
256 | return $this; |
||||
257 | } |
||||
258 | |||||
259 | |||||
260 | |||||
261 | /** |
||||
262 | * Build an update-query. |
||||
263 | * |
||||
264 | * @param string $table the table name. |
||||
265 | * @param array $columns to update or key=>value with columns and values. |
||||
266 | * @param array $values to update or empty if $columns has bot columns and values. |
||||
267 | * |
||||
268 | * @throws \Anax\Database\BuildException |
||||
269 | * |
||||
270 | * @return void |
||||
271 | */ |
||||
272 | public function update($table, $columns, $values = null) |
||||
273 | { |
||||
274 | $this->clear(); |
||||
275 | list($columns, $values) = $this->mapColumnsWithValues($columns, $values); |
||||
276 | |||||
277 | if (count($columns) != count($values)) { |
||||
278 | throw new BuildException("Columns does not match values, not equal items."); |
||||
279 | } |
||||
280 | |||||
281 | $cols = null; |
||||
282 | $max = count($columns); |
||||
283 | |||||
284 | for ($i = 0; $i < $max; $i++) { |
||||
285 | $cols .= "\t" . $columns[$i] . ' = '; |
||||
286 | |||||
287 | $val = $values[$i]; |
||||
288 | if ($val == '?') { |
||||
289 | $cols .= $val . ",\n"; |
||||
290 | } else { |
||||
291 | $cols .= (is_string($val) |
||||
292 | ? "'$val'" |
||||
293 | : $val) |
||||
294 | . ",\n"; |
||||
295 | } |
||||
296 | } |
||||
297 | |||||
298 | $cols = substr($cols, 0, -2); |
||||
0 ignored issues
–
show
It seems like
$cols can also be of type null ; however, parameter $string of substr() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
299 | |||||
300 | $this->start = "UPDATE " |
||||
301 | . $this->prefix |
||||
302 | . $table; |
||||
303 | $this->set = "SET\n$cols"; |
||||
304 | |||||
305 | return $this; |
||||
0 ignored issues
–
show
|
|||||
306 | } |
||||
307 | |||||
308 | |||||
309 | |||||
310 | /** |
||||
311 | * Build a delete-query. |
||||
312 | * |
||||
313 | * @param string $table the table name. |
||||
314 | * @param array $where limit which rows are updated. |
||||
315 | * |
||||
316 | * @return self |
||||
317 | */ |
||||
318 | public function deleteFrom($table, $where = null) |
||||
319 | { |
||||
320 | $this->clear(); |
||||
321 | |||||
322 | if (isset($where)) { |
||||
323 | $this->where = "WHERE\n\t(" . $where . ")"; |
||||
0 ignored issues
–
show
Are you sure
$where of type array can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
324 | } |
||||
325 | |||||
326 | $this->start = "DELETE"; |
||||
327 | $this->from($table); |
||||
328 | return $this; |
||||
329 | } |
||||
330 | |||||
331 | |||||
332 | |||||
333 | /** |
||||
334 | * Build a select-query. |
||||
335 | * |
||||
336 | * @param string $columns which columns to select. |
||||
337 | * |
||||
338 | * @return $this |
||||
339 | */ |
||||
340 | public function select($columns = '*') |
||||
341 | { |
||||
342 | $this->clear(); |
||||
343 | $this->start = "SELECT\n\t$columns"; |
||||
344 | return $this; |
||||
345 | } |
||||
346 | |||||
347 | |||||
348 | |||||
349 | /** |
||||
350 | * Build the from part. |
||||
351 | * |
||||
352 | * @param string $table name of table. |
||||
353 | * |
||||
354 | * @return $this |
||||
355 | */ |
||||
356 | public function from($table) |
||||
357 | { |
||||
358 | $this->from = "FROM " . $this->prefix . $table; |
||||
359 | return $this; |
||||
360 | } |
||||
361 | |||||
362 | |||||
363 | |||||
364 | /** |
||||
365 | * Build the inner join part. |
||||
366 | * |
||||
367 | * @param string $table name of table. |
||||
368 | * @param string $condition to join. |
||||
369 | * |
||||
370 | * @return $this |
||||
371 | */ |
||||
372 | public function join($table, $condition) |
||||
373 | { |
||||
374 | |||||
375 | return $this->createJoin($table, $condition, 'INNER'); |
||||
0 ignored issues
–
show
Are you sure the usage of
$this->createJoin($table, $condition, 'INNER') targeting Anax\DatabaseQueryBuilde...lderTrait::createJoin() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
376 | } |
||||
377 | |||||
378 | |||||
379 | |||||
380 | /** |
||||
381 | * Build the right join part. |
||||
382 | * |
||||
383 | * @param string $table name of table. |
||||
384 | * @param string $condition to join. |
||||
385 | * |
||||
386 | * @throws \Anax\Database\BuildException when dialect does not support. |
||||
387 | * |
||||
388 | * @return $this |
||||
389 | */ |
||||
390 | public function rightJoin($table, $condition) |
||||
391 | { |
||||
392 | if ($this->dialect == 'sqlite') { |
||||
393 | throw new BuildException("SQLite does not support RIGHT JOIN"); |
||||
394 | } |
||||
395 | |||||
396 | return $this->createJoin($table, $condition, 'RIGHT OUTER'); |
||||
0 ignored issues
–
show
Are you sure the usage of
$this->createJoin($table...ndition, 'RIGHT OUTER') targeting Anax\DatabaseQueryBuilde...lderTrait::createJoin() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
397 | } |
||||
398 | |||||
399 | |||||
400 | |||||
401 | /** |
||||
402 | * Build the left join part. |
||||
403 | * |
||||
404 | * @param string $table name of table. |
||||
405 | * @param string $condition to join. |
||||
406 | * |
||||
407 | * @return $this |
||||
408 | */ |
||||
409 | public function leftJoin($table, $condition) |
||||
410 | { |
||||
411 | return $this->createJoin($table, $condition, 'LEFT OUTER'); |
||||
0 ignored issues
–
show
Are you sure the usage of
$this->createJoin($table...ondition, 'LEFT OUTER') targeting Anax\DatabaseQueryBuilde...lderTrait::createJoin() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
412 | } |
||||
413 | |||||
414 | |||||
415 | |||||
416 | /** |
||||
417 | * Create a inner or outer join. |
||||
418 | * |
||||
419 | * @param string $table name of table. |
||||
420 | * @param string $condition to join. |
||||
421 | * @param string $type what type of join to create. |
||||
422 | * |
||||
423 | * @return void |
||||
424 | */ |
||||
425 | private function createJoin($table, $condition, $type) |
||||
426 | { |
||||
427 | $this->join .= $type |
||||
428 | . " JOIN " . $this->prefix . $table |
||||
429 | . "\n\tON " . $condition . "\n"; |
||||
430 | |||||
431 | return $this; |
||||
0 ignored issues
–
show
|
|||||
432 | } |
||||
433 | |||||
434 | |||||
435 | |||||
436 | /** |
||||
437 | * Build the where part. |
||||
438 | * |
||||
439 | * @param string $condition for building the where part of the query. |
||||
440 | * |
||||
441 | * @return $this |
||||
442 | */ |
||||
443 | public function where($condition) |
||||
444 | { |
||||
445 | $this->where = "WHERE\n\t(" . $condition . ")"; |
||||
446 | |||||
447 | return $this; |
||||
448 | } |
||||
449 | |||||
450 | |||||
451 | |||||
452 | /** |
||||
453 | * Build the where part with conditions. |
||||
454 | * |
||||
455 | * @param string $condition for building the where part of the query. |
||||
456 | * |
||||
457 | * @return $this |
||||
458 | */ |
||||
459 | public function andWhere($condition) |
||||
460 | { |
||||
461 | $this->where .= "\n\tAND (" . $condition . ")"; |
||||
462 | |||||
463 | return $this; |
||||
464 | } |
||||
465 | |||||
466 | |||||
467 | |||||
468 | /** |
||||
469 | * Build the group by part. |
||||
470 | * |
||||
471 | * @param string $condition for building the group by part of the query. |
||||
472 | * |
||||
473 | * @return $this |
||||
474 | */ |
||||
475 | public function groupBy($condition) |
||||
476 | { |
||||
477 | $this->groupby = "GROUP BY " . $condition; |
||||
478 | |||||
479 | return $this; |
||||
480 | } |
||||
481 | |||||
482 | |||||
483 | |||||
484 | /** |
||||
485 | * Build the order by part. |
||||
486 | * |
||||
487 | * @param string $condition for building the where part of the query. |
||||
488 | * |
||||
489 | * @return $this |
||||
490 | */ |
||||
491 | public function orderBy($condition) |
||||
492 | { |
||||
493 | $this->orderby = "ORDER BY " . $condition; |
||||
494 | |||||
495 | return $this; |
||||
496 | } |
||||
497 | |||||
498 | |||||
499 | |||||
500 | /** |
||||
501 | * Build the LIMIT by part. |
||||
502 | * |
||||
503 | * @param string $condition for building the LIMIT part of the query. |
||||
504 | * |
||||
505 | * @return $this |
||||
506 | */ |
||||
507 | public function limit($condition) |
||||
508 | { |
||||
509 | $this->limit = "LIMIT \n\t" . intval($condition); |
||||
510 | |||||
511 | return $this; |
||||
512 | } |
||||
513 | |||||
514 | |||||
515 | |||||
516 | /** |
||||
517 | * Build the OFFSET by part. |
||||
518 | * |
||||
519 | * @param string $condition for building the OFFSET part of the query. |
||||
520 | * |
||||
521 | * @return $this |
||||
522 | */ |
||||
523 | public function offset($condition) |
||||
524 | { |
||||
525 | $this->offset = "OFFSET \n\t" . intval($condition); |
||||
526 | |||||
527 | return $this; |
||||
528 | } |
||||
529 | |||||
530 | |||||
531 | |||||
532 | /** |
||||
533 | * Create a proper column value arrays from incoming $columns and $values. |
||||
534 | * |
||||
535 | * @param array $columns |
||||
536 | * @param array|null $values |
||||
537 | * |
||||
538 | * @return array that can be parsed with list($columns, $values) |
||||
539 | */ |
||||
540 | public function mapColumnsWithValues($columns, $values) |
||||
541 | { |
||||
542 | // If $values is null, then use $columns to build it up |
||||
543 | if (is_null($values)) { |
||||
544 | if ($this->isAssoc($columns)) { |
||||
545 | // Incoming is associative array, split it up in two |
||||
546 | $values = array_values($columns); |
||||
547 | $columns = array_keys($columns); |
||||
548 | } else { |
||||
549 | // Create an array of '?' to match number of columns |
||||
550 | $max = count($columns); |
||||
551 | for ($i = 0; $i < $max; $i++) { |
||||
552 | $values[] = '?'; |
||||
553 | } |
||||
554 | } |
||||
555 | } |
||||
556 | |||||
557 | return [$columns, $values]; |
||||
558 | } |
||||
559 | |||||
560 | |||||
561 | |||||
562 | /** |
||||
563 | * Utility to check if array is associative array. |
||||
564 | * |
||||
565 | * http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential/4254008#4254008 |
||||
566 | * |
||||
567 | * @param array $array input array to check. |
||||
568 | * |
||||
569 | * @return boolean true if array is associative array with at least |
||||
570 | * one key, else false. |
||||
571 | * |
||||
572 | */ |
||||
573 | private function isAssoc($array) |
||||
574 | { |
||||
575 | return (bool) count(array_filter(array_keys($array), 'is_string')); |
||||
576 | } |
||||
577 | } |
||||
578 |