Query   F
last analyzed

Complexity

Total Complexity 130

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 464
rs 1.5789
c 0
b 0
f 0
wmc 130

23 Methods

Rating   Name   Duplication   Size   Complexity  
A having() 0 5 2
B buildJoin() 0 17 5
A delete() 0 5 1
A colPrefix() 0 2 1
A group() 0 2 1
F buildQuery() 0 78 25
D buildHaving() 0 73 31
A setTable() 0 2 1
A start() 0 2 1
A insert() 0 6 1
A where() 0 5 2
D buildWhere() 0 75 31
C query() 0 24 8
A order() 0 10 4
A buildLimit() 0 8 2
A update() 0 6 1
A cols() 0 5 2
A join() 0 12 4
A limit() 0 2 1
A __construct() 0 5 2
A setDbOption() 0 2 1
A select() 0 6 2
A createTable() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Query, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Query class for mysql driver
5
 *
6
 * @author Alexey Krupskiy <[email protected]>
7
 * @link http://inji.ru/
8
 * @copyright 2015 Alexey Krupskiy
9
 * @license https://github.com/injitools/cms-Inji/blob/master/LICENSE
10
 */
11
12
namespace Inji\Db\Mysql;
13
14
class Query extends \Inji\InjiObject implements \Inji\Db\DriverQuery {
15
16
    public $curInstance = null;
17
    public $where = [];
18
    public $whereString = '';
19
    public $having = [];
20
    public $havingString = '';
21
    public $cols = [];
22
    public $order = null;
23
    public $join = [];
24
    public $group = [];
25
    public $limit = 0;
26
    public $start = 0;
27
    public $error = '';
28
    public $query = '';
29
    public $table = '';
30
    public $operation = '';
31
    public $indexes = [];
32
    public $params = [];
33
    public $distinct = false;
34
    public $dbOptions = [];
35
    public $colPrefix = '';
36
37
    /**
38
     * @param  $instance
39
     */
40
    public function __construct($instance = null) {
41
        if (!$instance) {
42
            $this->curInstance = \App::$cur->db->connection;
43
        } else {
44
            $this->curInstance = $instance;
45
        }
46
    }
47
48
    public function colPrefix($colPrefix) {
49
        $this->colPrefix = $colPrefix;
50
    }
51
52
    public function setDbOption($name, $value) {
53
        $this->dbOptions[$name] = $value;
54
    }
55
56
    public function setTable($tableName) {
57
        $this->table = $tableName;
58
    }
59
60
    public function insert($table, $data) {
61
        $this->operation = 'INSERT';
62
        $this->table = $table;
63
        $this->cols = $data;
64
        $this->query();
65
        return $this->curInstance->pdo->lastInsertId();
66
    }
67
68
    public function select($table = null) {
69
        $this->operation = 'SELECT';
70
        if ($table !== null) {
71
            $this->table = $table;
72
        }
73
        return $this->query();
74
    }
75
76
    public function update($table, $data) {
77
        $this->operation = 'UPDATE';
78
        $this->table = $table;
79
        $this->cols = $data;
80
        $result = $this->query();
81
        return $result->pdoResult->rowCount();
82
    }
83
84
    public function delete($table) {
85
        $this->operation = 'DELETE';
86
        $this->table = $table;
87
        $result = $this->query();
88
        return $result->pdoResult->rowCount();
89
    }
90
91
    public function createTable($table_name, $cols, $indexes = []) {
92
        $this->operation = 'CREATE TABLE';
93
        $this->table = $table_name;
94
        $this->cols = $cols;
95
        $this->indexes = $indexes;
96
        return $this->query();
97
    }
98
99
    public function cols($cols) {
100
        if (is_array($cols)) {
101
            $this->cols = array_merge($this->cols, array_values($cols));
102
        } else {
103
            $this->cols[] = $cols;
104
        }
105
    }
106
107
    public function join($table, $where = false, $type = 'LEFT', $alias = '') {
108
        if (is_array($table)) {
109
            foreach ($table as $item) {
110
                if (!is_array($item)) {
111
                    call_user_func_array(array($this, 'join'), $table);
112
                    break;
113
                } else {
114
                    $this->join($item);
115
                }
116
            }
117
        } else {
118
            $this->join[] = [$table, $where, $type, $alias];
119
        }
120
    }
121
122
    public function where($where = '', $value = '', $operation = false, $concatenation = 'AND') {
123
        if (!is_array($where)) {
124
            $this->where[] = [$where, $value, $operation, $concatenation];
125
        } else {
126
            $this->where[] = $where;
127
        }
128
    }
129
130
    public function having($where = '', $value = '', $operation = false, $concatenation = 'AND') {
131
        if (!is_array($where)) {
132
            $this->having[] = [$where, $value, $operation, $concatenation];
133
        } else {
134
            $this->having[] = $where;
135
        }
136
    }
137
138
    public function group($colname) {
139
        $this->group[] = $colname;
140
    }
141
142
    public function start($start) {
143
        $this->start = $start;
144
    }
145
146
    public function order($order, $type = 'ASC') {
147
        if (!is_array($order)) {
148
            $this->order[] = "{$this->colPrefix}{$order} {$type}";
149
        } else {
150
            foreach ($order as $item) {
151
                if (!is_array($item)) {
152
                    call_user_func_array(array($this, 'order'), $order);
153
                    break;
154
                } else {
155
                    $this->order($item);
156
                }
157
            }
158
        }
159
    }
160
161
    public function limit($limit = 0, $len = 0) {
162
        $this->limit = $limit;
163
    }
164
165
    public function buildLimit() {
166
        $start = intval($this->start);
167
        $len = intval($this->limit);
168
        $str = "LIMIT {$start}";
169
        if ($len !== 0) {
170
            $str .= ",{$len}";
171
        }
172
        return $str;
173
    }
174
175
    public function buildJoin($table, $where = false, $type = 'LEFT', $alias = '') {
176
        $join = '';
177
        if (is_array($table)) {
178
            $joins = func_get_args();
179
            foreach ($joins as $joinAr) {
180
                $join .= call_user_func_array([$this, 'buildJoin'], $joinAr);
181
            }
182
        } else {
183
            $join .= " {$type} JOIN {$this->curInstance->table_prefix}{$table}";
184
            if ($alias) {
185
                $join .= " AS `{$alias}`";
186
            }
187
            if ($where) {
188
                $join .= " ON {$where}";
189
            }
190
        }
191
        return $join;
192
    }
193
194
    /**
195
     * Build where string
196
     *
197
     * @param string|array $where
198
     * @param mixed $value
199
     * @param string $operation
200
     * @param string $concatenation
201
     */
202
    public function buildWhere($where = '', $value = '', $operation = '=', $concatenation = 'AND') {
203
        if (!is_array($where)) {
204
            if (empty($operation)) {
205
                $operation = '=';
206
            }
207
208
            if ($concatenation === false) {
209
                $concatenation = 'AND';
210
            } elseif ($concatenation === true) {
211
                $concatenation = '';
212
            }
213
214
            if ($this->whereString == '') {
215
                $this->whereString = ' WHERE ';
216
            }
217
218
            if (stristr($operation, 'IN') || stristr($operation, 'NOT IN')) {
219
                if (is_array($value)) {
220
                    $newValue = '';
221
                    foreach ($value as $item) {
222
                        if ($newValue) {
223
                            $newValue .= ',';
224
                        }
225
                        if (is_string($item)) {
226
                            $newValue .= '"' . $item . '"';
227
                        } else {
228
                            $newValue .= $item;
229
                        }
230
                    }
231
                    $value = '(' . $newValue . ')';
232
                } elseif (!preg_match('!\(!', $value) && !preg_match('![^0-9,\.\(\) ]!', $value)) {
233
                    $value = "({$value})";
234
                } elseif (preg_match('!\(!', $value) && preg_match('![^0-9,\.\(\) ]!', $value)) {
235
                    $value = "\"{$value}\"";
236
                }
237
            } elseif (!in_array($value, array('CURRENT_TIMESTAMP', 'NULL'))) {
238
                $this->params[] = $value;
239
                $value = "?";
240
            }
241
242
            if (substr($this->whereString, -1, 1) == '(' || substr($this->whereString, -2, 2) == 'E ') {
243
                $this->whereString .= " {$this->colPrefix}{$where} {$operation} {$value} ";
244
            } else {
245
                $this->whereString .= "{$concatenation} {$this->colPrefix}{$where} {$operation} {$value} ";
246
            }
247
        } else {
248
            $i = -1;
249
            while (isset($where[++$i])) {
250
                $item = $where[$i];
251
                if (isset($where[$i + 1]) && !isset($where[$i - 1]) && is_array($where[$i])) {
252
                    if ($this->whereString != null && substr($this->whereString, -1, 1) != '(' && $this->whereString != 'WHERE ') {
253
                        if (!isset($item[3])) {
254
                            $concatenation = 'AND';
255
                        } else {
256
                            $concatenation = $item[3];
257
                        }
258
259
                        $this->whereString .= "{$concatenation} ";
260
                    }
261
262
                    if ($this->whereString != '') {
263
                        $this->whereString .= '(';
264
                    } else {
265
                        $this->whereString = 'WHERE (';
266
                    }
267
                }
268
269
                if (!is_array($item)) {
270
                    call_user_func_array(array($this, 'buildWhere'), $where);
0 ignored issues
show
Bug introduced by
It seems like $where can also be of type string; however, parameter $param_arr of call_user_func_array() does only seem to accept array, 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

270
                    call_user_func_array(array($this, 'buildWhere'), /** @scrutinizer ignore-type */ $where);
Loading history...
271
                    break;
272
                } else {
273
                    $this->buildWhere($item);
274
                }
275
                if (!isset($where[$i + 1]) && isset($where[$i - 1])) {
276
                    $this->whereString .= ') ';
277
                }
278
            }
279
        }
280
    }
281
282
    /**
283
     * Build having string
284
     *
285
     * @param string|array $where
286
     * @param mixed $value
287
     * @param string $operation
288
     * @param string $concatenation
289
     */
290
    public function buildHaving($where = '', $value = '', $operation = '=', $concatenation = 'AND') {
291
        if (!is_array($where)) {
292
            if (empty($operation)) {
293
                $operation = '=';
294
            }
295
            if ($concatenation === false) {
296
                $concatenation = 'AND';
297
            } elseif ($concatenation === true) {
298
                $concatenation = '';
299
            }
300
301
            if ($this->havingString == '') {
302
                $this->havingString = ' HAVING ';
303
            }
304
305
            if (stristr($operation, 'IN') || stristr($operation, 'NOT IN')) {
306
                if (is_array($value)) {
307
                    $newValue = '';
308
                    foreach ($value as $item) {
309
                        if ($newValue) {
310
                            $newValue .= ',';
311
                        }
312
                        if (is_string($item)) {
313
                            $newValue .= '"' . $item . '"';
314
                        } else {
315
                            $newValue .= $item;
316
                        }
317
                    }
318
                    $value = '(' . $newValue . ')';
319
                } elseif (!preg_match('!\(!', $value) && !preg_match('![^0-9,\.\(\) ]!', $value)) {
320
                    $value = "({$value})";
321
                } elseif (preg_match('!\(!', $value) && preg_match('![^0-9,\.\(\) ]!', $value)) {
322
                    $value = "\"{$value}\"";
323
                }
324
            } elseif (!in_array($value, array('CURRENT_TIMESTAMP', 'NULL'))) {
325
                $this->params[] = $value;
326
                $value = "?";
327
            }
328
            if (substr($this->havingString, -1, 1) == '(' || substr($this->havingString, -2, 2) == 'G ') {
329
                $this->havingString .= " {$this->colPrefix}{$where} {$operation} {$value} ";
330
            } else {
331
                $this->havingString .= "{$concatenation} {$this->colPrefix}{$where} {$operation} {$value} ";
332
            }
333
        } else {
334
            $i = -1;
335
            while (isset($where[++$i])) {
336
                $item = $where[$i];
337
                if (isset($where[$i + 1]) && !isset($where[$i - 1]) && is_array($where[$i])) {
338
                    if ($this->havingString != null && substr($this->havingString, -1, 1) != '(' && $this->havingString != 'HAVING ') {
339
                        if (!isset($item[3])) {
340
                            $concatenation = 'AND';
341
                        } else {
342
                            $concatenation = $item[3];
343
                        }
344
345
                        $this->havingString .= "{$concatenation} ";
346
                    }
347
348
                    if ($this->havingString != '') {
349
                        $this->havingString .= '(';
350
                    } else {
351
                        $this->havingString = 'HAVING (';
352
                    }
353
                }
354
355
                if (!is_array($item)) {
356
                    call_user_func_array(array($this, 'buildHaving'), $where);
0 ignored issues
show
Bug introduced by
It seems like $where can also be of type string; however, parameter $param_arr of call_user_func_array() does only seem to accept array, 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

356
                    call_user_func_array(array($this, 'buildHaving'), /** @scrutinizer ignore-type */ $where);
Loading history...
357
                    break;
358
                } else {
359
                    $this->buildHaving($item);
360
                }
361
                if (!isset($where[$i + 1]) && isset($where[$i - 1])) {
362
                    $this->havingString .= ') ';
363
                }
364
            }
365
        }
366
    }
367
368
    public function buildQuery() {
369
        $query = $this->operation;
370
        $this->operation = strtoupper($this->operation);
371
372
        switch ($this->operation) {
373
            case 'SELECT':
374
                $query .= ' ' . ($this->distinct ? 'DISTINCT' : '');
375
                $query .= ' ' . (!$this->cols ? '*' : ((is_array($this->cols) ? implode(',', $this->cols) : $this->cols)));
376
            // no break
377
            case 'DELETE':
378
                $query .= ' FROM';
379
                break;
380
            case 'INSERT':
381
                $query .= ' INTO';
382
                break;
383
        }
384
        $query .= " `{$this->curInstance->db_name}`.`{$this->curInstance->table_prefix}{$this->table}`";
385
        if ($this->join) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->join of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
386
            $query .= $this->buildJoin($this->join);
387
        }
388
        switch ($this->operation) {
389
            case 'INSERT':
390
                $this->params = array_merge($this->params, array_values($this->cols));
391
                $colsStr = '';
392
                if ($this->cols) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cols of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
393
                    $colsStr = '`' . implode('`,`', array_keys($this->cols)) . '`';
394
                }
395
                $query .= ' (' . $colsStr . ') VALUES (' . rtrim(str_repeat('?,', count($this->cols)), ',') . ')';
396
                break;
397
            case 'CREATE TABLE':
398
                $query .= " (";
399
                foreach ($this->cols as $col_name => $param) {
400
                    if ($param == 'pk') {
401
                        $param = "int(11) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`{$col_name}`)";
402
                    }
403
                    $query .= " `{$col_name}` {$param},";
404
                }
405
                $query = rtrim($query, ',');
406
                if ($this->indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
407
                    $query .= ', ' . implode(',', $this->indexes);
408
                }
409
                $query .= ") ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci";
410
                break;
411
            case 'UPDATE':
412
                $updates = [];
413
                foreach ($this->cols as $key => $item) {
414
                    if (!in_array($item, array('CURRENT_TIMESTAMP'))) {
415
                        $this->params[] = $item;
416
                        $updates[] = "`{$key}` = ?";
417
                    } else {
418
                        $updates[] = "`{$key}` = {$item}";
419
                    }
420
                }
421
                $update = implode(',', $updates);
422
                $query .= " SET {$update}";
423
            // no break
424
            case 'SELECT':
425
            case 'DELETE':
426
                $this->buildWhere($this->where);
427
                if ($this->whereString) {
428
                    $query .= ' ' . $this->whereString;
429
                }
430
                break;
431
        }
