1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Anax\Database; |
4
|
|
|
|
5
|
|
|
use \Anax\Database\Exception\BuildException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Build SQL queries by method calling. |
9
|
|
|
*/ |
10
|
|
|
trait QueryBuilderTrait |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* @var $dialect current database dialect used |
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 |
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
|
19 |
|
public function getSQL() |
50
|
|
|
{ |
51
|
19 |
|
if ($this->sql) { |
52
|
8 |
|
return $this->sql; |
53
|
|
|
} |
54
|
11 |
|
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
|
11 |
|
protected function build() |
65
|
|
|
{ |
66
|
11 |
|
$sql = $this->start . "\n" |
67
|
11 |
|
. ($this->from ? $this->from . "\n" : null) |
68
|
11 |
|
. ($this->join ? $this->join : null) |
69
|
11 |
|
. ($this->set ? $this->set . "\n" : null) |
70
|
11 |
|
. ($this->where ? $this->where . "\n" : null) |
71
|
11 |
|
. ($this->groupby ? $this->groupby . "\n" : null) |
72
|
11 |
|
. ($this->orderby ? $this->orderby . "\n" : null) |
73
|
11 |
|
. ($this->limit ? $this->limit . "\n" : null) |
74
|
11 |
|
. ($this->offset ? $this->offset . "\n" : null) |
75
|
11 |
|
. ";"; |
76
|
|
|
|
77
|
11 |
|
return $sql; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
|
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Clear all previous sql-code. |
84
|
|
|
* |
85
|
|
|
* @return void |
86
|
|
|
*/ |
87
|
12 |
|
protected function clear() |
88
|
|
|
{ |
89
|
12 |
|
$this->sql = null; |
90
|
12 |
|
$this->start = null; |
91
|
12 |
|
$this->from = null; |
92
|
12 |
|
$this->join = null; |
93
|
12 |
|
$this->set = null; |
94
|
12 |
|
$this->where = null; |
95
|
12 |
|
$this->groupby = null; |
96
|
12 |
|
$this->orderby = null; |
97
|
12 |
|
$this->limit = null; |
98
|
12 |
|
$this->offset = null; |
99
|
12 |
|
} |
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
|
18 |
|
public function setTablePrefix($prefix) |
126
|
|
|
{ |
127
|
18 |
|
$this->prefix = $prefix; |
128
|
18 |
|
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
|
2 |
|
public function createTable($name, $columns) |
142
|
|
|
{ |
143
|
2 |
|
$cols = null; |
144
|
|
|
|
145
|
2 |
|
foreach ($columns as $col => $options) { |
146
|
2 |
|
$cols .= "\t" . $col . ' ' . implode(' ', $options) . ",\n"; |
147
|
2 |
|
} |
148
|
2 |
|
$cols = substr($cols, 0, -2); |
149
|
|
|
|
150
|
2 |
|
$this->sql = "CREATE TABLE " |
151
|
2 |
|
. $this->prefix |
152
|
2 |
|
. $name |
153
|
2 |
|
. "\n(\n" |
154
|
2 |
|
. $cols |
155
|
2 |
|
. "\n);\n"; |
156
|
|
|
|
157
|
2 |
|
if ($this->dialect == 'sqlite') { |
158
|
1 |
|
$this->sql = str_replace('auto_increment', '', $this->sql); |
159
|
1 |
|
} |
160
|
|
|
|
161
|
2 |
|
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
|
5 |
|
public function insert($table, $columns, $values = null) |
217
|
|
|
{ |
218
|
5 |
|
list($columns, $values) = $this->mapColumnsWithValues($columns, $values); |
219
|
|
|
|
220
|
5 |
|
if (count($columns) !== count($values)) { |
221
|
1 |
|
throw new BuildException("Columns does not match values, not equal items."); |
222
|
|
|
} |
223
|
|
|
|
224
|
4 |
|
$cols = null; |
225
|
4 |
|
$vals = null; |
226
|
|
|
|
227
|
4 |
|
$max = count($columns); |
228
|
4 |
View Code Duplication |
for ($i = 0; $i < $max; $i++) { |
|
|
|
|
229
|
4 |
|
$cols .= $columns[$i] . ', '; |
230
|
|
|
|
231
|
4 |
|
$val = $values[$i]; |
232
|
|
|
|
233
|
4 |
|
if ($val == '?') { |
234
|
1 |
|
$vals .= $val . ', '; |
235
|
1 |
|
} else { |
236
|
3 |
|
$vals .= (is_string($val) |
237
|
3 |
|
? "'$val'" |
238
|
3 |
|
: $val) |
239
|
3 |
|
. ', '; |
240
|
|
|
} |
241
|
4 |
|
} |
242
|
|
|
|
243
|
4 |
|
$cols = substr($cols, 0, -2); |
244
|
4 |
|
$vals = substr($vals, 0, -2); |
245
|
|
|
|
246
|
4 |
|
$this->sql = "INSERT INTO " |
247
|
4 |
|
. $this->prefix |
248
|
4 |
|
. $table |
249
|
4 |
|
. "\n\t(" |
250
|
4 |
|
. $cols |
251
|
4 |
|
. ")\n" |
252
|
4 |
|
. "\tVALUES\n\t(" |
253
|
4 |
|
. $vals |
254
|
4 |
|
. ");\n"; |
255
|
|
|
|
256
|
4 |
|
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 |
View Code Duplication |
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
|
1 |
|
} |
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; |
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 . ")"; |
324
|
1 |
|
} |
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
|
8 |
|
public function select($columns = '*') |
341
|
|
|
{ |
342
|
8 |
|
$this->clear(); |
343
|
8 |
|
$this->start = "SELECT\n\t$columns"; |
344
|
8 |
|
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
|
10 |
|
public function from($table) |
357
|
|
|
{ |
358
|
10 |
|
$this->from = "FROM " . $this->prefix . $table; |
359
|
10 |
|
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'); |
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'); |
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'); |
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; |
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
|
3 |
|
public function where($condition) |
444
|
|
|
{ |
445
|
3 |
|
$this->where = "WHERE\n\t(" . $condition . ")"; |
446
|
|
|
|
447
|
3 |
|
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
|
7 |
|
public function mapColumnsWithValues($columns, $values) |
541
|
|
|
{ |
542
|
|
|
// If $values is null, then use $columns to build it up |
543
|
7 |
|
if (is_null($values)) { |
544
|
|
|
|
545
|
3 |
|
if ($this->isAssoc($columns)) { |
546
|
|
|
|
547
|
|
|
// Incoming is associative array, split it up in two |
548
|
2 |
|
$values = array_values($columns); |
549
|
2 |
|
$columns = array_keys($columns); |
550
|
|
|
|
551
|
2 |
|
} else { |
552
|
|
|
|
553
|
|
|
// Create an array of '?' to match number of columns |
554
|
1 |
|
$max = count($columns); |
555
|
1 |
|
for ($i = 0; $i < $max; $i++) { |
556
|
1 |
|
$values[] = '?'; |
557
|
1 |
|
} |
558
|
|
|
} |
559
|
3 |
|
} |
560
|
|
|
|
561
|
7 |
|
return [$columns, $values]; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
|
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* Utility to check if array is associative array. |
568
|
|
|
* |
569
|
|
|
* http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential/4254008#4254008 |
570
|
|
|
* |
571
|
|
|
* @param array $array input array to check. |
572
|
|
|
* |
573
|
|
|
* @return boolean true if array is associative array with at least |
574
|
|
|
* one key, else false. |
575
|
|
|
* |
576
|
|
|
*/ |
577
|
3 |
|
private function isAssoc($array) |
578
|
|
|
{ |
579
|
3 |
|
return (bool) count(array_filter(array_keys($array), 'is_string')); |
580
|
|
|
} |
581
|
|
|
} |
582
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.