Passed
Push — master ( e9fa3d...22a26f )
by William
03:07
created

CaseExpression   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 142
dl 0
loc 282
ccs 131
cts 131
cp 1
rs 7.44
c 0
b 0
f 0
wmc 52

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
B build() 0 33 8
F parse() 0 179 43

How to fix   Complexity   

Complex Class

Complex classes like CaseExpression 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 CaseExpression, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components;
6
7
use PhpMyAdmin\SqlParser\Component;
8
use PhpMyAdmin\SqlParser\Context;
9
use PhpMyAdmin\SqlParser\Parser;
10
use PhpMyAdmin\SqlParser\Token;
11
use PhpMyAdmin\SqlParser\TokensList;
12
13
use function count;
14
15
/**
16
 * Parses a reference to a CASE expression.
17
 *
18
 * @final
19
 */
20
class CaseExpression extends Component
21
{
22
    /**
23
     * The value to be compared.
24
     *
25
     * @var Expression|null
26
     */
27
    public $value;
28
29
    /**
30
     * The conditions in WHEN clauses.
31
     *
32
     * @var Condition[][]
33
     */
34
    public $conditions = [];
35
36
    /**
37
     * The results matching with the WHEN clauses.
38
     *
39
     * @var Expression[]
40
     */
41
    public $results = [];
42
43
    /**
44
     * The values to be compared against.
45
     *
46
     * @var Expression[]
47
     */
48
    public $compare_values = [];
49
50
    /**
51
     * The result in ELSE section of expr.
52
     *
53
     * @var Expression|null
54
     */
55
    public $else_result;
56
57
    /**
58
     * The alias of this CASE statement.
59
     *
60
     * @var string|null
61
     */
62
    public $alias;
63
64
    /**
65
     * The sub-expression.
66
     *
67
     * @var string
68
     */
69
    public $expr = '';
70
71 32
    public function __construct()
72
    {
73 32
    }
74
75
    /**
76
     * @param Parser               $parser  the parser that serves as context
77
     * @param TokensList           $list    the list of tokens that are being parsed
78
     * @param array<string, mixed> $options parameters for parsing
79
     *
80
     * @return CaseExpression
81
     */
82 124
    public static function parse(Parser $parser, TokensList $list, array $options = [])
83
    {
84 124
        $ret = new static();
85
86
        /**
87
         * State of parser.
88
         *
89
         * @var int
90
         */
91 124
        $state = 0;
92
93
        /**
94
         * Syntax type (type 0 or type 1).
95
         *
96
         * @var int
97
         */
98 124
        $type = 0;
99
100 124
        ++$list->idx; // Skip 'CASE'
101
102 124
        for (; $list->idx < $list->count; ++$list->idx) {
103
            /**
104
             * Token parsed at this moment.
105
             */
106 124
            $token = $list->tokens[$list->idx];
107
108
            // Skipping whitespaces and comments.
109 124
            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
110 124
                continue;
111
            }
112
113 124
            if ($state === 0) {
114 124
                if ($token->type === Token::TYPE_KEYWORD) {
115 92
                    switch ($token->keyword) {
116 92
                        case 'WHEN':
117 56
                            ++$list->idx; // Skip 'WHEN'
118 56
                            $newCondition = Condition::parse($parser, $list);
119 56
                            $type = 1;
120 56
                            $state = 1;
121 56
                            $ret->conditions[] = $newCondition;
122 56
                            break;
123 88
                        case 'ELSE':
124 28
                            ++$list->idx; // Skip 'ELSE'
125 28
                            $ret->else_result = Expression::parse($parser, $list);
126 28
                            $state = 0; // last clause of CASE expression
127 28
                            break;
128 88
                        case 'END':
129 84
                            $state = 3; // end of CASE expression
130 84
                            ++$list->idx;
131 84
                            break 2;
132
                        default:
133 4
                            $parser->error('Unexpected keyword.', $token);
134 60
                            break 2;
135
                    }
136
                } else {
137 64
                    $ret->value = Expression::parse($parser, $list);
138 64
                    $type = 0;
139 120
                    $state = 1;
140
                }
141 120
            } elseif ($state === 1) {
142 120
                if ($type === 0) {
143 64
                    if ($token->type === Token::TYPE_KEYWORD) {
144 64
                        switch ($token->keyword) {
145 64
                            case 'WHEN':
146 56
                                ++$list->idx; // Skip 'WHEN'
147 56
                                $newValue = Expression::parse($parser, $list);
148 56
                                $state = 2;
149 56
                                $ret->compare_values[] = $newValue;
150 56
                                break;
151 60
                            case 'ELSE':
152 32
                                ++$list->idx; // Skip 'ELSE'
153 32
                                $ret->else_result = Expression::parse($parser, $list);
154 32
                                $state = 0; // last clause of CASE expression
155 32
                                break;
156 28
                            case 'END':
157 20
                                $state = 3; // end of CASE expression
158 20
                                ++$list->idx;
159 20
                                break 2;
160
                            default:
161 8
                                $parser->error('Unexpected keyword.', $token);
162 64
                                break 2;
163
                        }
164
                    }
165
                } else {
166 56
                    if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'THEN') {
167 52
                        ++$list->idx; // Skip 'THEN'
168 52
                        $newResult = Expression::parse($parser, $list);
169 52
                        $state = 0;
170 52
                        $ret->results[] = $newResult;
171 4
                    } elseif ($token->type === Token::TYPE_KEYWORD) {
172 4
                        $parser->error('Unexpected keyword.', $token);
173 112
                        break;
174
                    }
175
                }
176 56
            } elseif ($state === 2) {
177 56
                if ($type === 0) {
178 56
                    if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'THEN') {
179 56
                        ++$list->idx; // Skip 'THEN'
180 56
                        $newResult = Expression::parse($parser, $list);
181 56
                        $ret->results[] = $newResult;
182 56
                        $state = 1;
183 4
                    } elseif ($token->type === Token::TYPE_KEYWORD) {
184 4
                        $parser->error('Unexpected keyword.', $token);
185 4
                        break;
186
                    }
187
                }
188
            }
