Passed
Push — master ( 914087...c6011d )
by Alexander
11:22
created

QueryHelper::filterCondition()   D

Complexity

Conditions 19
Paths 18

Size

Total Lines 59
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 32
c 0
b 0
f 0
dl 0
loc 59
rs 4.5166
cc 19
nc 18
nop 1

How to fix   Long Method    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\Exception\InvalidArgumentException;
8
use Yiisoft\Db\Expression\Expression;
9
use Yiisoft\Db\Expression\ExpressionInterface;
10
use Yiisoft\Db\Query\QueryInterface;
11
use Yiisoft\Db\Schema\QuoterInterface;
12
13
use function array_key_exists;
14
use function array_shift;
15
use function array_unshift;
16
use function is_string;
17
use function preg_match;
18
use function preg_split;
19
use function str_contains;
20
use function strcasecmp;
21
use function strtoupper;
22
use function trim;
23
24
final class QueryHelper
25
{
26
    /**
27
     * Clean up table names and aliases.
28
     *
29
     * Both aliases and names are enclosed into {{ and }}.
30
     *
31
     * @param array $tableNames non-empty array
32
     * @param QuoterInterface $quoter The quoter used to quote table names and column names.
33
     *
34
     * @throws InvalidArgumentException
35
     *
36
     * @psalm-return array<array-key, ExpressionInterface|string> table names indexed by aliases
37
     */
38
    public function cleanUpTableNames(array $tableNames, QuoterInterface $quoter): array
39
    {
40
        $cleanedUpTableNames = [];
41
        $pattern = <<<PATTERN
42
        ~^\s*((?:['"`\[]|{{).*?(?:['"`\]]|}})|\(.*?\)|.*?)(?:(?:\s+(?:as)?\s*)((?:['"`\[]|{{).*?(?:['"`\]]|}})|.*?))?\s*$~iux
43
        PATTERN;
44
45
        /** @psalm-var array<array-key, Expression|string> $tableNames */
46
        foreach ($tableNames as $alias => $tableName) {
47
            if (is_string($tableName) && !is_string($alias)) {
48
                if (preg_match($pattern, $tableName, $matches)) {
49
                    if (isset($matches[2])) {
50
                        [, $tableName, $alias] = $matches;
51
                    } else {
52
                        $tableName = $alias = $matches[1];
53
                    }
54
                }
55
            }
56
57
            if (!is_string($alias)) {
58
                throw new InvalidArgumentException(
59
                    'To use Expression in from() method, pass it in array format with alias.'
60
                );
61
            }
62
63
            if ($tableName instanceof Expression) {
64
                $cleanedUpTableNames[$quoter->ensureNameQuoted($alias)] = $tableName;
65
            } elseif ($tableName instanceof QueryInterface) {
66
                $cleanedUpTableNames[$quoter->ensureNameQuoted($alias)] = $tableName;
67
            } else {
68
                $cleanedUpTableNames[$quoter->ensureNameQuoted($alias)] = $quoter->ensureNameQuoted(
69
                    (string) $tableName
70
                );
71
            }
72
        }
73
74
        return $cleanedUpTableNames;
75
    }
76
77
    /**
78
     * Removes {@see isEmpty()|empty operands} from the given query condition.
79
     *
80
     * @param array|string $condition the original condition
81
     *
82
     * @return array|string the condition with {@see isEmpty()|empty operands} removed.
83
     */
84
    public function filterCondition(array|string $condition): array|string
