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 | 18 | public function getSQL() |
|||
50 | { |
||||
51 | 18 | if ($this->sql) { |
|||
52 | 8 | return $this->sql; |
|||
53 | } |
||||
54 | 12 | 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 | 12 | protected function build() |
|||
65 | { |
||||
66 | 12 | $sql = $this->start . "\n" |
|||
67 | 12 | . ($this->from ? $this->from . "\n" : null) |
|||
68 | 12 | . ($this->join ? $this->join : null) |
|||
69 | 12 | . ($this->set ? $this->set . "\n" : null) |
|||
70 | 12 | . ($this->where ? $this->where . "\n" : null) |
|||
71 | 12 | . ($this->groupby ? $this->groupby . "\n" : null) |
|||
72 | 12 | . ($this->orderby ? $this->orderby . "\n" : null) |
|||
73 | 12 | . ($this->limit ? $this->limit . "\n" : null) |
|||
74 | 12 | . ($this->offset ? $this->offset . "\n" : null) |
|||
75 | 12 | . ";"; |
|||
76 | |||||
77 | 12 | return $sql; |
|||
78 | } |
||||
79 | |||||
80 | |||||
81 | |||||
82 | /** |
||||
83 | * Clear all previous sql-code. |
||||
84 | * |
||||
85 | * @return void |
||||
86 | */ |
||||
87 | 13 | protected function clear() |
|||
88 | { |
||||
89 | 13 | $this->sql = null; |
|||
90 | 13 | $this->start = null; |
|||
91 | 13 | $this->from = null; |
|||
92 | 13 | $this->join = null; |
|||
93 | 13 | $this->set = null; |
|||
94 | 13 | $this->where = null; |
|||
95 | 13 | $this->groupby = null; |
|||
96 | 13 | $this->orderby = null; |
|||
97 | 13 | $this->limit = null; |
|||
98 | 13 | $this->offset = null; |
|||
99 | 13 | } |
|||
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 | 2 | public function setSQLDialect($dialect) |
|||
111 | { |
||||
112 | 2 | $this->dialect = $dialect; |
|||
113 | 2 | 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 | 20 | public function setTablePrefix($prefix) |
|||
126 | { |
||||
127 | 20 | $this->prefix = $prefix; |
|||
128 | 20 | 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 | 3 | public function createTable($name, $columns) |
|||
142 | { |
||||
143 | 3 | $cols = null; |
|||
144 | |||||
145 | 3 | foreach ($columns as $col => $options) { |
|||
146 | 3 | $cols .= "\t" . $col . ' ' . implode(' ', $options) . ",\n"; |
|||
147 | } |
||||
148 | 3 | $cols = substr($cols, 0, -2); |
|||
149 | |||||
150 | 3 | $this->sql = "CREATE TABLE " |
|||
151 | 3 | . $this->prefix |
|||
152 | 3 | . $name |
|||
153 | 3 | . "\n(\n" |
|||
154 | 3 | . $cols |
|||
155 | 3 | . "\n);\n"; |
|||
156 | |||||
157 | 3 | if ($this->dialect == 'sqlite') { |
|||
158 | 2 | $this->sql = str_replace('auto_increment', '', $this->sql); |
|||
159 | } |
||||
160 | |||||
161 | 3 | 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 | 1 | public function dropTable($name) |
|||
174 | { |
||||
175 | 1 | $this->sql = "DROP TABLE " |
|||
176 | 1 | . $this->prefix |
|||
177 | 1 | . $name |
|||
178 | 1 | . ";\n"; |
|||
179 | |||||
180 | 1 | 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 | 1 | public function dropTableIfExists($name) |
|||
193 | { |
||||
194 | 1 | $this->sql = "DROP TABLE IF EXISTS " |
|||
195 | 1 | . $this->prefix |
|||
196 | 1 | . $name |
|||
197 | 1 | . ";\n"; |
|||
198 | |||||
199 | 1 | 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 | 6 | public function insert($table, $columns, $values = null) |
|||
217 | { |
||||
218 | 6 | list($columns, $values) = $this->mapColumnsWithValues($columns, $values); |
|||
219 | |||||
220 | 6 | if (count($columns) !== count($values)) { |
|||
221 | 1 | throw new BuildException("Columns does not match values, not equal items."); |
|||
222 | } |
||||
223 | |||||
224 | 5 | $cols = null; |
|||
225 | 5 | $vals = null; |
|||
226 | |||||
227 | 5 | $max = count($columns); |
|||
228 | 5 | for ($i = 0; $i < $max; $i++) { |
|||
229 | 5 | $cols .= $columns[$i] . ', '; |
|||
230 | |||||
231 | 5 | $val = $values[$i]; |
|||
232 | |||||
233 | 5 | if ($val == '?') { |
|||
234 | 1 | $vals .= $val . ', '; |
|||
235 | } else { |
||||
236 | 4 | $vals .= (is_string($val) |
|||
237 | 4 | ? "'$val'" |
|||
238 | 4 | : $val) |
|||
239 | 4 | . ', '; |
|||
240 | } |
||||
241 | } |
||||
242 | |||||
243 | 5 | $cols = substr($cols, 0, -2); |
|||
244 | 5 | $vals = substr($vals, 0, -2); |
|||
245 | |||||
246 | 5 | $this->sql = "INSERT INTO " |
|||
247 | 5 | . $this->prefix |
|||
248 | 5 | . $table |
|||
249 | 5 | . "\n\t(" |
|||
250 | 5 | . $cols |
|||
251 | 5 | . ")\n" |
|||
252 | 5 | . "\tVALUES\n\t(" |
|||
253 | 5 | . $vals |
|||
254 | 5 | . ");\n"; |
|||
255 | |||||
256 | 5 | 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 | 2 | public function update($table, $columns, $values = null) |
|||
273 | { |
||||
274 | 2 | $this->clear(); |
|||
275 | 2 | list($columns, $values) = $this->mapColumnsWithValues($columns, $values); |
|||
276 | |||||
277 | 2 | if (count($columns) != count($values)) { |
|||
278 | 1 | throw new BuildException("Columns does not match values, not equal items."); |
|||
279 | } |
||||
280 | |||||
281 | 1 | $cols = null; |
|||
282 | 1 | $max = count($columns); |
|||
283 | |||||
284 | 1 | for ($i = 0; $i < $max; $i++) { |
|||
285 | 1 | $cols .= "\t" . $columns[$i] . ' = '; |
|||
286 | |||||
287 | 1 | $val = $values[$i]; |
|||
288 | 1 | if ($val == '?') { |
|||
289 | $cols .= $val . ",\n"; |
||||
290 | } else { |
||||
291 | 1 | $cols .= (is_string($val) |
|||
292 | 1 | ? "'$val'" |
|||
293 | 1 | : $val) |
|||
294 | 1 | . ",\n"; |
|||
295 | } |
||||
296 | } |
||||
297 | |||||
298 | 1 | $cols = substr($cols, 0, -2); |
|||
299 | |||||
300 | 1 | $this->start = "UPDATE " |
|||
301 | 1 | . $this->prefix |
|||
302 | 1 | . $table; |
|||
303 | 1 | $this->set = "SET\n$cols"; |
|||
304 | |||||
305 | 1 | 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 | 2 | public function deleteFrom($table, $where = null) |
|||
319 | { |
||||
320 | 2 | $this->clear(); |
|||
321 | |||||
322 | 2 | if (isset($where)) { |
|||
323 | 1 | $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 | 2 | $this->start = "DELETE"; |
|||
327 | 2 | $this->from($table); |
|||
328 | 2 | 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 | 9 | public function select($columns = '*') |
|||
341 | { |
||||
342 | 9 | $this->clear(); |
|||
343 | 9 | $this->start = "SELECT\n\t$columns"; |
|||
344 | 9 | 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 | 11 | public function from($table) |
|||
357 | { |
||||
358 | 11 | $this->from = "FROM " . $this->prefix . $table; |
|||
359 | 11 | 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 | 1 | public function join($table, $condition) |
|||
373 | { |
||||
374 | |||||
375 | 1 | 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 | 1 | public function rightJoin($table, $condition) |
|||
391 | { |
||||
392 | 1 | if ($this->dialect == 'sqlite') { |
|||
393 | throw new BuildException("SQLite does not support RIGHT JOIN"); |
||||
394 | } |
||||
395 | |||||
396 | 1 | 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 | 1 | public function leftJoin($table, $condition) |
|||
410 | { |
||||
411 | 1 | 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 | 3 | private function createJoin($table, $condition, $type) |
|||
426 | { |
||||
427 | 3 | $this->join .= $type |
|||
428 | 3 | . " JOIN " . $this->prefix . $table |
|||
429 | 3 | . "\n\tON " . $condition . "\n"; |
|||
430 | |||||
431 | 3 | 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 | 4 | public function where($condition) |
|||
444 | { |
||||
445 | 4 | $this->where = "WHERE\n\t(" . $condition . ")"; |
|||
446 | |||||
447 | 4 | 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 | 1 | public function andWhere($condition) |
|||
460 | { |
||||
461 | 1 | $this->where .= "\n\tAND (" . $condition . ")"; |
|||
462 | |||||
463 | 1 | 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 | 1 | public function groupBy($condition) |
|||
476 | { |
||||
477 | 1 | $this->groupby = "GROUP BY " . $condition; |
|||
478 | |||||
479 | 1 | 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 | 1 | public function orderBy($condition) |
|||
492 | { |
||||
493 | 1 | $this->orderby = "ORDER BY " . $condition; |
|||
494 | |||||
495 | 1 | 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 | 1 | public function limit($condition) |
|||
508 | { |
||||
509 | 1 | $this->limit = "LIMIT \n\t" . intval($condition); |
|||
510 | |||||
511 | 1 | 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 | 1 | public function offset($condition) |
|||
524 | { |
||||
525 | 1 | $this->offset = "OFFSET \n\t" . intval($condition); |
|||
526 | |||||
527 | 1 | 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 | 8 | public function mapColumnsWithValues($columns, $values) |
|||
541 | { |
||||
542 | // If $values is null, then use $columns to build it up |
||||
543 | 8 | if (is_null($values)) { |
|||
544 | 4 | if ($this->isAssoc($columns)) { |
|||
545 | // Incoming is associative array, split it up in two |
||||
546 | 3 | $values = array_values($columns); |
|||
547 | 3 | $columns = array_keys($columns); |
|||
548 | } else { |
||||
549 | // Create an array of '?' to match number of columns |
||||
550 | 1 | $max = count($columns); |
|||
551 | 1 | for ($i = 0; $i < $max; $i++) { |
|||
552 | 1 | $values[] = '?'; |
|||
553 | } |
||||
554 | } |
||||
555 | } |
||||
556 | |||||
557 | 8 | 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 | 4 | private function isAssoc($array) |
|||
574 | { |
||||
575 | 4 | return (bool) count(array_filter(array_keys($array), 'is_string')); |
|||
576 | } |
||||
577 | } |
||||
578 |