Passed
Push — v5 ( e348bf...8a8f01 )
by Alexey
06:35
created

Query   F

Complexity

Total Complexity 126

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 453
rs 1.5789
c 0
b 0
f 0
wmc 126

20 Methods

Rating   Name   Duplication   Size   Complexity  
A having() 0 5 2
B buildJoin() 0 17 5
A delete() 0 5 1
F buildQuery() 0 78 24
A group() 0 2 1
D buildHaving() 0 73 31
A setTable() 0 2 1
A insert() 0 6 1
A where() 0 5 2
D buildWhere() 0 75 31
C query() 0 26 8
A order() 0 12 4
A update() 0 6 1
A cols() 0 5 2
A join() 0 12 4
A limit() 0 6 2
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 = '';
26
    public $error = '';
27
    public $query = '';
28
    public $table = '';
29
    public $operation = '';
30
    public $indexes = [];
31
    public $params = [];
32
    public $distinct = false;
33
    public $dbOptions = [];
34
35
    /**
36
     * @param  $instance
37
     */
38
    public function __construct($instance = null) {
39
        if (!$instance) {
40
            $this->curInstance = \App::$cur->db->connection;
41
        } else {
42
            $this->curInstance = $instance;
43
        }
44
    }
45
46
    public function setDbOption($name, $value) {
47
        $this->dbOptions[$name] = $value;
48
    }
49
50
    public function setTable($tableName) {
51
        $this->table = $tableName;
52
    }
53
54
    public function insert($table, $data) {
55
        $this->operation = 'INSERT';
56
        $this->table = $table;
57
        $this->cols = $data;
58
        $this->query();
59
        return $this->curInstance->pdo->lastInsertId();
60
    }
61
62
    public function select($table = null) {
63
        $this->operation = 'SELECT';
64
        if ($table !== null) {
65
            $this->table = $table;
66
        }
67
        return $this->query();
68
    }
69
70
    public function update($table, $data) {
71
        $this->operation = 'UPDATE';
72
        $this->table = $table;
73
        $this->cols = $data;
74
        $result = $this->query();
75
        return $result->pdoResult->rowCount();
76
    }
77
78
    public function delete($table) {
79
        $this->operation = 'DELETE';
80
        $this->table = $table;
81
        $result = $this->query();
82
        return $result->pdoResult->rowCount();
83
    }
84
85
    public function createTable($table_name, $cols, $indexes = []) {
86
        $this->operation = 'CREATE TABLE';
87
        $this->table = $table_name;
88
        $this->cols = $cols;
89
        $this->indexes = $indexes;
90
        return $this->query();
91
    }
92
93
    public function cols($cols) {
94
        if (is_array($cols)) {
95
            $this->cols = array_merge($this->cols, array_values($cols));
96
        } else {
97
            $this->cols[] = $cols;
98
        }
99
    }
100
101
    public function join($table, $where = false, $type = 'LEFT', $alias = '') {
102
        if (is_array($table)) {
103
            foreach ($table as $item) {
104
                if (!is_array($item)) {
105
                    call_user_func_array(array($this, 'join'), $table);
106
                    break;
107
                } else {
108
                    $this->join($item);
109
                }
110
            }
111
        } else {
112
            $this->join[] = [$table, $where, $type, $alias];
113
        }
114
    }
115
116
    public function where($where = '', $value = '', $operation = false, $concatenation = 'AND') {
117
        if (!is_array($where)) {
118
            $this->where[] = [$where, $value, $operation, $concatenation];
119
        } else {
120
            $this->where[] = $where;
121
        }
122
    }
123
124
    public function having($where = '', $value = '', $operation = false, $concatenation = 'AND') {
125
        if (!is_array($where)) {
126
            $this->having[] = [$where, $value, $operation, $concatenation];
127
        } else {
128
            $this->having[] = $where;
129
        }
130
    }
131
132
    public function group($colname) {
133
        $this->group[] = $colname;
134
    }
135
136
    public function order($order, $type = 'ASC') {
137
138
139
        if (!is_array($order)) {
140
            $this->order[] = "{$order} {$type}";
141
        } else {
142
            foreach ($order as $item) {
143
                if (!is_array($item)) {
144
                    call_user_func_array(array($this, 'order'), $order);
145
                    break;
146
                } else {
147
                    $this->order($item);
148
                }
149
            }
150
        }
151
    }
