Passed
Push — main ( 1ea679...486097 )
by Peter
02:43
created

Select   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 439
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 138
c 3
b 0
f 0
dl 0
loc 439
ccs 152
cts 152
cp 1
rs 3.6
wmc 60

28 Methods

Rating   Name   Duplication   Size   Complexity  
A addColumns() 0 18 4
A addColumn() 0 5 1
A addFrom() 0 5 1
A addModifier() 0 5 1
A addInnerJoin() 0 5 1
A addWhere() 0 9 3
A addOrderBy() 0 5 1
A isValid() 0 3 2
A setLimit() 0 5 1
A addLeftJoin() 0 5 1
A setOffset() 0 5 1
A __toString() 0 26 3
A addJoin() 0 5 1
A addRightJoin() 0 5 1
A addGroupBy() 0 9 3
A addHaving() 0 9 3
A addFullJoin() 0 5 1
A getParams() 0 25 6
A getLimit() 0 11 3
A getWhere() 0 12 3
A getJoin() 0 12 3
A getHaving() 0 12 3
A getModifiers() 0 7 2
A getColumns() 0 12 3
A getOrderBy() 0 12 3
A getFrom() 0 3 1
A getSelect() 0 11 1
A getGroupBy() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like Select often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Select, and based on these observations, apply Extract Interface, too.

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\Join;
11
use QB\Generic\Clause\Table;
12
use QB\Generic\Expr\Expr;
13
use QB\Generic\IQueryPart;
14
15
/**
16
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
17
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
18
 * SuppressWarnings("complexity")
19
 */
