Issues (9)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Generic/Statement/Select.php (5 issues)

Labels
1
<?php
2
3
declare(strict_types=1);
4
5
namespace QB\Generic\Statement;
6
7
use QB\Generic\Clause\Column;
8
use QB\Generic\Clause\IColumn;
9
use QB\Generic\Clause\IJoin;
10
use QB\Generic\Clause\ITable;
11
use QB\Generic\Clause\Join;
12
use QB\Generic\Clause\Table;
13
use QB\Generic\Expr\Expr;
14
use QB\Generic\IQueryPart;
15
use RuntimeException;
16
17
/**
18
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
19
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
20
 * SuppressWarnings("complexity")
21
 */
22
class Select implements ISelect
23
{
24
    public const ALL      = 'ALL';
25
    public const DISTINCT = 'DISTINCT';
26
27
    /** @var array<int,Table|string> */
28
    protected array $tables = [];
29
30
    /** @var string[] */
31
    protected array $modifiers = [];
32
33
    /** @var IColumn[] */
34
    protected array $columns = [];
35
36
    /** @var IJoin[] */
37
    protected array $joins = [];
38
39
    /** @var IQueryPart[] */
40
    protected array $whereParts = [];
41
42
    /** @var IQueryPart[] */
43
    protected array $groupByParts = [];
44
45
    /** @var IQueryPart[] */
46
    protected array $havingParts = [];
47
48
    /** @var array<string,string> */
49
    protected array $orderBy = [];
50
51
    protected ?int $offset = null;
52
53
    protected ?int $limit = null;
54
55
    /**
56
     * Select constructor.
57
     *
58
     * @param IColumn|string ...$columns
59
     */
60 69
    public function __construct(IColumn|string ...$columns)
61
    {
62 69
        $this->columns(...$columns);
63 69
    }
64
65
    /**
66
     * @param ITable|string ...$tables
67
     *
68
     * @return $this
69
     */
70 51
    public function from(ITable|string ...$tables): static
71
    {
72 51
        $this->tables = array_merge($this->tables, $tables);
73
74 51
        return $this;
75
    }
76
77
    /**
78
     * @param string ...$modifiers
79
     *
80
     * @return $this
81
     */
82 5
    public function modifier(string ...$modifiers): static
83
    {
84 5
        $this->modifiers = array_merge($this->modifiers, $modifiers);
85
86 5
        return $this;
87
    }
88
89
    /**
90
     * @param IColumn|string ...$columns
91
     *
92
     * @return $this
93
     */
94 69
    public function columns(IColumn|string ...$columns): static
95
    {
96 69
        foreach ($columns as $column) {
97 42
            if ($column instanceof IColumn) {
98 13
                $this->columns[] = $column;
99 13
                continue;
100
            }
101
102 33
            if (strpos($column, ' AS ')) {
103 5
                $parts = explode(' AS ', $column);
104
105 5
                $this->columns[] = new Column($parts[0], $parts[1]);
106
            } else {
107 33
                $this->columns[] = new Column($column, null);
108
            }
109
        }
110
111 69
        return $this;
112
    }
113
114
    /**
115
     * @param ITable|string          $table
116
     * @param IQueryPart|string|null $on
117
     *
118
     * @return $this
119
     */
120 8
    public function innerJoin(ITable|string $table, IQueryPart|string|null $on = null): static
0 ignored issues
show
The type QB\Generic\Statement\null was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
121
    {
122 8
        $this->joins[] = new Join(IJoin::TYPE_INNER_JOIN, $table, $on);
0 ignored issues
show
It seems like $on can also be of type null; however, parameter $on of QB\Generic\Clause\Join::__construct() does only seem to accept QB\Generic\Clause\null|Q...neric\IQueryPart|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

122
        $this->joins[] = new Join(IJoin::TYPE_INNER_JOIN, $table, /** @scrutinizer ignore-type */ $on);
Loading history...
123
124 8
        return $this;
125
    }
126
127
    /**
128
     * @param ITable|string          $table
129
     * @param IQueryPart|string|null $on
130
     *
131
     * @return $this
132
     */
133 6
    public function leftJoin(ITable|string $table, IQueryPart|string|null $on = null): static
134
    {
135 6
        $this->joins[] = new Join(IJoin::TYPE_LEFT_JOIN, $table, $on);
0 ignored issues
show
It seems like $on can also be of type null; however, parameter $on of QB\Generic\Clause\Join::__construct() does only seem to accept QB\Generic\Clause\null|Q...neric\IQueryPart|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

135
        $this->joins[] = new Join(IJoin::TYPE_LEFT_JOIN, $table, /** @scrutinizer ignore-type */ $on);
Loading history...
136
137 6
        return $this;
138
    }
139
140
    /**
141
     * @param ITable|string          $table
142
     * @param IQueryPart|string|null $on
143
     *
144
     * @return $this
145
     */
146 3
    public function rightJoin(ITable|string $table, IQueryPart|string|null $on = null): static
147
    {
148 3
        $this->joins[] = new Join(IJoin::TYPE_RIGHT_JOIN, $table, $on);
0 ignored issues
show
It seems like $on can also be of type null; however, parameter $on of QB\Generic\Clause\Join::__construct() does only seem to accept QB\Generic\Clause\null|Q...neric\IQueryPart|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

148
        $this->joins[] = new Join(IJoin::TYPE_RIGHT_JOIN, $table, /** @scrutinizer ignore-type */ $on);
Loading history...
149
150 3
        return $this;
151
    }
152
153
    /**
154
     * @param ITable|string          $table
155
     * @param IQueryPart|string|null $on
156
     *
157
     * @return $this
158
     */
159 3
    public function fullJoin(ITable|string $table, IQueryPart|string|null $on = null): static
160
    {
161 3
        $this->joins[] = new Join(IJoin::TYPE_FULL_JOIN, $table, $on);
0 ignored issues
show
It seems like $on can also be of type null; however, parameter $on of QB\Generic\Clause\Join::__construct() does only seem to accept QB\Generic\Clause\null|Q...neric\IQueryPart|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

161
        $this->joins[] = new Join(IJoin::TYPE_FULL_JOIN, $table, /** @scrutinizer ignore-type */ $on);
Loading history...
162
163 3
        return $this;
164
    }
165
166
    /**
167
     * @param IJoin ...$joins
168
     *
169
     * @return $this
170
     */
171 3
    public function join(IJoin ...$joins): static
172
    {
173 3
        $this->joins = array_merge($this->joins, $joins);
174
175 3
        return $this;
176
    }
177
178
    /**
179
     * @param IQueryPart|string ...$whereParts
180
     *
181
     * @return $this
182
     */
183 8
    public function where(IQueryPart|string ...$whereParts): static
184
    {
185 8
        foreach ($whereParts as $wherePart) {
186 8
            $wherePart = is_string($wherePart) ? new Expr($wherePart) : $wherePart;
187
188 8
            $this->whereParts[] = $wherePart;
189
        }
190
191 8
        return $this;
192
    }
193
194
    /**
195
     * @param IQueryPart|string ...$groupByParts
196
     *
197
     * @return $this
198
     */
199 8
    public function groupBy(IQueryPart|string ...$groupByParts): static
200
    {
201 8
        foreach ($groupByParts as $groupByPart) {
202 8
            $groupByPart = is_string($groupByPart) ? new Expr($groupByPart) : $groupByPart;
203
204 8
            $this->groupByParts[] = $groupByPart;
205
        }
206
207 8
        return $this;
208
    }
209
210
    /**
211
     * @param IQueryPart|string ...$havingParts
212
     *
213
     * @return $this
214
     */
215 8
    public function having(IQueryPart|string ...$havingParts): static
216
    {
217 8
        foreach ($havingParts as $havingPart) {
218 8
            $havingPart = is_string($havingPart) ? new Expr($havingPart) : $havingPart;
219
220 8
            $this->havingParts[] = $havingPart;
221
        }
222
223 8
        return $this;
224
    }
225
226
    /**
227
     * @param string $column
228
     * @param string $direction
229
     *
230
     * @return $this
231
     */
232 5
    public function orderBy(string $column, string $direction = self::DIRECTION_ASC): static
233
    {
234 5
        $this->orderBy[$column] = $direction;
235
236 5
        return $this;
237
    }
238
239
    /**
240
     * @param int|null $offset
241
     *
242
     * @return $this
243
     */
244 5
    public function offset(?int $offset): static
245
    {
246 5
        $this->offset = $offset;
247
248 5
        return $this;
249
    }
250
251
    /**
252
     * @param int|null $limit
253
     *
254
     * @return $this
255
     */
256 6
    public function limit(?int $limit): static
257
    {
258 6
        $this->limit = $limit;
259
260 6
        return $this;
261
    }
262
263
    /**
264
     * @return string
265
     */
266 61
    public function __toString(): string
267
    {
268 61
        if (!$this->isValid()) {
269 3
            throw new RuntimeException('under-initialized SELECT query');
270
        }
271
272 58
        $select = $this->getSelect();
273
274 58
        if (count($this->tables) === 0) {
275 19
            return $select;
276
        }
277
278 39
        $parts = array_merge(
279 39
            [$select],
280 39
            $this->getFrom(),
281 39
            $this->getJoin(),
282 39
            $this->getWhere(),
283 39
            $this->getGroupBy(),
284 39
            $this->getHaving(),
285 39
            $this->getOrderBy(),
286 39
            $this->getLimit(),
287
        );
288
289 39
        $parts = array_filter($parts);
290
291 39
        return implode(PHP_EOL, $parts);
292
    }
293
294 61
    public function isValid(): bool
295
    {
296 61
        return count($this->columns) > 0 || count($this->tables) > 0;
297
    }
298
299 58
    protected function getSelect(): string
300
    {
301 58
        $sql   = [];
302 58
        $sql[] = 'SELECT';
303 58
        $sql[] = $this->getModifiers();
304
305 58
        $sql = array_filter($sql);
306
307 58
        $sql = implode(' ', $sql);
308
309 58
        return $sql . ' ' . $this->getColumns();
310
    }
311
312 58
    protected function getColumns(): string
313
    {
314 58
        if (empty($this->columns)) {
315 24
            return '*';
316
        }
317
318 34
        $parts = [];
319 34
        foreach ($this->columns as $column) {
320 34
            $parts[] = (string)$column;
321
        }
322
323 34
        return implode(', ', $parts);
324
    }
325
326 58
    protected function getModifiers(): string
327
    {
328 58
        if (empty($this->modifiers)) {
329 55
            return '';
330
        }
331
332 7
        return implode(' ', $this->modifiers);
333
    }
334
335 39
    protected function getFrom(): array
336
    {
337 39
        return ['FROM ' . implode(', ', $this->tables)];
338
    }
339
340
    /**
341
     * @return string[]
342
     */
343 39
    protected function getJoin(): array
344
    {
345 39
        if (count($this->joins) === 0) {
346 23
            return [];
347
        }
348
349 20
        $parts = [];
350 20
        foreach ($this->joins as $join) {
351 20
            $parts[] = (string)$join;
352
        }
353
354 20
        return $parts;
355
    }
356
357 39
    protected function getWhere(): array
358
    {
359 39
        if (count($this->whereParts) === 0) {
360 38
            return [];
361
        }
362
363 5
        $parts = [];
364 5
        foreach ($this->whereParts as $wherePart) {
365 5
            $parts[] = (string)$wherePart;
366
        }
367
368 5
        return ['WHERE ' . implode(' AND ', $parts)];
369
    }
370
371 39
    protected function getGroupBy(): array
372
    {
373 39
        if (count($this->groupByParts) === 0) {
374 38
            return [];
375
        }
376
377 5
        $parts = [];
378 5
        foreach ($this->groupByParts as $groupByPart) {
379 5
            $parts[] = (string)$groupByPart;
380
        }
381
382 5
        return ['GROUP BY ' . implode(', ', $parts)];
383
    }
384
385 39
    protected function getHaving(): array
386
    {
387 39
        if (count($this->havingParts) === 0) {
388 38
            return [];
389
        }
390
391 5
        $parts = [];
392 5
        foreach ($this->havingParts as $havingPart) {
393 5
            $parts[] = (string)$havingPart;
394
        }
395
396 5
        return ['HAVING ' . implode(' AND ', $parts)];
397
    }
398
399 39
    protected function getOrderBy(): array
400
    {
401 39
        if (count($this->orderBy) === 0) {
402 38
            return [];
403
        }
404
405 5
        $parts = [];
406 5
        foreach ($this->orderBy as $column => $direction) {
407 5
            $parts[] = "$column $direction";
408
        }
409
410 5
        return ['ORDER BY ' . implode(', ', $parts)];
411
    }
412
413 24
    protected function getLimit(): array
414
    {
415 24
        $parts = [];
416 24
        if ($this->offset !== null) {
417 4
            $parts[] = sprintf('OFFSET %d ROWS', $this->offset);
418
        }
419 24
        if ($this->limit !== null) {
420 4
            $parts[] = sprintf('FETCH FIRST %d ROWS ONLY', $this->limit);
421
        }
422
423 24
        return $parts;
424
    }
425
426
    /**
427
     * @return array
428
     */
429 6
    public function getParams(): array
430
    {
431 6
        $params = [];
432
433 6
        foreach ($this->columns as $column) {
434 6
            $params = array_merge($params, $column->getParams());
435
        }
436
437 6
        foreach ($this->joins as $join) {
438 3
            $params = array_merge($params, $join->getParams());
439
        }
440
441 6
        foreach ($this->whereParts as $wherePart) {
442 3
            $params = array_merge($params, $wherePart->getParams());
443
        }
444
445 6
        foreach ($this->groupByParts as $groupByPart) {
446 3
            $params = array_merge($params, $groupByPart->getParams());
447
        }
448
449 6
        foreach ($this->havingParts as $havingPart) {
450 3
            $params = array_merge($params, $havingPart->getParams());
451
        }
452
453 6
        return $params;
454
    }
455
}
456