432
        if ($this->group) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->group of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
433
            $query .= ' GROUP BY ' . implode(',', $this->group);
434
        }
435
        $this->buildHaving($this->having);
436
        if ($this->havingString) {
437
            $query .= ' ' . $this->havingString;
438
        }
439
        if ($this->order) {
440
            $query .= ' ORDER BY ' . implode(',', $this->order);
441
        }
442
        if ($this->limit || $this->start) {
443
            $query .= ' ' . $this->buildLimit();
444
        }
445
        return ['query' => $query, 'params' => $this->params];
446
    }
447
448
    /**
449
     * Execute query
450
     *
451
     * @param string|array $query
452
     * @return Result
453
     */
454
    public function query($query = []) {
455
        if (!$query) {
456
            $this->params = [];
457
            $query = $this->buildQuery();
458
        }
459
        if (is_string($query)) {
460
            $query = ['query' => $query, 'params' => $this->params];
461
        }
462
463
        $prepare = $this->curInstance->pdo->prepare($query['query']);
464
        $pos = 1;
465
        foreach ($query['params'] as $param) {
466
            $prepare->bindValue($pos++, $param, is_null($param) ? \PDO::PARAM_NULL : \PDO::PARAM_STR);
467
        }
468
        $key = \Inji\App::$cur->log->start('query: ' . $query['query']);
0 ignored issues
show
Bug Best Practice introduced by
The property log does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
The method start() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
        /** @scrutinizer ignore-call */ 
469
        $key = \Inji\App::$cur->log->start('query: ' . $query['query']);
Loading history...
Bug introduced by
The method start() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

468
        /** @scrutinizer ignore-call */ 
469
        $key = \Inji\App::$cur->log->start('query: ' . $query['query']);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
469
        $prepare->execute();
470
        \Inji\App::$cur->log->end($key);
0 ignored issues
show
Bug introduced by
The method end() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

470
        \Inji\App::$cur->log->/** @scrutinizer ignore-call */ 
471
                              end($key);
Loading history...
471
        $this->curInstance->lastQuery = $query;
472
        $result = new Result($prepare, $this);
473
        if ($this->curInstance->dbInstance && $this->curInstance->dbInstance->curQuery && $this->curInstance->dbInstance->curQuery === $this) {
474
            $this->curInstance->dbInstance->curQuery = null;
475
        }
476
477
        return $result;
478
    }
479
}