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
![]() |
|||||||||
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
$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
![]() |
|||||||||
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
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.
}
}
![]() |
|||||||||
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
$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
![]() |
|||||||||
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
|
|||||||||
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
|
|||||||||
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
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
![]() 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
![]() 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
![]() |
|||||||||
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
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
![]() |
|||||||||
280 | $result .= $joinTable; |
||||||||
281 | 2 | } else { |
|||||||
282 | $result .= $this->protect($joinTable); |
||||||||
0 ignored issues
–
show
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
![]() |
|||||||||
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
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
![]() |
|||||||||
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 |