20
class Select implements ISelect
21
{
22
    public const ALL      = 'ALL';
23
    public const DISTINCT = 'DISTINCT';
24
25
    /** @var array<int,string|Table> */
26
    protected array $tables = [];
27
28
    /** @var string[] */
29
    protected array $modifiers = [];
30
31
    /** @var IColumn[] */
32
    protected array $columns = [];
33
34
    /** @var IJoin[] */
35
    protected array $joins = [];
36
37
    /** @var IQueryPart[] */
38
    protected array $whereParts = [];
39
40
    /** @var IQueryPart[] */
41
    protected array $groupByParts = [];
42
43
    /** @var IQueryPart[] */
44
    protected array $havingParts = [];
45
46
    /** @var array<string,string> */
47
    protected array $orderByParts = [];
48
49
    protected ?int $offset = null;
50
51
    protected ?int $limit = null;
52
53
    /**
54
     * @param string|Table ...$tables
55
     *
56
     * @return $this
57
     */
58 50
    public function addFrom(string|Table ...$tables): static
59
    {
60 50
        $this->tables = array_merge($this->tables, $tables);
61
62 50
        return $this;
63
    }
64
65
    /**
66
     * @param string ...$modifiers
67
     *
68
     * @return $this
69
     */
70 6
    public function addModifier(string ...$modifiers): static
71
    {
72 6
        $this->modifiers = array_merge($this->modifiers, $modifiers);
73
74 6
        return $this;
75
    }
76
77
    /**
78
     * @param string|IQueryPart $column
79
     * @param string|null       $alias
80
     *
81
     * @return $this
82
     */
83 14
    public function addColumn(string|IQueryPart $column, ?string $alias = null): static
84
    {
85 14
        $this->columns[] = new Column($column, $alias);
86
87 14
        return $this;
88
    }
89
90
    /**
91
     * @param string|IColumn ...$columns
92
     *
93
     * @return $this
94
     */
95 14
    public function addColumns(string|IColumn ...$columns): static
96
    {
97 14
        foreach ($columns as $column) {
98 14
            if ($column instanceof IColumn) {
99 7
                $this->columns[] = $column;
100 7
                continue;
101
            }
102
103 11
            if (strpos($column, ' AS ')) {
104 6
                $parts = explode(' AS ', $column);
105
106 6
                $this->columns[] = new Column($parts[0], $parts[1]);
107
            } else {
108 11
                $this->columns[] = new Column($column, null);
109
            }
110
        }
111
112 14
        return $this;
113
    }
114
115
    /**
116
     * @param string            $table
117
     * @param string|IQueryPart $on
118
     * @param string|null       $alias
119
     *
120
     * @return $this
121
     */
122 9
    public function addInnerJoin(string $table, string|IQueryPart $on, ?string $alias = null): static
123
    {
124 9
        $this->joins[] = new Join(IJoin::TYPE_INNER_JOIN, $table, $on, $alias);
125
126 9
        return $this;
127
    }
128
129
    /**
130
     * @param string            $table
131
     * @param string|IQueryPart $on
132
     * @param string|null       $alias
133
     *
134
     * @return $this
135
     */
136 6
    public function addLeftJoin(string $table, string|IQueryPart $on, ?string $alias = null): static
137
    {
138 6
        $this->joins[] = new Join(IJoin::TYPE_LEFT_JOIN, $table, $on, $alias);
139
140 6
        return $this;
141
    }
142
143
    /**
144
     * @param string            $table
145
     * @param string|IQueryPart $on
146
     * @param string|null       $alias
147
     *
148
     * @return $this
149
     */
150 3
    public function addRightJoin(string $table, string|IQueryPart $on, ?string $alias = null): static
151
    {
152 3
        $this->joins[] = new Join(IJoin::TYPE_RIGHT_JOIN, $table, $on, $alias);
153
154 3
        return $this;
155
    }
156
157
    /**
158
     * @param string            $table
159
     * @param string|IQueryPart $on
160
     * @param string|null       $alias
161
     *
162
     * @return $this
163
     */
164 3
    public function addFullJoin(string $table, string|IQueryPart $on, ?string $alias = null): static
165
    {
166 3
        $this->joins[] = new Join(IJoin::TYPE_FULL_JOIN, $table, $on, $alias);
167
168 3
        return $this;
169
    }
170
171
    /**
172
     * @param IJoin ...$joins
173
     *
174
     * @return $this
175
     */
176 3
    public function addJoin(IJoin ...$joins): static
177
    {
178 3
        $this->joins = array_merge($this->joins, $joins);
179
180 3
        return $this;
181
    }
182
183
    /**
184
     * @param string|IQueryPart ...$whereParts
185
     *
186
     * @return $this
187
     */
188 9
    public function addWhere(string|IQueryPart ...$whereParts): static
189
    {
190 9
        foreach ($whereParts as $wherePart) {
191 9
            $wherePart = is_string($wherePart) ? new Expr($wherePart) : $wherePart;
192
193 9
            $this->whereParts[] = $wherePart;
194
        }
195
196 9
        return $this;
197
    }
198
199
    /**
200
     * @param string|IQueryPart ...$groupByParts
201
     *
202
     * @return $this
203
     */
204 9
    public function addGroupBy(string|IQueryPart ...$groupByParts): static
205
    {
206 9
        foreach ($groupByParts as $groupByPart) {
207 9
            $groupByPart = is_string($groupByPart) ? new Expr($groupByPart) : $groupByPart;
208
209 9
            $this->groupByParts[] = $groupByPart;
210
        }
211
212 9
        return $this;
213
    }
214
215
    /**
216
     * @param string|IQueryPart ...$havingParts
217
     *
218
     * @return $this
219
     */
220 9
    public function addHaving(string|IQueryPart ...$havingParts): static
221
    {
222 9
        foreach ($havingParts as $havingPart) {
223 9
            $havingPart = is_string($havingPart) ? new Expr($havingPart) : $havingPart;
224
225 9
            $this->havingParts[] = $havingPart;
226
        }
227
228 9
        return $this;
229
    }
230
231
    /**
232
     * @param string $column
233
     * @param string $direction
234
     *
235
     * @return $this
236
     */
237 6
    public function addOrderBy(string $column, string $direction = 'ASC'): static
238
    {
239 6
        $this->orderByParts[$column] = $direction;
240
241 6
        return $this;
242
    }
243
244
    /**
245
     * @param int|null $offset
246
     *
247
     * @return $this
248
     */
249 6
    public function setOffset(?int $offset): static
250
    {
251 6
        $this->offset = $offset;
252
253 6
        return $this;
254
    }
255
256
    /**
257
     * @param int|null $limit
258
     *
259
     * @return $this
260
     */
261 6
    public function setLimit(?int $limit): static
262
    {
263 6
        $this->limit = $limit;
264
265 6
        return $this;
266
    }
267
268
    /**
269
     * @return string
270
     */
271 50
    public function __toString(): string
272
    {
273 50
        if (!$this->isValid()) {
274 3
            throw new \RuntimeException('under-initialized SELECT query');
275
        }
276
277 47
        $select = $this->getSelect();
278
279 47
        if (count($this->tables) === 0) {
280 7
            return $select;
281
        }
282
283 40
        $parts = array_merge(
284 40
            [$select],
285 40
            $this->getFrom(),
286 40
            $this->getJoin(),
287 40
            $this->getWhere(),
288 40
            $this->getGroupBy(),
289 40
            $this->getHaving(),
290 40
            $this->getOrderBy(),
291 40
            $this->getLimit(),
292
        );
293
294 40
        $parts = array_filter($parts);
295
296 40
        return implode(PHP_EOL, $parts);
297
    }
298
299 50
    public function isValid(): bool
300
    {
301 50
        return count($this->columns) > 0 || count($this->tables) > 0;
302
    }
303
304 47
    protected function getSelect(): string
305
    {
306 47
        $sql   = [];
307 47
        $sql[] = 'SELECT';
308 47
        $sql[] = $this->getModifiers();
309
310 47
        $sql = array_filter($sql);
311
312 47
        $sql = implode(' ', $sql);
313
314 47
        return $sql . ' ' . $this->getColumns();
315
    }
316
317 47
    protected function getColumns(): string
318
    {
319 47
        if (empty($this->columns)) {
320 26
            return '*';
321
        }
322
323 21
        $parts = [];
324 21
        foreach ($this->columns as $column) {
325 21
            $parts[] = (string)$column;
326
        }
327
328 21
        return implode(', ', $parts);
329
    }
330
331 47
    protected function getModifiers(): string
332
    {
333 47
        if (empty($this->modifiers)) {
334 43
            return '';
335
        }
336
337 8
        return implode(' ', $this->modifiers);
338
    }
339
340 40
    protected function getFrom(): array
341
    {
342 40
        return ['FROM ' . implode(', ', $this->tables)];
343
    }
344
345
    /**
346
     * @return string[]
347
     */
348 40
    protected function getJoin(): array
349
    {
350 40
        if (count($this->joins) === 0) {
351 23
            return [];
352
        }
353
354 21
        $parts = [];
355 21
        foreach ($this->joins as $join) {
356 21
            $parts[] = (string)$join;
357
        }
358
359 21
        return $parts;
360
    }
361
362 40
    protected function getWhere(): array
363
    {
364 40
        if (count($this->whereParts) === 0) {
365 37
            return [];
366
        }
367
368 7
        $parts = [];
369 7
        foreach ($this->whereParts as $wherePart) {
370 7
            $parts[] = (string)$wherePart;
371
        }
372
373 7
        return ['WHERE ' . implode(' AND ', $parts)];
374
    }
375
376 40
    protected function getGroupBy(): array
377
    {
378 40
        if (count($this->groupByParts) === 0) {
379 38
            return [];
380
        }
381
382 6
        $parts = [];
383 6
        foreach ($this->groupByParts as $groupByPart) {
384 6
            $parts[] = (string)$groupByPart;
385
        }
386
387 6
        return ['GROUP BY ' . implode(', ', $parts)];
388
    }
389
390 40
    protected function getHaving(): array
391
    {
392 40
        if (count($this->havingParts) === 0) {
393 38
            return [];
394
        }
395
396 6
        $parts = [];
397 6
        foreach ($this->havingParts as $havingPart) {
398 6
            $parts[] = (string)$havingPart;
399
        }
400
401 6
        return ['HAVING ' . implode(' AND ', $parts)];
402
    }
403
404 40
    protected function getOrderBy(): array
405
    {
406 40
        if (count($this->orderByParts) === 0) {
407 38
            return [];
408
        }
409
410 6
        $parts = [];
411 6
        foreach ($this->orderByParts as $column => $direction) {
412 6
            $parts[] = "$column $direction";
413
        }
414
415 6
        return ['ORDER BY ' . implode(', ', $parts)];
416
    }
417
418 27
    protected function getLimit(): array
419
    {
420 27
        $parts = [];
421 27
        if ($this->offset !== null) {
422 5
            $parts[] = sprintf('OFFSET %d ROWS', $this->offset);
423
        }
424 27
        if ($this->limit !== null) {
425 5
            $parts[] = sprintf('FETCH FIRST %d ROWS ONLY', $this->limit);
426
        }
427
428 27
        return $parts;
429
    }
430
431
    /**
432
     * @return array
433
     */
434 5
    public function getParams(): array
435
    {
436 5
        $params = [];
437
438 5
        foreach ($this->columns as $column) {
439 3
            $params = array_merge($params, $column->getParams());
440
        }
441
442 5
        foreach ($this->joins as $join) {
443 3
            $params = array_merge($params, $join->getParams());
444
        }
445
446 5
        foreach ($this->whereParts as $wherePart) {
447 4
            $params = array_merge($params, $wherePart->getParams());
448
        }
449
450 5
        foreach ($this->groupByParts as $groupByPart) {
451 3
            $params = array_merge($params, $groupByPart->getParams());
452
        }
453
454 5
        foreach ($this->havingParts as $havingPart) {
455 3
            $params = array_merge($params, $havingPart->getParams());
456
        }
457
458 5
        return $params;
459
    }
460
}
461