This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Childish\query; |
||
3 | |||
4 | use Childish\query\Builder; |
||
5 | use Childish\support\Collection; |
||
6 | use Childish\support\Tools; |
||
7 | |||
8 | /** |
||
9 | * QueryGrammar |
||
10 | * |
||
11 | * @author Pu ShaoWei <[email protected]> |
||
12 | * @date 2017/12/7 |
||
13 | * @package Childish |
||
14 | * @version 1.0 |
||
15 | */ |
||
16 | class QueryGrammar extends Grammar |
||
17 | { |
||
18 | /** |
||
19 | * The components that make up a select clause. |
||
20 | * |
||
21 | * @var array |
||
22 | */ |
||
23 | protected $selectComponents = [ |
||
24 | 'aggregate', |
||
25 | 'columns', |
||
26 | 'from', |
||
27 | 'joins', |
||
28 | 'wheres', |
||
29 | 'groups', |
||
30 | 'havings', |
||
31 | 'orders', |
||
32 | 'limit', |
||
33 | 'offset', |
||
34 | 'lock', |
||
35 | ]; |
||
36 | |||
37 | /** |
||
38 | * Compile a select query into SQL. |
||
39 | * |
||
40 | * @param \Childish\query\Builder $query |
||
41 | * @return string |
||
42 | */ |
||
43 | public function compileSelect(Builder $query) |
||
44 | { |
||
45 | $sql = parent::compileSelect($query); |
||
46 | |||
47 | if ($query->unions) { |
||
0 ignored issues
–
show
|
|||
48 | $sql = '('.$sql.') '.$this->compileUnions($query); |
||
49 | } |
||
50 | |||
51 | return $sql; |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Compile the random statement into SQL. |
||
56 | * |
||
57 | * @param string $seed |
||
58 | * @return string |
||
59 | */ |
||
60 | public function compileRandom($seed) |
||
61 | { |
||
62 | return 'RAND('.$seed.')'; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Compile the lock into SQL. |
||
67 | * |
||
68 | * @param \Childish\query\Builder $query |
||
69 | * @param bool|string $value |
||
70 | * @return string |
||
71 | */ |
||
72 | protected function compileLock(Builder $query, $value) |
||
73 | { |
||
74 | if (! is_string($value)) { |
||
75 | return $value ? 'for update' : 'lock in share mode'; |
||
76 | } |
||
77 | |||
78 | return $value; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Compile an update statement into SQL. |
||
83 | * |
||
84 | * @param \Childish\query\Builder $query |
||
85 | * @param array $values |
||
86 | * @return string |
||
87 | */ |
||
88 | public function compileUpdate(Builder $query, $values) |
||
89 | { |
||
90 | $table = $this->wrapTable($query->from); |
||
91 | |||
92 | // Each one of the columns in the update statements needs to be wrapped in the |
||
93 | // keyword identifiers, also a place-holder needs to be created for each of |
||
94 | // the values in the list of bindings so we can make the sets statements. |
||
95 | $columns = $this->compileUpdateColumns($values); |
||
96 | |||
97 | // If the query has any "join" clauses, we will setup the joins on the builder |
||
98 | // and compile them so we can attach them to this update, as update queries |
||
99 | // can get join statements to attach to other tables when they're needed. |
||
100 | $joins = ''; |
||
101 | |||
102 | if (isset($query->joins)) { |
||
103 | $joins = ' '.$this->compileJoins($query, $query->joins); |
||
104 | } |
||
105 | |||
106 | // Of course, update queries may also be constrained by where clauses so we'll |
||
107 | // need to compile the where clauses and attach it to the query so only the |
||
108 | // intended records are updated by the SQL statements we generate to run. |
||
109 | $where = $this->compileWheres($query); |
||
110 | |||
111 | $sql = rtrim("update {$table}{$joins} set $columns $where"); |
||
112 | |||
113 | // If the query has an order by clause we will compile it since MySQL supports |
||
114 | // order bys on update statements. We'll compile them using the typical way |
||
115 | // of compiling order bys. Then they will be appended to the SQL queries. |
||
116 | if (! empty($query->orders)) { |
||
117 | $sql .= ' '.$this->compileOrders($query, $query->orders); |
||
118 | } |
||
119 | |||
120 | // Updates on MySQL also supports "limits", which allow you to easily update a |
||
121 | // single record very easily. This is not supported by all database engines |
||
122 | // so we have customized this update compiler here in order to add it in. |
||
123 | if (isset($query->limit)) { |
||
124 | $sql .= ' '.$this->compileLimit($query, $query->limit); |
||
125 | } |
||
126 | |||
127 | return rtrim($sql); |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Compile all of the columns for an update statement. |
||
132 | * |
||
133 | * @param array $values |
||
134 | * @return string |
||
135 | */ |
||
136 | protected function compileUpdateColumns($values) |
||
137 | { |
||
138 | return (new Collection($values))->map(function ($value, $key) { |
||
139 | if ($this->isJsonSelector($key)) { |
||
140 | return $this->compileJsonUpdateColumn($key, new JsonExpression($value)); |
||
141 | } else { |
||
142 | return $this->wrap($key).' = ? '; |
||
143 | } |
||
144 | })->implode(', '); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Prepares a JSON column being updated using the JSON_SET function. |
||
149 | * |
||
150 | * @param string $key |
||
151 | * @param \Illuminate\Database\Query\JsonExpression $value |
||
152 | * @return string |
||
153 | */ |
||
154 | protected function compileJsonUpdateColumn($key, JsonExpression $value) |
||
155 | { |
||
156 | $path = explode('->', $key); |
||
157 | |||
158 | $field = $this->wrapValue(array_shift($path)); |
||
159 | |||
160 | $accessor = '"$.'.implode('.', $path).'"'; |
||
161 | |||
162 | return "{$field} = json_set({$field}, {$accessor}, {$value->getValue()})"; |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Prepare the bindings for an update statement. |
||
167 | * |
||
168 | * Booleans, integers, and doubles are inserted into JSON updates as raw values. |
||
169 | * |
||
170 | * @param array $bindings |
||
171 | * @param array $values |
||
172 | * @return array |
||
173 | */ |
||
174 | public function prepareBindingsForUpdate(array $bindings, array $values) |
||
175 | { |
||
176 | $values = (new Collection($values))->reject(function ($value, $column) { |
||
177 | return $this->isJsonSelector($column) && |
||
178 | in_array(gettype($value), ['boolean', 'integer', 'double']); |
||
179 | })->all(); |
||
180 | |||
181 | return parent::prepareBindingsForUpdate($bindings, $values); |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Compile a delete statement into SQL. |
||
186 | * |
||
187 | * @param \Childish\query\Builder $query |
||
188 | * @return string |
||
189 | */ |
||
190 | public function compileDelete(Builder $query) |
||
191 | { |
||
192 | $table = $this->wrapTable($query->from); |
||
193 | |||
194 | $where = is_array($query->wheres) ? $this->compileWheres($query) : ''; |
||
195 | |||
196 | return isset($query->joins) |
||
197 | ? $this->compileDeleteWithJoins($query, $table, $where) |
||
0 ignored issues
–
show
$where is of type string , but the function expects a array .
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);
![]() |
|||
198 | : $this->compileDeleteWithoutJoins($query, $table, $where); |
||
0 ignored issues
–
show
$where is of type string , but the function expects a array .
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);
![]() |
|||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Compile a delete query that does not use joins. |
||
203 | * |
||
204 | * @param \Childish\query\Builder $query |
||
205 | * @param string $table |
||
206 | * @param array $where |
||
207 | * @return string |
||
208 | */ |
||
209 | protected function compileDeleteWithoutJoins($query, $table, $where) |
||
210 | { |
||
211 | $sql = trim("delete from {$table} {$where}"); |
||
212 | |||
213 | // When using MySQL, delete statements may contain order by statements and limits |
||
214 | // so we will compile both of those here. Once we have finished compiling this |
||
215 | // we will return the completed SQL statement so it will be executed for us. |
||
216 | if (! empty($query->orders)) { |
||
217 | $sql .= ' '.$this->compileOrders($query, $query->orders); |
||
218 | } |
||
219 | |||
220 | if (isset($query->limit)) { |
||
221 | $sql .= ' '.$this->compileLimit($query, $query->limit); |
||
222 | } |
||
223 | |||
224 | return $sql; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Compile a delete query that uses joins. |
||
229 | * |
||
230 | * @param \Childish\query\Builder $query |
||
231 | * @param string $table |
||
232 | * @param array $where |
||
233 | * @return string |
||
234 | */ |
||
235 | protected function compileDeleteWithJoins($query, $table, $where) |
||
236 | { |
||
237 | $joins = ' '.$this->compileJoins($query, $query->joins); |
||
238 | |||
239 | $alias = strpos(strtolower($table), ' as ') !== false |
||
240 | ? explode(' as ', $table)[1] : $table; |
||
241 | |||
242 | return trim("delete {$alias} from {$table}{$joins} {$where}"); |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Wrap a single string in keyword identifiers. |
||
247 | * |
||
248 | * @param string $value |
||
249 | * @return string |
||
250 | */ |
||
251 | protected function wrapValue($value) |
||
252 | { |
||
253 | if ($value === '*') { |
||
254 | return $value; |
||
255 | } |
||
256 | |||
257 | // If the given value is a JSON selector we will wrap it differently than a |
||
258 | // traditional value. We will need to split this path and wrap each part |
||
259 | // wrapped, etc. Otherwise, we will simply wrap the value as a string. |
||
260 | if ($this->isJsonSelector($value)) { |
||
261 | return $this->wrapJsonSelector($value); |
||
262 | } |
||
263 | |||
264 | return '`'.str_replace('`', '``', $value).'`'; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Wrap the given JSON selector. |
||
269 | * |
||
270 | * @param string $value |
||
271 | * @return string |
||
272 | */ |
||
273 | protected function wrapJsonSelector($value) |
||
274 | { |
||
275 | $path = explode('->', $value); |
||
276 | |||
277 | $field = $this->wrapValue(array_shift($path)); |
||
278 | |||
279 | return sprintf('%s->\'$.%s\'', $field, collect($path)->map(function ($part) { |
||
280 | return '"'.$part.'"'; |
||
281 | })->implode('.')); |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Determine if the given string is a JSON selector. |
||
286 | * |
||
287 | * @param string $value |
||
288 | * @return bool |
||
289 | */ |
||
290 | protected function isJsonSelector($value) |
||
291 | { |
||
292 | return Tools::contains($value, '->'); |
||
293 | } |
||
294 | } |
||
295 |
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.