152
153
    public function limit($start = 0, $len = 0) {
154
        $start = intval($start);
155
        $len = intval($len);
156
        $this->limit = "LIMIT {$start}";
157
        if ($len !== 0) {
158
            $this->limit .= ",{$len}";
159
        }
160
    }
161
162
    public function buildJoin($table, $where = false, $type = 'LEFT', $alias = '') {
163
        $join = '';
164
        if (is_array($table)) {
165
            $joins = func_get_args();
166
            foreach ($joins as $joinAr) {
167
                $join .= call_user_func_array([$this, 'buildJoin'], $joinAr);
168
            }
169
        } else {
170
            $join .= " {$type} JOIN {$this->curInstance->table_prefix}{$table}";
171
            if ($alias) {
172
                $join .= " AS `{$alias}`";
173
            }
174
            if ($where) {
175
                $join .= " ON {$where}";
176
            }
177
        }
178
        return $join;
179
    }
180
181
    /**
182
     * Build where string
183
     *
184
     * @param string|array $where
185
     * @param mixed $value
186
     * @param string $operation
187
     * @param string $concatenation
188
     */
189
    public function buildWhere($where = '', $value = '', $operation = '=', $concatenation = 'AND') {
190
        if (!is_array($where)) {
191
            if (empty($operation)) {
192
                $operation = '=';
193
            }
194
195
            if ($concatenation === false) {
196
                $concatenation = 'AND';
197
            } elseif ($concatenation === true) {
198
                $concatenation = '';
199
            }
200
201
            if ($this->whereString == '') {
202
                $this->whereString = ' WHERE ';
203
            }
204
205
            if (stristr($operation, 'IN') || stristr($operation, 'NOT IN')) {
206
                if (is_array($value)) {
207
                    $newValue = '';
208
                    foreach ($value as $item) {
209
                        if ($newValue) {
210
                            $newValue .= ',';
211
                        }
212
                        if (is_string($item)) {
213
                            $newValue .= '"' . $item . '"';
214
                        } else {
215
                            $newValue .= $item;
216
                        }
217
                    }
218
                    $value = '(' . $newValue . ')';
219
                } elseif (!preg_match('!\(!', $value) && !preg_match('![^0-9,\.\(\) ]!', $value)) {
220
                    $value = "({$value})";
221
                } elseif (preg_match('!\(!', $value) && preg_match('![^0-9,\.\(\) ]!', $value)) {
222
                    $value = "\"{$value}\"";
223
                }
224
            } elseif (!in_array($value, array('CURRENT_TIMESTAMP', 'NULL'))) {
225
                $this->params[] = $value;
226
                $value = "?";
227
            }
228
229
            if (substr($this->whereString, -1, 1) == '(' || substr($this->whereString, -2, 2) == 'E ') {
230
                $this->whereString .= " {$where} {$operation} {$value} ";
231
            } else {
232
                $this->whereString .= "{$concatenation} {$where} {$operation} {$value} ";
233
            }
234
        } else {
235
            $i = -1;
236
            while (isset($where[++$i])) {
237
                $item = $where[$i];
238
                if (isset($where[$i + 1]) && !isset($where[$i - 1]) && is_array($where[$i])) {
239
                    if ($this->whereString != null && substr($this->whereString, -1, 1) != '(' && $this->whereString != 'WHERE ') {
240
                        if (!isset($item[3])) {
241
                            $concatenation = 'AND';
242
                        } else {
243
                            $concatenation = $item[3];
244
                        }
245
246
                        $this->whereString .= "{$concatenation} ";
247
                    }
248
249
                    if ($this->whereString != '') {
250
                        $this->whereString .= '(';
251
                    } else {
252
                        $this->whereString = 'WHERE (';
253
                    }
254
                }
255
256
                if (!is_array($item)) {
257
                    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

257
                    call_user_func_array(array($this, 'buildWhere'), /** @scrutinizer ignore-type */ $where);
Loading history...
258
                    break;
259
                } else {
260
                    $this->buildWhere($item);
261
                }
262
                if (!isset($where[$i + 1]) && isset($where[$i - 1])) {
263
                    $this->whereString .= ') ';
264
                }
265
            }
266
        }
267
    }
