Select::parseFrom()   B
last analyzed

Complexity

Conditions 10
Paths 16

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 22.5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 18
c 2
b 0
f 0
nc 16
nop 0
dl 0
loc 29
ccs 8
cts 16
cp 0.5
crap 22.5
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * @param $name
19
     * @param $arguments
20
     * @return AbstractQuery|Select
21
     */
22
    public function __call($name, $arguments)
23 11
    {
24
        if (in_array($name, ['min', 'max', 'count', 'avg', 'sum'])) {
25 11
            $input = reset($arguments);
26
27
            if (is_array($input)) {
28
                $input[] = false;
29
            } else {
30
                $alias = isset($arguments[1]) ? $arguments[1] : null;
31
                $protected = isset($arguments[2]) ? $arguments[2] : null;
32
                $input = [$input, $alias, $protected];
33
            }
34
35
            $input[0] = strtoupper($name) . '(' . $this->protect($input[0]) . ')';
36
37
            return $this->cols($input);
38
        }
39
40
        return parent::__call($name, $arguments);
41 11
    }
42
43
    /**
44
     * Inserts FULLTEXT statement into $this->select and $this->where
45
     *
46
     * @param mixed $fields
47
     * @param string $against
48
     * @param string $alias
49
     * @param boolean $boolean_mode
50
     * @return $this
51
     */
52
    public function match($fields, $against, $alias, $boolean_mode = true)
53
    {
54
        if (!is_array($fields)) {
55
            $fields = [];
56
        }
57
58
        $match = [];
59
        foreach ($fields as $itemField) {
60
            if (!is_array($itemField)) {
61
                $itemField = [$itemField];
62
63
                $field = isset($itemField[0]) ? $itemField[0] : false;
64
                $protected = isset($itemField[1]) ? $itemField[1] : true;
65
66
                $match[] = $protected ? $this->protect($field) : $field;
0 ignored issues
show
Bug introduced by
It seems like $field can also be of type false; however, parameter $input of Nip\Database\Query\AbstractQuery::protect() 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

66
                $match[] = $protected ? $this->protect(/** @scrutinizer ignore-type */ $field) : $field;
Loading history...
67
            }
68
        }
69
        $match = 'MATCH(' . implode(
70
            ',',
71
            $match
72
        ) . ") AGAINST ('" . $against . "'" . ($boolean_mode ? ' IN BOOLEAN MODE' : '') . ')';
73
74
        return $this->cols([$match, $alias, false])->where([$match]);
75
    }
76
77
    /**
78
     * Inserts JOIN entry for the last table inserted by $this->from()
79
     *
80
     * @param mixed $table the table to be joined, given as simple string or name - alias pair
81
     * @param string|boolean $on
82
     * @param string $type SQL join type (INNER, OUTER, LEFT INNER, etc.)
83
     * @return $this
84 3
     */
85
    public function join($table, $on = false, $type = '')
86 3
    {
87
        $lastTable = end($this->parts['from']);
0 ignored issues
show
Bug introduced by
$this->parts['from'] of type null is incompatible with the type array|object expected by parameter $array of end(). ( Ignorable by Annotation )

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

87
        $lastTable = end(/** @scrutinizer ignore-type */ $this->parts['from']);
Loading history...
88 3
89
        if (!$lastTable) {
90
            trigger_error('No previous table to JOIN', E_USER_ERROR);
91
        }
92 3
93
        if (is_array($lastTable)) {
94
            $lastTable = $lastTable[1];
95
        }
96 3
97
        $this->parts['join'][$lastTable][] = [$table, $on, $type];
98 3
99
        return $this;
100
    }
101
102
    /**
103
     * Sets the group paramater for the query
104
     *
105
     * @param array $fields
106
     * @param boolean $rollup suport for modifier WITH ROLLUP
107
     * @return $this
108
     */
109
    public function group($fields, $rollup = false)
110
    {
111
        $this->parts['group']['fields'] = $fields;
112
        $this->parts['group']['rollup'] = $rollup;
113
114
        return $this;
115
    }
116
117
    /**
118
     * @return string
119 10
     */
120
    public function assemble()
