Select::offset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
ccs 3
cts 3
cp 1
crap 1
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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