268
269
    /**
270
     * Build having string
271
     *
272
     * @param string|array $where
273
     * @param mixed $value
274
     * @param string $operation
275
     * @param string $concatenation
276
     */
277
    public function buildHaving($where = '', $value = '', $operation = '=', $concatenation = 'AND') {
278
        if (!is_array($where)) {
279
            if (empty($operation)) {
280
                $operation = '=';
281
            }
282
            if ($concatenation === false) {
283
                $concatenation = 'AND';
284
            } elseif ($concatenation === true) {
285
                $concatenation = '';
286
            }
287
288
            if ($this->havingString == '') {
289
                $this->havingString = ' HAVING ';
290
            }
291
292
            if (stristr($operation, 'IN') || stristr($operation, 'NOT IN')) {
293
                if (is_array($value)) {
294
                    $newValue = '';
295
                    foreach ($value as $item) {
296
                        if ($newValue) {
297
                            $newValue .= ',';
298
                        }
299
                        if (is_string($item)) {
300
                            $newValue .= '"' . $item . '"';
301
                        } else {
302
                            $newValue .= $item;
303
                        }
304
                    }
305
                    $value = '(' . $newValue . ')';
306
                } elseif (!preg_match('!\(!', $value) && !preg_match('![^0-9,\.\(\) ]!', $value)) {
307
                    $value = "({$value})";
308
                } elseif (preg_match('!\(!', $value) && preg_match('![^0-9,\.\(\) ]!', $value)) {
309
                    $value = "\"{$value}\"";
310
                }
311
            } elseif (!in_array($value, array('CURRENT_TIMESTAMP', 'NULL'))) {
312
                $this->params[] = $value;
313
                $value = "?";
314
            }
315
            if (substr($this->havingString, -1, 1) == '(' || substr($this->havingString, -2, 2) == 'G ') {
316
                $this->havingString .= " {$where} {$operation} {$value} ";
317
            } else {
318
                $this->havingString .= "{$concatenation} {$where} {$operation} {$value} ";
319
            }
320
        } else {
321
            $i = -1;
322
            while (isset($where[++$i])) {
323
                $item = $where[$i];
324
                if (isset($where[$i + 1]) && !isset($where[$i - 1]) && is_array($where[$i])) {
325
                    if ($this->havingString != null && substr($this->havingString, -1, 1) != '(' && $this->havingString != 'HAVING ') {
326
                        if (!isset($item[3])) {
327
                            $concatenation = 'AND';
328
                        } else {
329
                            $concatenation = $item[3];
330
                        }
331
332
                        $this->havingString .= "{$concatenation} ";
333
                    }
334
335
                    if ($this->havingString != '') {
336
                        $this->havingString .= '(';
337
                    } else {
338
                        $this->havingString = 'HAVING (';
339
                    }
340
                }
341
342
                if (!is_array($item)) {
343
                    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

343
                    call_user_func_array(array($this, 'buildHaving'), /** @scrutinizer ignore-type */ $where);
Loading history...
344
                    break;
345
                } else {
346
                    $this->buildHaving($item);
347
                }
348
                if (!isset($where[$i + 1]) && isset($where[$i - 1])) {
349
                    $this->havingString .= ') ';
350
                }
351
            }
352
        }
353
    }
354
355
    public function buildQuery() {
356
        $query = $this->operation;
357
        $this->operation = strtoupper($this->operation);
358
359
        switch ($this->operation) {
360
            case 'SELECT':
361
                $query .= ' ' . ($this->distinct ? 'DISTINCT' : '');
362
                $query .= ' ' . (!$this->cols ? '*' : ((is_array($this->cols) ? implode(',', $this->cols) : $this->cols)));
363
            // no break
364
            case 'DELETE':
365
                $query .= ' FROM';
366
                break;
367
            case 'INSERT':
368
                $query .= ' INTO';
369
                break;
370
        }
371
        $query .= " `{$this->curInstance->db_name}`.`{$this->curInstance->table_prefix}{$this->table}`";
372
        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...
373
            $query .= $this->buildJoin($this->join);
374
        }