189
        }
190
191 124
        if ($state !== 3) {
192 20
            $parser->error('Unexpected end of CASE expression', $list->tokens[$list->idx - 1]);
193
        } else {
194
            // Parse for alias of CASE expression
195 104
            $asFound = false;
196 104
            for (; $list->idx < $list->count; ++$list->idx) {
197 104
                $token = $list->tokens[$list->idx];
198
199
                // End of statement.
200 104
                if ($token->type === Token::TYPE_DELIMITER) {
201 40
                    break;
202
                }
203
204
                // Skipping whitespaces and comments.
205 76
                if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
206 76
                    continue;
207
                }
208
209
                // Handle optional AS keyword before alias
210 76
                if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'AS') {
211 36
                    if ($asFound || ! empty($ret->alias)) {
212 4
                        $parser->error('Potential duplicate alias of CASE expression.', $token);
213 4
                        break;
214
                    }
215
216 36
                    $asFound = true;
217 36
                    continue;
218
                }
219
220
                if (
221 68
                    $asFound
222 68
                    && $token->type === Token::TYPE_KEYWORD
223 68
                    && ($token->flags & Token::FLAG_KEYWORD_RESERVED || $token->flags & Token::FLAG_KEYWORD_FUNCTION)
224
                ) {
225 4
                    $parser->error('An alias expected after AS but got ' . $token->value, $token);
226 4
                    $asFound = false;
227 4
                    break;
228
                }
229
230
                if (
231 64
                    $asFound
232 60
                    || $token->type === Token::TYPE_STRING
233 60
                    || ($token->type === Token::TYPE_SYMBOL && ! $token->flags & Token::FLAG_SYMBOL_VARIABLE)
234 64
                    || $token->type === Token::TYPE_NONE
235
                ) {
236
                    // An alias is expected (the keyword `AS` was previously found).
237 36
                    if (! empty($ret->alias)) {
238 4
                        $parser->error('An alias was previously found.', $token);
239 4
                        break;
240
                    }
241
242 36
                    $ret->alias = $token->value;
243 36
                    $asFound = false;
244
245 36
                    continue;
246
                }
247
248 52
                break;
249
            }
250
251 104
            if ($asFound) {
252 8
                $parser->error('An alias was expected after AS.', $list->tokens[$list->idx - 1]);
253
            }
254
255 104
            $ret->expr = self::build($ret);
256
        }
257
258 124
        --$list->idx;
259
260 124
        return $ret;
261
    }
262
263
    /**
264
     * @param CaseExpression       $component the component to be built
265
     * @param array<string, mixed> $options   parameters for building
266
     *
267
     * @return string
268
     */
269 108
    public static function build($component, array $options = [])
270
    {
271 108
        $ret = 'CASE ';
272 108
        if (isset($component->value)) {
273
            // Syntax type 0
274 52
            $ret .= $component->value . ' ';
275 52
            $valuesCount = count($component->compare_values);
276 52
            $resultsCount = count($component->results);
277 52
            for ($i = 0; $i < $valuesCount && $i < $resultsCount; ++$i) {
278 52
                $ret .= 'WHEN ' . $component->compare_values[$i] . ' ';
279 52
                $ret .= 'THEN ' . $component->results[$i] . ' ';
280
            }
281
        } else {
282
            // Syntax type 1
283 56
            $valuesCount = count($component->conditions);
284 56
            $resultsCount = count($component->results);
285 56
            for ($i = 0; $i < $valuesCount && $i < $resultsCount; ++$i) {
286 52
                $ret .= 'WHEN ' . Condition::build($component->conditions[$i]) . ' ';
287 52
                $ret .= 'THEN ' . $component->results[$i] . ' ';
288
            }
289
        }
290
291 108
        if (isset($component->else_result)) {
292 60
            $ret .= 'ELSE ' . $component->else_result . ' ';
293
        }
294
295 108
        $ret .= 'END';
296
297 108
        if ($component->alias) {
298 36
            $ret .= ' AS ' . Context::escape($component->alias);
299
        }
300
301 108
        return $ret;
302
    }
303
}
304