Passed
Pull Request — master (#511)
by Def
02:24
created

QueryHelper::normalizeSelect()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
c 0
b 0
f 0
dl 0
loc 41
rs 7.6666
cc 10
nc 18
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Query\Helper;
6
7
use Yiisoft\Db\Expression\ExpressionInterface;
8
9
use function array_key_exists;
10
use function array_shift;
11
use function array_unshift;
12
use function is_string;
13
use function preg_match;
14
use function preg_split;
15
use function str_contains;
16
use function strcasecmp;
17
use function strtoupper;
18
use function trim;
19
20
/**
21
 * The QueryHelper provides a set of helper methods for working with database queries. These methods can be used to
22
 * simplify the process of building and executing complex database queries.
23
 */
24
final class QueryHelper
25
{
26
    /**
27
     * Removes {@see isEmpty()|empty operands} from the given query condition.
28
     *
29
     * @param array|string $condition the original condition
30
     *
31
     * @return array|string the condition with {@see isEmpty()|empty operands} removed.
32
     */
33
    public function filterCondition(array|string $condition): array|string
34
    {
35
        if (!is_array($condition)) {
0 ignored issues
show
introduced by
The condition is_array($condition) is always true.
Loading history...
36
            return $condition;
37
        }
38
39
        if (!isset($condition[0])) {
40
            /** hash format: 'column1' => 'value1', 'column2' => 'value2', ... */
41
            /** @var mixed $value */
42
            foreach ($condition as $name => $value) {
43
                if ($this->isEmpty($value)) {
44
                    unset($condition[$name]);
45
                }
46
            }
47
48
            return $condition;
49
        }
50
51
        /** operator format: operator, operand 1, operand 2, ... */
52
        /** @var string */
53
        $operator = array_shift($condition);
54
55
        switch (strtoupper($operator)) {
56
            case 'NOT':
57
            case 'AND':
58
            case 'OR':
59
                /** @psalm-var array<array-key, array|string> $condition */
60
                foreach ($condition as $i => $operand) {
61
                    $subCondition = $this->filterCondition($operand);
62
                    if ($this->isEmpty($subCondition)) {
63
                        unset($condition[$i]);
64
                    } else {
65
                        $condition[$i] = $subCondition;
66
                    }
67
                }
68
69
                if (empty($condition)) {
70
                    return [];
71
                }
72
73
                break;
74
            case 'BETWEEN':
75
            case 'NOT BETWEEN':
76
                if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
77
                    if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
78
                        return [];
79
                    }
80
                } else {
81
                    return [];
82
                }
83
84
                break;
85
            default:
86
                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
87
                    return [];
88
                }
89
        }
90
91
        array_unshift($condition, $operator);
92
93
        return $condition;
94
    }
95
96
    /**
97
     * Returns a value indicating whether the give value is "empty".
98
     *
99
     * The value is considered "empty", if one of the following conditions is satisfied:
100
     *
101
     * - it is `null`,
102
     * - an empty string (`''`),
103
     * - a string containing only whitespace characters,
104
     * - or an empty array.
105
     *
106
     * @param mixed $value
107
     *
108
     * @return bool if the value is empty
109
     */
110
    public function isEmpty(mixed $value): bool
111
    {
112
        return $value === '' || $value === [] || $value === null || (is_string($value) && trim($value) === '');
113
    }
114
115
    /**
116
     * Normalizes format of ORDER BY data.
117
     *
118
     * @param array|ExpressionInterface|string $columns the columns value to normalize.
119
     *
120
     * See {@see orderBy} and {@see addOrderBy}.
121
     */
122
    public function normalizeOrderBy(array|string|ExpressionInterface $columns): array
123
    {
124
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
125
            return [$columns];
126
        }
127
128
        if (is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
129
            return $columns;
130
        }
131
132
        $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
133
        $result = [];
134
135
        foreach ($columns as $column) {
136
            if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
137
                $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
138
            } else {
139
                $result[$column] = SORT_ASC;
140
            }
141
        }
142
143
        return $result;
144
    }
145
146
    /**
147
     * Normalizes the SELECT columns passed to {@see select()} or {@see addSelect()}.
148
     */
149
    public function normalizeSelect(array|ExpressionInterface|string $columns): array
150
    {
151
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
152
            $columns = [$columns];
153
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
154
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
155
        }
156
157
        $select = [];
158
159
        /** @psalm-var array<array-key, ExpressionInterface|string> $columns */
160
        foreach ($columns as $columnAlias => $columnDefinition) {
161
            if (is_string($columnAlias)) {
162
                // Already in the normalized format, good for them.
163
                $select[$columnAlias] = $columnDefinition;
164
                continue;
165
            }
166
167
            if (is_string($columnDefinition)) {
168
                if (
169
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_.]+)$/', $columnDefinition, $matches) &&
170
                    !preg_match('/^\d+$/', $matches[2]) &&
171
                    !str_contains($matches[2], '.')
172
                ) {
173
                    /** Using "columnName as alias" or "columnName alias" syntax */
174
                    $select[$matches[2]] = $matches[1];
175
                    continue;
176
                }
177
                if (!str_contains($columnDefinition, '(')) {
178
                    /** Normal column name, just alias it to itself to ensure it's not selected twice */
179
                    $select[$columnDefinition] = $columnDefinition;
180
                    continue;
181
                }
182
            }
183
184
            // Either a string calling a function, DB expression, or sub-query
185
            /** @var string */
186
            $select[] = $columnDefinition;
187
        }
188
189
        return $select;
190
    }
191
}
192