375
        switch ($this->operation) {
376
            case 'INSERT':
377
                $this->params = array_merge($this->params, array_values($this->cols));
378
                $colsStr = '';
379
                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...
380
                    $colsStr = '`' . implode('`,`', array_keys($this->cols)) . '`';
381
                }
382
                $query .= ' (' . $colsStr . ') VALUES (' . rtrim(str_repeat('?,', count($this->cols)), ',') . ')';
383
                break;
384
            case 'CREATE TABLE':
385
                $query .= " (";
386
                foreach ($this->cols as $col_name => $param) {
387
                    if ($param == 'pk') {
388
                        $param = "int(11) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`{$col_name}`)";
389
                    }
390
                    $query .= " `{$col_name}` {$param},";
391
                }
392
                $query = rtrim($query, ',');
393
                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...
394
                    $query .= ', ' . implode(',', $this->indexes);
395
                }
396
                $query .= ") ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci";
397
                break;
398
            case 'UPDATE':
399
                $updates = [];
400
                foreach ($this->cols as $key => $item) {
401
                    if (!in_array($item, array('CURRENT_TIMESTAMP'))) {
402
                        $this->params[] = $item;
403
                        $updates[] = "`{$key}` = ?";
404
                    } else {
405
                        $updates[] = "`{$key}` = {$item}";
406
                    }
407
                }
408
                $update = implode(',', $updates);
409
                $query .= " SET {$update}";
410
            // no break
411
            case 'SELECT':
412
            case 'DELETE':
413
                $this->buildWhere($this->where);
414
                if ($this->whereString) {
415
                    $query .= ' ' . $this->whereString;
416
                }
417
                break;
418
        }
419
        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...
420
            $query .= ' GROUP BY ' . implode(',', $this->group);
421
        }
422
        $this->buildHaving($this->having);
423
        if ($this->havingString) {
424
            $query .= ' ' . $this->havingString;
425
        }
426
        if ($this->order) {
427
            $query .= ' ORDER BY ' . implode(',', $this->order);
428
        }
429
        if ($this->limit) {
430
            $query .= ' ' . $this->limit;
431
        }
432
        return ['query' => $query, 'params' => $this->params];
433
    }
434
435
    /**
436
     * Execute query
437
     *
438
     * @param string|array $query
439
     * @return Result
440
     */
441
    public function query($query = []) {
442
        if (!$query) {
443
            $this->params = [];
444
            $query = $this->buildQuery();
445
        }
446
        if (is_string($query)) {
447
            $query = ['query' => $query, 'params' => $this->params];
448
        }
449
450
        $prepare = $this->curInstance->pdo->prepare($query['query']);
451
        $params = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $params is dead and can be removed.
Loading history...
452
        $pos = 1;
453
        foreach ($query['params'] as $param) {
454
            $prepare->bindValue($pos++, $param, is_null($param) ? \PDO::PARAM_NULL : \PDO::PARAM_STR);
455
        }
456
        $key = \Inji\App::$cur->log->start('query: ' . $query['query']);
0 ignored issues
show
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

456
        /** @scrutinizer ignore-call */ 
457
        $key = \Inji\App::$cur->log->start('query: ' . $query['query']);
Loading history...
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...
457
        $prepare->execute();
458
        \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

458
        \Inji\App::$cur->log->/** @scrutinizer ignore-call */ 
459
                              end($key);
Loading history...
459
        $this->curInstance->lastQuery = $query;
460
        $result = new Result();
0 ignored issues
show
Bug introduced by
The call to Inji\Db\Mysql\Result::__construct() has too few arguments starting with dbResult. ( Ignorable by Annotation )

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

460
        $result = /** @scrutinizer ignore-call */ new Result();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
461
        $result->pdoResult = $prepare;
462
        if ($this->curInstance->dbInstance && $this->curInstance->dbInstance->curQuery && $this->curInstance->dbInstance->curQuery === $this) {
463
            $this->curInstance->dbInstance->curQuery = null;
464
        }
465
466
        return $result;
467
    }
468
}