Issues (67)

src/DatabaseQueryBuilder/QueryBuilderTrait.php (14 issues)

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
The doc comment $dialect at position 0 could not be parsed: Unknown type name '$dialect' at position 0 in $dialect.
Loading history...
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
Documentation Bug introduced by
The doc comment $start at position 0 could not be parsed: Unknown type name '$start' at position 0 in $start.
Loading history...
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 ignore-type  annotation

148
        $cols = substr(/** @scrutinizer ignore-type */ $cols, 0, -2);
Loading history...
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 ignore-type  annotation

243
        $cols = substr(/** @scrutinizer ignore-type */ $cols, 0, -2);
Loading history...
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 ignore-type  annotation

298
        $cols = substr(/** @scrutinizer ignore-type */ $cols, 0, -2);
Loading history...
299
300
        $this->start = "UPDATE "
301
            . $this->prefix
302
            . $table;
303
        $this->set = "SET\n$cols";
304
305
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Anax\DatabaseQueryBuilder\QueryBuilderTrait which is incompatible with the documented return type void.
Loading history...
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 ignore-type  annotation

323
            $this->where = "WHERE\n\t(" . /** @scrutinizer ignore-type */ $where . ")";
Loading history...
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
Bug Best Practice introduced by
The expression return $this->createJoin...e, $condition, 'INNER') returns the type void which is incompatible with the documented return type Anax\DatabaseQueryBuilder\QueryBuilderTrait.
Loading history...
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 getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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 getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug Best Practice introduced by
The expression return $this->createJoin...ndition, 'RIGHT OUTER') returns the type void which is incompatible with the documented return type Anax\DatabaseQueryBuilder\QueryBuilderTrait.
Loading history...
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
Bug Best Practice introduced by
The expression return $this->createJoin...ondition, 'LEFT OUTER') returns the type void which is incompatible with the documented return type Anax\DatabaseQueryBuilder\QueryBuilderTrait.
Loading history...
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 getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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
Bug Best Practice introduced by
The expression return $this returns the type Anax\DatabaseQueryBuilder\QueryBuilderTrait which is incompatible with the documented return type void.
Loading history...
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