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 | |||
3 | namespace Brouzie\Sphinxy; |
||
4 | |||
5 | class QueryBuilder |
||
6 | { |
||
7 | const TYPE_INSERT = 1; |
||
8 | const TYPE_SELECT = 2; |
||
9 | const TYPE_UPDATE = 3; |
||
10 | const TYPE_DELETE = 4; |
||
11 | const TYPE_REPLACE = 5; |
||
12 | |||
13 | /** |
||
14 | * @var Connection |
||
15 | */ |
||
16 | private $conn; |
||
17 | |||
18 | private $type; |
||
19 | |||
20 | private $sqlParts = array( |
||
21 | 'select' => array(), |
||
22 | 'from' => array(), |
||
23 | 'where' => array(), |
||
24 | 'groupBy' => array(), |
||
25 | 'groupByLimit' => null, |
||
26 | 'withinGroupOrderBy' => array(), |
||
27 | 'orderBy' => array(), |
||
28 | 'facet' => array(), |
||
29 | 'resultSetNames' => array(0), |
||
30 | 'set' => array(), |
||
31 | 'values' => array(), |
||
32 | 'options' => array(), |
||
33 | 'firstResult' => 0, |
||
34 | 'maxResults' => null, |
||
35 | ); |
||
36 | |||
37 | private static $multipleParts = array( |
||
38 | 'select' => true, |
||
39 | 'from' => true, |
||
40 | 'where' => true, |
||
41 | 'groupBy' => true, |
||
42 | 'groupByLimit' => false, |
||
43 | 'withinGroupOrderBy' => true, |
||
44 | 'orderBy' => true, |
||
45 | 'facet' => true, |
||
46 | 'resultSetNames' => true, |
||
47 | 'set' => true, |
||
48 | 'values' => true, |
||
49 | 'options' => true, |
||
50 | 'firstResult' => false, |
||
51 | 'maxResults' => false, |
||
52 | ); |
||
53 | |||
54 | private $isDirty = true; |
||
55 | |||
56 | private $sql; |
||
57 | |||
58 | private $parameters = array(); |
||
59 | |||
60 | private $parametersCounter = 0; |
||
61 | |||
62 | 81 | public function __construct(Connection $conn) |
|
63 | { |
||
64 | 81 | $this->conn = $conn; |
|
65 | 81 | } |
|
66 | |||
67 | public function getEscaper() |
||
68 | { |
||
69 | return $this->conn->getEscaper(); |
||
70 | } |
||
71 | |||
72 | 60 | public function select($select = null) |
|
73 | { |
||
74 | 60 | $this->type = self::TYPE_SELECT; |
|
75 | 60 | if (null === $select) { |
|
76 | return $this; |
||
77 | } |
||
78 | |||
79 | 60 | return $this->add('select', (array) $select); |
|
80 | } |
||
81 | |||
82 | 9 | public function addSelect($select) |
|
83 | { |
||
84 | 9 | $this->type = self::TYPE_SELECT; |
|
85 | |||
86 | 9 | return $this->add('select', (array) $select, true); |
|
87 | } |
||
88 | |||
89 | 6 | public function update($index) |
|
90 | { |
||
91 | 6 | $this->type = self::TYPE_UPDATE; |
|
92 | |||
93 | 6 | return $this->add('from', array('table' => $index)); |
|
94 | } |
||
95 | |||
96 | 6 | public function insert($index) |
|
97 | { |
||
98 | 6 | $this->type = self::TYPE_INSERT; |
|
99 | |||
100 | 6 | return $this->add('from', array('table' => $index)); |
|
101 | } |
||
102 | |||
103 | 3 | public function replace($index) |
|
104 | { |
||
105 | 3 | $this->type = self::TYPE_REPLACE; |
|
106 | |||
107 | 3 | return $this->add('from', array('table' => $index)); |
|
108 | } |
||
109 | |||
110 | 3 | public function delete($index) |
|
111 | { |
||
112 | 3 | $this->type = self::TYPE_DELETE; |
|
113 | |||
114 | 3 | return $this->add('from', array('table' => $index)); |
|
115 | } |
||
116 | |||
117 | 6 | public function set($key, $value) |
|
118 | { |
||
119 | 6 | return $this->add('set', compact('key', 'value'), true); |
|
120 | } |
||
121 | |||
122 | 9 | public function values(array $values) |
|
123 | { |
||
124 | 9 | return $this->add('values', $values); |
|
125 | } |
||
126 | |||
127 | 3 | public function addValues(array $values) |
|
128 | { |
||
129 | 3 | return $this->add('values', $values, true); |
|
130 | } |
||
131 | |||
132 | 60 | public function from($index) |
|
133 | { |
||
134 | 60 | return $this->add('from', array('table' => $index)); |
|
135 | } |
||
136 | |||
137 | 3 | public function addFrom($index) |
|
138 | { |
||
139 | 3 | return $this->add('from', array('table' => $index), true); |
|
140 | } |
||
141 | |||
142 | 39 | public function where($where) |
|
143 | { |
||
144 | 39 | return $this->add('where', $where); |
|
145 | } |
||
146 | |||
147 | 9 | public function andWhere($where) |
|
148 | { |
||
149 | 9 | return $this->add('where', $where, true); |
|
150 | } |
||
151 | |||
152 | 15 | public function groupBy($groupBy, $limit = null) |
|
153 | { |
||
154 | 10 | return $this |
|
155 | 15 | ->add('groupBy', $groupBy) |
|
156 | 15 | ->add('groupByLimit', $limit); |
|
157 | } |
||
158 | |||
159 | 3 | public function addGroupBy($groupBy) |
|
160 | { |
||
161 | 3 | return $this->add('groupBy', $groupBy, true); |
|
162 | } |
||
163 | |||
164 | 6 | public function withinGroupOrderBy($order, $direction = null) |
|
165 | { |
||
166 | 6 | return $this->add('withinGroupOrderBy', compact('order', 'direction')); |
|
167 | } |
||
168 | |||
169 | 3 | public function addWithinGroupOrderBy($order, $direction = null) |
|
170 | { |
||
171 | 3 | return $this->add('withinGroupOrderBy', compact('order', 'direction'), true); |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * @param string|array $facet 'column1', or array('column1', 'column1') or array('column1' => 'column_alias', 'column2') |
||
176 | * @param string $by |
||
177 | * @param string $order |
||
178 | * @param string $direction |
||
179 | * @param int $limit |
||
180 | * @param int $skip |
||
181 | * |
||
182 | * @return $this |
||
183 | */ |
||
184 | 3 | public function facet($facet, $by = null, $order = null, $direction = null, $limit = null, $skip = 0) |
|
185 | { |
||
186 | 3 | $facet = (array) $facet; |
|
187 | |||
188 | 3 | return $this->add('facet', compact('facet', 'by', 'order', 'direction', 'limit', 'skip'), true); |
|
189 | } |
||
190 | |||
191 | public function nameResultSet($name) |
||
192 | { |
||
193 | return $this->add('resultSetNames', $name, true); |
||
194 | } |
||
195 | |||
196 | 9 | public function orderBy($order, $direction = null) |
|
197 | { |
||
198 | 9 | return $this->add('orderBy', compact('order', 'direction')); |
|
199 | } |
||
200 | |||
201 | 6 | public function addOrderBy($order, $direction = null) |
|
202 | { |
||
203 | 6 | return $this->add('orderBy', compact('order', 'direction'), true); |
|
204 | } |
||
205 | |||
206 | 6 | public function setOption($name, $value) |
|
207 | { |
||
208 | 6 | return $this->add('options', compact('name', 'value'), true); |
|
209 | } |
||
210 | |||
211 | 6 | public function setMaxResults($limit) |
|
212 | { |
||
213 | 6 | return $this->add('maxResults', $limit); |
|
214 | } |
||
215 | |||
216 | 3 | public function setFirstResult($skip) |
|
217 | { |
||
218 | 3 | return $this->add('firstResult', $skip); |
|
219 | } |
||
220 | |||
221 | public function merge(self $qb) |
||
0 ignored issues
–
show
|
|||
222 | { |
||
223 | //TODO: делать или не делать? |
||
224 | // ... |
||
225 | } |
||
226 | |||
227 | 9 | public function setParameter($parameter, $value) |
|
228 | { |
||
229 | 9 | $this->parameters[$parameter] = $value; |
|
230 | |||
231 | 9 | return $this; |
|
232 | } |
||
233 | |||
234 | /** |
||
235 | * Creates a new named parameter and bind the value $value to it. |
||
236 | * |
||
237 | * @param string $value |
||
238 | * @param string $prefix the name to bind with |
||
239 | * |
||
240 | * @return string the placeholder name used |
||
241 | */ |
||
242 | 6 | public function createParameter($value, $prefix = 'gen_') |
|
243 | { |
||
244 | 6 | $prefix = preg_replace('/[^a-z0-9_]/ui', '_', $prefix); |
|
245 | 6 | $prefix .= ++$this->parametersCounter; |
|
246 | 6 | $this->setParameter($prefix, $value); |
|
247 | |||
248 | 6 | return ':'.$prefix; |
|
249 | } |
||
250 | |||
251 | 9 | public function getParameters() |
|
252 | { |
||
253 | 9 | return $this->parameters; |
|
254 | } |
||
255 | |||
256 | public function execute() |
||
257 | { |
||
258 | return $this->conn->executeUpdate($this->getSql(), $this->parameters); |
||
259 | } |
||
260 | |||
261 | public function getResult() |
||
262 | { |
||
263 | return $this->conn->executeQuery($this->getSql(), $this->parameters); |
||
264 | } |
||
265 | |||
266 | public function getMultiResult() |
||
267 | { |
||
268 | return $this->conn->executeMultiQuery($this->getSql(), $this->parameters, array(), $this->sqlParts['resultSetNames']); |
||
269 | } |
||
270 | |||
271 | 78 | public function getSql() |
|
272 | { |
||
273 | 78 | if (!$this->isDirty) { |
|
274 | return $this->sql; |
||
275 | } |
||
276 | |||
277 | 78 | switch ($this->type) { |
|
278 | 78 | case self::TYPE_SELECT: |
|
279 | 60 | $this->sql = $this->buildSqlForSelect(); |
|
280 | 60 | break; |
|
281 | |||
282 | 18 | case self::TYPE_INSERT: |
|
283 | 16 | case self::TYPE_REPLACE: |
|
284 | 9 | $this->sql = $this->buildSqlForInsert(); |
|
285 | 9 | break; |
|
286 | |||
287 | 9 | case self::TYPE_UPDATE: |
|
288 | 6 | $this->sql = $this->buildSqlForUpdate(); |
|
289 | 6 | break; |
|
290 | |||
291 | 3 | case self::TYPE_DELETE: |
|
292 | 3 | $this->sql = $this->buildSqlForDelete(); |
|
293 | 3 | break; |
|
294 | 52 | } |
|
295 | |||
296 | 78 | $this->isDirty = false; |
|
297 | |||
298 | 78 | return $this->sql; |
|
299 | } |
||
300 | |||
301 | /** |
||
302 | * Either appends to or replaces a single, generic query part. |
||
303 | * |
||
304 | * @param string $sqlPartName |
||
305 | * @param string|array $sqlPart |
||
306 | * @param bool $append |
||
307 | * |
||
308 | * @return $this this QueryBuilder instance |
||
309 | */ |
||
310 | 78 | protected function add($sqlPartName, $sqlPart, $append = false) |
|
311 | { |
||
312 | 78 | $this->isDirty = true; |
|
313 | |||
314 | 78 | if (self::$multipleParts[$sqlPartName]) { |
|
315 | 78 | if ($append) { |
|
316 | 45 | $this->sqlParts[$sqlPartName][] = $sqlPart; |
|
317 | 30 | } else { |
|
318 | 78 | $this->sqlParts[$sqlPartName] = array($sqlPart); |
|
319 | } |
||
320 | 52 | } else { |
|
321 | 21 | $this->sqlParts[$sqlPartName] = $sqlPart; |
|
322 | } |
||
323 | |||
324 | 78 | return $this; |
|
325 | } |
||
326 | |||
327 | 60 | protected function buildSqlForSelect() |
|
328 | { |
||
329 | 60 | $select = call_user_func_array('array_merge', $this->sqlParts['select']); |
|
330 | 60 | $query = 'SELECT '.implode(', ', $select).' FROM '; |
|
331 | |||
332 | 60 | $fromParts = array(); |
|
333 | 60 | foreach ($this->sqlParts['from'] as $from) { |
|
334 | 60 | $table = $from['table']; |
|
335 | 60 | if ($table instanceof static) { |
|
336 | 3 | $fromParts[] = '('.$table->getSql().')'; |
|
337 | 3 | foreach ($table->getParameters() as $parameter => $value) { |
|
338 | 3 | $this->setParameter($parameter, $value); |
|
339 | 2 | } |
|
340 | 2 | } else { |
|
341 | 60 | $fromParts[] = $table; |
|
342 | } |
||
343 | 40 | } |
|
344 | |||
345 | 60 | $query .= implode(', ', $fromParts) |
|
346 | 60 | .$this->buildWherePart() |
|
347 | 60 | .$this->buildGroupByPart() |
|
348 | 60 | .$this->buildOrderByPart(); |
|
349 | |||
350 | //TODO: inject limit, skip as parameters for better caching? Or just move caching to upper layer |
||
351 | 60 | if ($this->sqlParts['maxResults']) { |
|
352 | 6 | $query .= ' LIMIT '.(int) $this->sqlParts['firstResult'].', '.(int) $this->sqlParts['maxResults']; |
|
353 | 4 | } |
|
354 | |||
355 | 60 | $query .= $this->buildOptionsPart() |
|
356 | 60 | .$this->buildFacetPart(); |
|
357 | |||
358 | 60 | return $query; |
|
359 | } |
||
360 | |||
361 | 9 | protected function buildSqlForInsert() |
|
362 | { |
||
363 | 9 | $columns = array(); |
|
364 | 9 | $valuesParts = array(); |
|
365 | 9 | foreach ($this->sqlParts['values'] as $value) { |
|
366 | //TODO: check columns |
||
367 | 9 | $columns = array_keys($value); |
|
368 | 9 | $valuesParts[] = '('.implode(', ', $value).')'; |
|
369 | 6 | } |
|
370 | |||
371 | 9 | $index = current($this->sqlParts['from'])['table']; |
|
372 | 9 | $query = ($this->type === self::TYPE_REPLACE ? 'REPLACE' : 'INSERT') |
|
373 | 9 | .' INTO '.$index |
|
374 | 9 | .' ('.implode(', ', $columns).') VALUES '.implode(', ', $valuesParts); |
|
375 | |||
376 | 9 | return $query; |
|
377 | } |
||
378 | |||
379 | 6 | protected function buildSqlForUpdate() |
|
380 | { |
||
381 | 6 | $index = current($this->sqlParts['from'])['table']; |
|
382 | 6 | $setParts = array(); |
|
383 | 6 | foreach ($this->sqlParts['set'] as $setPart) { |
|
384 | 6 | $setParts[] = $setPart['key'].' = '.$setPart['value']; |
|
385 | 4 | } |
|
386 | |||
387 | 6 | $query = 'UPDATE '.$index.' SET '.implode(', ', $setParts).$this->buildWherePart(); |
|
388 | |||
389 | 6 | return $query; |
|
390 | } |
||
391 | |||
392 | 3 | protected function buildSqlForDelete() |
|
393 | { |
||
394 | 3 | $index = current($this->sqlParts['from'])['table']; |
|
395 | 3 | $query = 'DELETE FROM '.$index.$this->buildWherePart(); |
|
396 | |||
397 | 3 | return $query; |
|
398 | } |
||
399 | |||
400 | 69 | protected function buildWherePart() |
|
401 | { |
||
402 | 69 | if (!$this->sqlParts['where']) { |
|
403 | 33 | return ''; |
|
404 | } |
||
405 | |||
406 | 39 | return ' WHERE '.implode(' AND ', $this->sqlParts['where']); |
|
407 | } |
||
408 | |||
409 | 60 | protected function buildGroupByPart() |
|
410 | { |
||
411 | 60 | if (!$this->sqlParts['groupBy']) { |
|
412 | 45 | return ''; |
|
413 | } |
||
414 | |||
415 | 15 | $sql = ' GROUP'.($this->sqlParts['groupByLimit'] ? ' '.$this->sqlParts['groupByLimit'] : '') |
|
416 | 15 | .' BY '.implode(', ', $this->sqlParts['groupBy']); |
|
417 | |||
418 | 15 | if (!$this->sqlParts['withinGroupOrderBy']) { |
|
419 | 9 | return $sql; |
|
420 | } |
||
421 | |||
422 | 6 | $orderByParts = array(); |
|
423 | 6 | View Code Duplication | foreach ($this->sqlParts['withinGroupOrderBy'] as $orderBy) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
424 | 6 | $orderByParts[] = $orderBy['order'].$this->getDirection($orderBy['order'], $orderBy['direction']); |
|
425 | 4 | } |
|
426 | |||
427 | 6 | return $sql.' WITHIN GROUP ORDER BY '.implode(', ', $orderByParts); |
|
428 | } |
||
429 | |||
430 | 60 | protected function buildOrderByPart() |
|
431 | { |
||
432 | 60 | if (!$this->sqlParts['orderBy']) { |
|
433 | 51 | return ''; |
|
434 | } |
||
435 | |||
436 | 9 | $orderByParts = array(); |
|
437 | 9 | View Code Duplication | foreach ($this->sqlParts['orderBy'] as $orderBy) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
438 | 9 | $orderByParts[] = $orderBy['order'].$this->getDirection($orderBy['order'], $orderBy['direction']); |
|
439 | 6 | } |
|
440 | |||
441 | 9 | return ' ORDER BY '.implode(', ', $orderByParts); |
|
442 | } |
||
443 | |||
444 | 60 | protected function buildOptionsPart() |
|
445 | { |
||
446 | 60 | if (!$this->sqlParts['options']) { |
|
447 | 54 | return ''; |
|
448 | } |
||
449 | |||
450 | 6 | $optionsParts = array(); |
|
451 | 6 | foreach ($this->sqlParts['options'] as $option) { |
|
452 | 6 | $optionsParts[] = $option['name'].' = '.$option['value']; |
|
453 | 4 | } |
|
454 | |||
455 | 6 | return ' OPTION '.implode(', ', $optionsParts); |
|
456 | } |
||
457 | |||
458 | /** |
||
459 | * Build FACET {expr_list} [BY {expr_list}] [ORDER BY {expr | FACET()} {ASC | DESC}] [LIMIT [offset,] count]. |
||
460 | * |
||
461 | * @return string |
||
462 | */ |
||
463 | 60 | protected function buildFacetPart() |
|
464 | { |
||
465 | 60 | if (!$this->sqlParts['facet']) { |
|
466 | 57 | return ''; |
|
467 | } |
||
468 | |||
469 | 3 | $facetParts = array(); |
|
470 | 3 | foreach ($this->sqlParts['facet'] as $facet) { |
|
471 | 3 | $facetExpressions = array(); |
|
472 | 3 | foreach ($facet['facet'] as $key => $facetExpr) { |
|
473 | 3 | if (is_int($key)) { |
|
474 | 3 | $facetExpressions[] = $facetExpr; |
|
475 | 2 | } else { |
|
476 | 3 | $facetExpressions[] = $key.' AS '.$facetExpr; |
|
477 | } |
||
478 | 2 | } |
|
479 | 3 | $facetPart = 'FACET '.implode(', ', $facetExpressions); |
|
480 | 3 | if ($facet['by']) { |
|
481 | 3 | $facetPart .= ' BY '.$facet['by']; |
|
482 | 2 | } |
|
483 | 3 | if ($facet['order']) { |
|
484 | 3 | $facetPart .= ' ORDER BY '.$facet['order'].$this->getDirection($facet['order'], $facet['direction']); |
|
485 | 2 | } |
|
486 | 3 | if ($facet['limit']) { |
|
487 | $facetPart .= ' LIMIT '.(int) $facet['skip'].', '.(int) $facet['limit']; |
||
488 | } |
||
489 | |||
490 | 3 | $facetParts[] = $facetPart; |
|
491 | 2 | } |
|
492 | |||
493 | 3 | return ' '.implode(' ', $facetParts); |
|
494 | } |
||
495 | |||
496 | 18 | protected function getDirection($order, $direction) |
|
497 | { |
||
498 | 18 | if (strtoupper($direction) === 'DESC') { |
|
499 | 18 | return ' DESC'; |
|
500 | } |
||
501 | |||
502 | 15 | if (null === $direction && strtoupper($order) === 'RAND()') { |
|
503 | 3 | return ''; |
|
504 | } |
||
505 | |||
506 | 15 | return ' ASC'; |
|
507 | } |
||
508 | } |
||
509 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.