121 10
    {
122 10
        $select = $this->parseCols();
123 10
        $options = $this->parseOptions();
124
        $from = $this->parseFrom();
125 10
126 10
        $group = $this->parseGroup();
127
        $having = $this->parseHaving();
128 10
129
        $order = $this->parseOrder();
130 10
131
        $query = "SELECT";
132 10
133 1
        if (!empty($options)) {
134
            $query .= " $options";
135
        }
136 10
137 10
        if (!empty($select)) {
138
            $query .= " $select";
139
        }
140 10
141 10
        if (!empty($from)) {
142
            $query .= " FROM $from";
143
        }
144 10
145
        $query .= $this->assembleWhere();
146 10
147
        if (!empty($group)) {
148
            $query .= " GROUP BY $group";
149
        }
150 10
151
        if (!empty($having)) {
152
            $query .= " HAVING $having";
153
        }
154 10
155
        if (!empty($order)) {
156
            $query .= " ORDER BY $order";
157
        }
158 10
159
        $query .= $this->assembleLimit();
160 10
161
        return $query;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query returns the type string which is incompatible with the return type mandated by Nip\Database\Query\AbstractQuery::assemble() of null.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
162
    }
163
164
    /**
165
     * @return null|string
166 10
     */
167
    public function parseOptions()
168 10
    {
169 1
        if (!empty($this->parts['options'])) {
170
            return implode(" ", array_map("strtoupper", $this->parts['options']));
0 ignored issues
show
Bug introduced by
$this->parts['options'] of type null is incompatible with the type array expected by parameter $array of array_map(). ( Ignorable by Annotation )

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

170
            return implode(" ", array_map("strtoupper", /** @scrutinizer ignore-type */ $this->parts['options']));
Loading history...
171
        }
172 9
173
        return null;
174
    }
175
176
    /**
177
     * @param $query
178
     * @return Union
179 1
     */
180
    public function union($query)
181 1
    {
182
        return new Union($this, $query);
183
    }
184
185
    /**
186
     * Parses SELECT entries
187
     *
188
     * @return string
189 10
     */
190
    protected function parseCols()
191 10
    {
192 5
        if (!isset($this->parts['cols']) || !is_array($this->parts['cols']) || count($this->parts['cols']) < 1) {
0 ignored issues
show
introduced by
The condition is_array($this->parts['cols']) is always false.
Loading history...
193
            return '*';
194 5
        } else {
195
            $selectParts = [];
196 5
197 5
            foreach ($this->parts['cols'] as $itemSelect) {
198
                if (is_array($itemSelect)) {
199
                    $field = isset($itemSelect[0]) ? $itemSelect[0] : false;
200
                    $alias = isset($itemSelect[1]) ? $itemSelect[1] : false;
201
                    $protected = isset($itemSelect[2]) ? $itemSelect[2] : true;
202
203
                    $selectParts[] = ($protected ? $this->protect($field) : $field) . (!empty($alias) ? ' AS ' . $this->protect($alias) : '');
204 5
                } else {
205
                    $selectParts[] = $itemSelect;
206
                }
207
            }
208 5
209
            return implode(', ', $selectParts);
210
        }
211
    }
212
213
    /**
214
     * Parses FROM entries
215
     * @return string
216 10
     */
217
    private function parseFrom()
218 10
    {
219 10
        if (!empty($this->parts['from'])) {
220
            $parts = [];
221 10
222 10
            foreach ($this->parts['from'] as $key => $item) {
0 ignored issues
show
Bug introduced by
The expression $this->parts['from'] of type null is not traversable.
Loading history...
223
                if (is_array($item)) {
224
                    $table = isset($item[0]) ? $item[0] : false;
225
                    $alias = isset($item[1]) ? $item[1] : false;
226
227
                    if (is_object($table)) {
228
                        if (!$alias) {
229
                            trigger_error('Select statements in for need aliases defined', E_USER_ERROR);
230
                        }
231
                        $parts[$key] = '(' . $table . ') AS ' . $this->protect($alias) . $this->parseJoin($alias);
0 ignored issues
show
Bug introduced by
It seems like $alias can also be of type false; however, parameter $input of Nip\Database\Query\AbstractQuery::protect() 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

231
                        $parts[$key] = '(' . $table . ') AS ' . $this->protect(/** @scrutinizer ignore-type */ $alias) . $this->parseJoin($alias);
Loading history...
Bug introduced by
Are you sure $table of type object 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

231
                        $parts[$key] = '(' . /** @scrutinizer ignore-type */ $table . ') AS ' . $this->protect($alias) . $this->parseJoin($alias);
Loading history...
Bug introduced by
It seems like $alias can also be of type false; however, parameter $table of Nip\Database\Query\Select::parseJoin() 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

231
                        $parts[$key] = '(' . $table . ') AS ' . $this->protect($alias) . $this->parseJoin(/** @scrutinizer ignore-type */ $alias);
Loading history...
232
                    } else {
233
                        $parts[$key] = $this->protect($table) . ' AS ' . $this->protect((!empty($alias) ? $alias : $table)) . $this->parseJoin($alias);
234 10
                    }
235 5
                } elseif (!strpos($item, ' ')) {
236
                    $parts[] = $this->protect($item) . $this->parseJoin($item);
237 10
                } else {
238
                    $parts[] = $item;
239
                }
240
            }
241 10
242
            return implode(", ", array_unique($parts));
243
        }
244
245
        return null;
246
    }
