bytic /
database
| 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
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
$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
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
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
$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
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
|
|||||||||
| 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
Loading history...
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
Loading history...
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
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
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
Loading history...
|
|||||||||
| 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
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
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
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 |