Passed
Push — main ( 8a18ed...1ea679 )
by Peter
02:40
created

Select   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Test Coverage

Coverage 99.35%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 140
dl 0
loc 443
ccs 153
cts 154
cp 0.9935
rs 3.52
c 2
b 0
f 0
wmc 61

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 getParams() 0 25 6
A addWhere() 0 9 3
A addOrderBy() 0 5 1
A isValid() 0 3 2
A setLimit() 0 5 1
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 addLeftJoin() 0 5 1
A getColumns() 0 12 3
A getOrderBy() 0 12 3
A getFrom() 0 3 1
A setOffset() 0 5 1
A __toString() 0 26 3
A getSelect() 0 15 2
A addJoin() 0 5 1
A addRightJoin() 0 5 1
A addGroupBy() 0 9 3
A addHaving() 0 9 3
A getGroupBy() 0 12 3
A addFullJoin() 0 5 1

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
        if (count($this->columns) == 0) {
315 26
            return $sql . ' *';
316
        }
317
318 21
        return $sql . ' ' . $this->getColumns();
319
    }
320
321 21
    protected function getColumns(): string
322
    {
323 21
        if (empty($this->columns)) {
324
            return '';
325
        }
326
327 21
        $parts = [];
328 21
        foreach ($this->columns as $column) {
329 21
            $parts[] = (string)$column;
330
        }
331
332 21
        return implode(', ', $parts);
333
    }
334
335 47
    protected function getModifiers(): string
336
    {
337 47
        if (empty($this->modifiers)) {
338 43
            return '';
339
        }
340
341 8
        return implode(' ', $this->modifiers);
342
    }
343
344 40
    protected function getFrom(): array
345
    {
346 40
        return ['FROM ' . implode(', ', $this->tables)];
347
    }
348
349
    /**
350
     * @return string[]
351
     */
352 40
    protected function getJoin(): array
353
    {
354 40
        if (count($this->joins) === 0) {
355 23
            return [];
356
        }
357
358 21
        $parts = [];
359 21
        foreach ($this->joins as $join) {
360 21
            $parts[] = (string)$join;
361
        }
362
363 21
        return $parts;
364
    }
365
366 40
    protected function getWhere(): array
367
    {
368 40
        if (count($this->whereParts) === 0) {
369 37
            return [];
370
        }
371
372 7
        $parts = [];
373 7
        foreach ($this->whereParts as $wherePart) {
374 7
            $parts[] = (string)$wherePart;
375
        }
376
377 7
        return ['WHERE ' . implode(' AND ', $parts)];
378
    }
379
380 40
    protected function getGroupBy(): array
381
    {
382 40
        if (count($this->groupByParts) === 0) {
383 38
            return [];
384
        }
385
386 6
        $parts = [];
387 6
        foreach ($this->groupByParts as $groupByPart) {
388 6
            $parts[] = (string)$groupByPart;
389
        }
390
391 6
        return ['GROUP BY ' . implode(', ', $parts)];
392
    }
393
394 40
    protected function getHaving(): array
395
    {
396 40
        if (count($this->havingParts) === 0) {
397 38
            return [];
398
        }
399
400 6
        $parts = [];
401 6
        foreach ($this->havingParts as $havingPart) {
402 6
            $parts[] = (string)$havingPart;
403
        }
404
405 6
        return ['HAVING ' . implode(' AND ', $parts)];
406
    }
407
408 40
    protected function getOrderBy(): array
409
    {
410 40
        if (count($this->orderByParts) === 0) {
411 38
            return [];
412
        }
413
414 6
        $parts = [];
415 6
        foreach ($this->orderByParts as $column => $direction) {
416 6
            $parts[] = "$column $direction";
417
        }
418
419 6
        return ['ORDER BY ' . implode(', ', $parts)];
420
    }
421
422 27
    protected function getLimit(): array
423
    {
424 27
        $parts = [];
425 27
        if ($this->offset !== null) {
426 5
            $parts[] = sprintf('OFFSET %d ROWS', $this->offset);
427
        }
428 27
        if ($this->limit !== null) {
429 5
            $parts[] = sprintf('FETCH FIRST %d ROWS ONLY', $this->limit);
430
        }
431
432 27
        return $parts;
433
    }
434
435
    /**
436
     * @return array
437
     */
438 5
    public function getParams(): array
439
    {
440 5
        $params = [];
441
442 5
        foreach ($this->columns as $column) {
443 3
            $params = array_merge($params, $column->getParams());
444
        }
445
446 5
        foreach ($this->joins as $join) {
447 3
            $params = array_merge($params, $join->getParams());
448
        }
449
450 5
        foreach ($this->whereParts as $wherePart) {
451 4
            $params = array_merge($params, $wherePart->getParams());
452
        }
453
454 5
        foreach ($this->groupByParts as $groupByPart) {
455 3
            $params = array_merge($params, $groupByPart->getParams());
456
        }
457
458 5
        foreach ($this->havingParts as $havingPart) {
459 3
            $params = array_merge($params, $havingPart->getParams());
460
        }
461
462 5
        return $params;
463
    }
464
}
465