Completed
Push — master ( 186a7f...bab9dc )
by Gabriel
10:46
created

Select   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 56.93%

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 2
dl 0
loc 319
ccs 78
cts 137
cp 0.5693
rs 2.56
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 20 5
B match() 0 22 8
A join() 0 16 3
A group() 0 7 1
B assemble() 0 43 7
A parseOptions() 0 8 2
B parseCols() 0 22 11
B parseFrom() 0 30 10
B parseGroup() 0 26 10
A union() 0 4 1
C parseJoin() 0 46 15

How to fix   Complexity   

Complex Class

Complex classes like Select 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Select, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Nip\Database\Query;
4
5
use Nip\Database\Query\Select\Union;
6
7
/**
8
 * Class Select
9
 * @package Nip\Database\Query
10
 *
11
 * @method $this options() options(string $option = null)
12
 * @method $this setFrom() setFrom(string $table = null)
13
 * @method $this setOrder() setOrder(array | string $cols = null)
14
 */
15
class Select extends AbstractQuery
16
{
17
18
    /**
19
     * @param $name
20
     * @param $arguments
21
     * @return AbstractQuery|Select
22
     */
23 8
    public function __call($name, $arguments)
24
    {
25 8
        if (in_array($name, ['min', 'max', 'count', 'avg', 'sum'])) {
26
            $input = reset($arguments);
27
28
            if (is_array($input)) {
29
                $input[] = false;
30
            } else {
31
                $alias = isset($arguments[1]) ? $arguments[1] : null;
32
                $protected = isset($arguments[2]) ? $arguments[2] : null;
33
                $input = [$input, $alias, $protected];
34
            }
35
36
            $input[0] = strtoupper($name).'('.$this->protect($input[0]).')';
0 ignored issues
show
Documentation introduced by
$input[0] is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
37
38
            return $this->cols($input);
0 ignored issues
show
Unused Code introduced by
The call to Select::cols() has too many arguments starting with $input.

This check compares calls to functions or methods with their respective definitions. If the call has more 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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
39
        }
40
41 8
        return parent::__call($name, $arguments);
42
    }
43
44
    /**
45
     * Inserts FULLTEXT statement into $this->select and $this->where
46
     *
47
     * @param mixed $fields
48
     * @param string $against
49
     * @param string $alias
50
     * @param boolean $boolean_mode
51
     * @return $this
52
     */
53
    public function match($fields, $against, $alias, $boolean_mode = true)
54
    {
55
        if (!is_array($fields)) {
56
            $fields = [];
57
        }
58
59
        $match = [];
60
        foreach ($fields as $itemField) {
61
            if (!is_array($itemField)) {
62
                $itemField = [$itemField];
63
64
                $field = isset($itemField[0]) ? $itemField[0] : false;
65
                $protected = isset($itemField[1]) ? $itemField[1] : true;
66
67
                $match[] = $protected ? $this->protect($field) : $field;
68
            }
69
        }
70
        $match = "MATCH(".implode(",",
71
                $match).") AGAINST ('".$against."'".($boolean_mode ? " IN BOOLEAN MODE" : "").")";
72
73
        return $this->cols([$match, $alias, false])->where([$match]);
0 ignored issues
show
Unused Code introduced by
The call to Select::cols() has too many arguments starting with array($match, $alias, false).

This check compares calls to functions or methods with their respective definitions. If the call has more 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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
74
    }
75
76
    /**
77
     * Inserts JOIN entry for the last table inserted by $this->from()
78
     *
79
     * @param mixed $table the table to be joined, given as simple string or name - alias pair
80
     * @param string|boolean $on
81
     * @param string $type SQL join type (INNER, OUTER, LEFT INNER, etc.)
82
     * @return $this
83
     */
84 2
    public function join($table, $on = false, $type = '')
85
    {
86 2
        $lastTable = end($this->parts['from']);
87
88 2
        if (!$lastTable) {
89
            trigger_error('No previous table to JOIN', E_USER_ERROR);
90
        }
91
92 2
        if (is_array($lastTable)) {
93
            $lastTable = $lastTable[1];
94
        }
95
96 2
        $this->parts['join'][$lastTable][] = [$table, $on, $type];
97
98 2
        return $this;
99
    }
100
101
    /**
102
     * Sets the group paramater for the query
103
     *
104
     * @param array $fields
105
     * @param boolean $rollup suport for modifier WITH ROLLUP
106
     * @return $this
107
     */
108
    public function group($fields, $rollup = false)
109
    {
110
        $this->parts['group']['fields'] = $fields;
111
        $this->parts['group']['rollup'] = $rollup;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @return string
118
     */
119 7
    public function assemble()
120
    {
121 7
        $select = $this->parseCols();
122 7
        $options = $this->parseOptions();
123 7
        $from = $this->parseFrom();
124
125 7
        $group = $this->parseGroup();
126 7
        $having = $this->parseHaving();
127
128 7
        $order = $this->parseOrder();
129
130 7
        $query = "SELECT";
131
132 7
        if (!empty($options)) {
133 1
            $query .= " $options";
134
        }
135
136 7
        if (!empty($select)) {
137 7
            $query .= " $select";
138
        }
139
140 7
        if (!empty($from)) {
141 7
            $query .= " FROM $from";
142
        }
143
144 7
        $query .= $this->assembleWhere();
145
146 7
        if (!empty($group)) {
147
            $query .= " GROUP BY $group";
148
        }
149
150 7
        if (!empty($having)) {
151
            $query .= " HAVING $having";
152
        }
153
154 7
        if (!empty($order)) {
155
            $query .= " ORDER BY $order";
156
        }
157
158 7
        $query .= $this->assembleLimit();
159
160 7
        return $query;
161
    }
162
163
    /**
164
     * @return null|string
165
     */
166 7
    public function parseOptions()
167
    {
168 7
        if (!empty($this->parts['options'])) {
169 1
            return implode(" ", array_map("strtoupper", $this->parts['options']));
170
        }
171
172 6
        return null;
173
    }
174
175
    /**
176
     * @param $query
177
     * @return Union
178
     */
179
    public function union($query)
180
    {
181
        return new Union($this, $query);
182
    }
183
184
    /**
185
     * Parses SELECT entries
186
     *
187
     * @return string
188
     */
189 7
    protected function parseCols()
190
    {
191 7
        if (!isset($this->parts['cols']) || !is_array($this->parts['cols']) || count($this->parts['cols']) < 1) {
192 2
            return '*';
193
        } else {
194 5
            $selectParts = [];
195
196 5
            foreach ($this->parts['cols'] as $itemSelect) {
197 5
                if (is_array($itemSelect)) {
198
                    $field = isset($itemSelect[0]) ? $itemSelect[0] : false;
199
                    $alias = isset($itemSelect[1]) ? $itemSelect[1] : false;
200
                    $protected = isset($itemSelect[2]) ? $itemSelect[2] : true;
201
202
                    $selectParts[] = ($protected ? $this->protect($field) : $field).(!empty($alias) ? ' AS '.$this->protect($alias) : '');
203
                } else {
204 5
                    $selectParts[] = $itemSelect;
205
                }
206
            }
207
208 5
            return implode(', ', $selectParts);
209
        }
210
    }
211
212
    /**
213
     * Parses FROM entries
214
     * @return string
215
     */
216 7
    private function parseFrom()
217
    {
218 7
        if (!empty($this->parts['from'])) {
219 7
            $parts = [];
220
221 7
            foreach ($this->parts['from'] as $key => $item) {
222 7
                if (is_array($item)) {
223
                    $table = isset($item[0]) ? $item[0] : false;
224
                    $alias = isset($item[1]) ? $item[1] : false;
225
226
                    if (is_object($table)) {
227
                        if (!$alias) {
228
                            trigger_error('Select statements in for need aliases defined', E_USER_ERROR);
229
                        }
230
                        $parts[$key] = '('.$table.') AS '.$this->protect($alias).$this->parseJoin($alias);
231
                    } else {
232
                        $parts[$key] = $this->protect($table).' AS '.$this->protect((!empty($alias) ? $alias : $table)).$this->parseJoin($alias);
233
                    }
234 7
                } elseif (!strpos($item, ' ')) {
235 2
                    $parts[] = $this->protect($item).$this->parseJoin($item);
236
                } else {
237 7
                    $parts[] = $item;
238
                }
239
            }
240
241 7
            return implode(", ", array_unique($parts));
242
        }
243
244
        return null;
245
    }
246
247
    /**
248
     * Parses JOIN entries for a given table
249
     * Concatenates $this->join entries for input table
250
     *
251
     * @param string $table table to build JOIN statement for
252
     * @return string
253
     */
254 2
    private function parseJoin($table)
255
    {
256 2
        $result = '';
257
258 2
        if (isset($this->parts['join'][$table])) {
259 2
            foreach ($this->parts['join'][$table] as $join) {
260 2
                if (!is_array($join[0])) {
261 1
                    $join[0] = [$join[0]];
262
                }
263
264 2
                $joinTable = isset($join[0][0]) ? $join[0][0] : false;
265 2
                $joinAlias = isset($join[0][1]) ? $join[0][1] : false;
266 2
                $joinOn = isset($join[1]) ? $join[1] : false;
267
268
269 2
                $joinType = isset($join[2]) ? $join[2] : '';
270
271 2
                $result .= ($joinType ? ' '.strtoupper($joinType) : '').' JOIN ';
272 2
                if ($joinTable instanceof AbstractQuery) {
273
                    $result .= '('.$joinTable.')';
274
                    if (empty($joinAlias)) {
275
                        $joinAlias = 'join1';
276
                    }
277
                    $joinTable = $joinAlias;
278 2
                } elseif (strpos($joinTable, '(') !== false) {
279
                    $result .= $joinTable;
280
                } else {
281 2
                    $result .= $this->protect($joinTable);
282
                }
283 2
                $result .= (!empty($joinAlias) ? ' AS '.$this->protect($joinAlias) : '');
284
285 2
                if ($joinOn) {
286 2
                    $result .= ' ON ';
287 2
                    if (is_array($joinOn)) {
288 2
                        $result .= $this->protect($table.'.'.$joinOn[0])
289 2
                            .' = '
290 2
                            .$this->protect($joinTable.'.'.$joinOn[1]);
291
                    } else {
292 2
                        $result .= '('.$joinOn.')';
293
                    }
294
                }
295
            }
296
        }
297
298 2
        return $result;
299
    }
300
301
    /**
302
     * Parses GROUP entries
303
     *
304
     * @uses $this->group['fields'] array with elements to group by
305
     * @return string
306
     */
307 7
    private function parseGroup()
308
    {
309 7
        $group = '';
310 7
        if (isset($this->parts['group']['fields'])) {
311
            if (is_array($this->parts['group']['fields'])) {
312
                $groupFields = [];
313
                foreach ($this->parts['group']['fields'] as $field) {
314
                    $field = is_array($field) ? $field : [$field];
315
                    $column = isset($field[0]) ? $field[0] : false;
316
                    $type = isset($field[1]) ? $field[1] : '';
317
318
                    $groupFields[] = $this->protect($column).($type ? ' '.strtoupper($type) : '');
319
                }
320
321
                $group .= implode(', ', $groupFields);
322
            } else {
323
                $group .= $this->parts['group']['fields'];
324
            }
325
        }
326
327 7
        if (isset($this->parts['group']['rollup']) && $this->parts['group']['rollup'] !== false) {
328
            $group .= ' WITH ROLLUP';
329
        }
330
331 7
        return $group;
332
    }
333
}
334