85
    {
86
        if (!is_array($condition)) {
0 ignored issues
show
introduced by
The condition is_array($condition) is always true.
Loading history...
87
            return $condition;
88
        }
89
90
        if (!isset($condition[0])) {
91
            /** hash format: 'column1' => 'value1', 'column2' => 'value2', ... */
92
            /** @var mixed $value */
93
            foreach ($condition as $name => $value) {
94
                if ($this->isEmpty($value)) {
95
                    unset($condition[$name]);
96
                }
97
            }
98
99
            return $condition;
100
        }
101
102
        /** operator format: operator, operand 1, operand 2, ... */
103
        /** @var string */
104
        $operator = array_shift($condition);
105
106
        switch (strtoupper($operator)) {
107
            case 'NOT':
108
            case 'AND':
109
            case 'OR':
110
                /** @psalm-var array<array-key, array|string> $condition */
111
                foreach ($condition as $i => $operand) {
112
                    $subCondition = $this->filterCondition($operand);
113
                    if ($this->isEmpty($subCondition)) {
114
                        unset($condition[$i]);
115
                    } else {
116
                        $condition[$i] = $subCondition;
117
                    }
118
                }
119
120
                if (empty($condition)) {
121
                    return [];
122
                }
123
124
                break;
125
            case 'BETWEEN':
126
            case 'NOT BETWEEN':
127
                if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
128
                    if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
129
                        return [];
130
                    }
131
                }
132
133
                break;
134
            default:
135
                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
136
                    return [];
137
                }
138
        }
139
140
        array_unshift($condition, $operator);
141
142
        return $condition;
143
    }
144
145
    /**
146
     * Returns a value indicating whether the give value is "empty".
147
     *
148
     * The value is considered "empty", if one of the following conditions is satisfied:
149
     *
150
     * - it is `null`,
151
     * - an empty string (`''`),
152
     * - a string containing only whitespace characters,
153
     * - or an empty array.
154
     *
155
     * @param mixed $value
156
     *
157
     * @return bool if the value is empty
158
     */
159
    public function isEmpty(mixed $value): bool
160
    {
161
        return $value === '' || $value === [] || $value === null || (is_string($value) && trim($value) === '');
162
    }
163
164
    /**
165
     * Normalizes format of ORDER BY data.
166
     *
167
     * @param array|ExpressionInterface|string $columns the columns value to normalize.
168
     *
169
     * See {@see orderBy} and {@see addOrderBy}.
170
     *
171
     * @return array
172
     */
173
    public function normalizeOrderBy(array|string|ExpressionInterface $columns): array
174
    {
175
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
176
            return [$columns];
177
        }
178
179
        if (is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
180
            return $columns;
181
        }
182
183
        $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
184
        $result = [];
185
186
        foreach ($columns as $column) {
187
            if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
188
                $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
189
            } else {
190
                $result[$column] = SORT_ASC;
191
            }
192
        }
193
194
        return $result;
195
    }
196
197
    /**
198
     * Normalizes the SELECT columns passed to {@see select()} or {@see addSelect()}.
199
     *
200
     * @param array|ExpressionInterface|string $columns
201
     *
202
     * @return array
203
     */
204
    public function normalizeSelect(array|ExpressionInterface|string $columns): array
205
    {
206
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
207
            $columns = [$columns];
208
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
209
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
210
        }
211
212
        $select = [];
213
214
        /** @psalm-var array<array-key, ExpressionInterface|string> $columns */
215
        foreach ($columns as $columnAlias => $columnDefinition) {
216
            if (is_string($columnAlias)) {
217
                // Already in the normalized format, good for them.
218
                $select[$columnAlias] = $columnDefinition;
219
                continue;
220
            }
221
222
            if (is_string($columnDefinition)) {
223
                if (
224
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_.]+)$/', $columnDefinition, $matches) &&
225
                    !preg_match('/^\d+$/', $matches[2]) &&
226
                    !str_contains($matches[2], '.')
227
                ) {
228
                    /** Using "columnName as alias" or "columnName alias" syntax */
229
                    $select[$matches[2]] = $matches[1];
230
                    continue;
231
                }
232
                if (!str_contains($columnDefinition, '(')) {
233
                    /** Normal column name, just alias it to itself to ensure it's not selected twice */
234
                    $select[$columnDefinition] = $columnDefinition;
235
                    continue;
236
                }
237
            }
238
239
            // Either a string calling a function, DB expression, or sub-query
240
            /** @var string */
241
            $select[] = $columnDefinition;
242
        }
243
244
        return $select;
245
    }
246
}
247