247
248
    /**
249
     * Parses JOIN entries for a given table
250
     * Concatenates $this->join entries for input table
251
     *
252
     * @param string $table table to build JOIN statement for
253
     * @return string
254 5
     */
255
    private function parseJoin($table)
256 5
    {
257
        $result = '';
258 5
259 3
        if (isset($this->parts['join'][$table])) {
260 3
            foreach ($this->parts['join'][$table] as $join) {
261 1
                if (!is_array($join[0])) {
262
                    $join[0] = [$join[0]];
263
                }
264 3
265 3
                $joinTable = isset($join[0][0]) ? $join[0][0] : false;
266 3
                $joinAlias = isset($join[0][1]) ? $join[0][1] : false;
267
                $joinOn = isset($join[1]) ? $join[1] : false;
268
269 3
270
                $joinType = isset($join[2]) ? $join[2] : '';
271 3
272 3
                $result .= ($joinType ? ' ' . strtoupper($joinType) : '') . ' JOIN ';
273 1
                if ($joinTable instanceof AbstractQuery) {
274 1
                    $result .= '(' . $joinTable . ')';
275
                    if (empty($joinAlias)) {
276
                        $joinAlias = 'join1';
277 1
                    }
278 2
                    $joinTable = $joinAlias;
279
                } elseif (strpos($joinTable, '(') !== false) {
0 ignored issues
show
Bug introduced by
It seems like $joinTable can also be of type false; however, parameter $haystack of strpos() 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

279
                } elseif (strpos(/** @scrutinizer ignore-type */ $joinTable, '(') !== false) {
Loading history...
280
                    $result .= $joinTable;
281 2
                } else {
282
                    $result .= $this->protect($joinTable);
0 ignored issues
show
Bug introduced by
It seems like $joinTable can also be of type false; however, parameter $input of Nip\Database\Query\AbstractQuery::protect() 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

282
                    $result .= $this->protect(/** @scrutinizer ignore-type */ $joinTable);
Loading history...
283 3
                }
284
                $result .= (!empty($joinAlias) ? ' AS ' . $this->protect($joinAlias) : '');
285 3
286 3
                if ($joinOn) {
287 3
                    $result .= ' ON ';
288 3
                    if (is_array($joinOn)) {
289 3
                        $result .= $this->protect($table . '.' . $joinOn[0])
290 3
                            . ' = '
291
                            . $this->protect($joinTable . '.' . $joinOn[1]);
292 3
                    } else {
293
                        $result .= '(' . $joinOn . ')';
294
                    }
295
                }
296
            }
297
        }
298 5
299
        return $result;
300
    }
301
302
    /**
303
     * Parses GROUP entries
304
     *
305
     * @uses $this->group['fields'] array with elements to group by
306
     * @return string
307 10
     */
308
    private function parseGroup()
309 10
    {
310 10
        $group = '';
311
        if (isset($this->parts['group']['fields'])) {
312
            if (is_array($this->parts['group']['fields'])) {
313
                $groupFields = [];
314
                foreach ($this->parts['group']['fields'] as $field) {
315
                    $field = is_array($field) ? $field : [$field];
316
                    $column = isset($field[0]) ? $field[0] : false;
317
                    $type = isset($field[1]) ? $field[1] : '';
318
319
                    $groupFields[] = $this->protect($column) . ($type ? ' ' . strtoupper($type) : '');
0 ignored issues
show
Bug introduced by
It seems like $column can also be of type false; however, parameter $input of Nip\Database\Query\AbstractQuery::protect() 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

319
                    $groupFields[] = $this->protect(/** @scrutinizer ignore-type */ $column) . ($type ? ' ' . strtoupper($type) : '');
Loading history...
320
                }
321
322
                $group .= implode(', ', $groupFields);
323
            } else {
324
                $group .= $this->parts['group']['fields'];
325
            }
326
        }
327 10
328
        if (isset($this->parts['group']['rollup']) && $this->parts['group']['rollup'] !== false) {
329
            $group .= ' WITH ROLLUP';
330
        }
331 10
332
        return $group;
333
    }
334
}
335