Passed
Push — master ( b16987...8b6d77 )
by Maurício
03:49 queued 13s
created

OptionsArrays::parse()   D

Complexity

Conditions 38
Paths 12

Size

Total Lines 216
Code Lines 95

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 99
CRAP Score 38

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 38
eloc 95
nc 12
nop 3
dl 0
loc 216
ccs 99
cts 99
cp 1
crap 38
rs 4.1666
c 1
b 0
f 0

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 PhpMyAdmin\SqlParser\Parsers;
6
7
use PhpMyAdmin\SqlParser\Components\OptionsArray;
8
use PhpMyAdmin\SqlParser\Parseable;
9
use PhpMyAdmin\SqlParser\Parser;
10
use PhpMyAdmin\SqlParser\TokensList;
11
use PhpMyAdmin\SqlParser\TokenType;
12
use PhpMyAdmin\SqlParser\Translator;
13
14
use function count;
15
use function is_array;
16
use function ksort;
17
use function sprintf;
18
use function strtoupper;
19
20
/**
21
 * Parses a list of options.
22
 */
23
final class OptionsArrays implements Parseable
24
{
25
    /**
26
     * @param Parser               $parser  the parser that serves as context
27
     * @param TokensList           $list    the list of tokens that are being parsed
28
     * @param array<string, mixed> $options parameters for parsing
29
     */
30 1098
    public static function parse(Parser $parser, TokensList $list, array $options = []): OptionsArray
31
    {
32 1098
        $ret = new OptionsArray();
33
34
        /**
35
         * The ID that will be assigned to duplicate options.
36
         */
37 1098
        $lastAssignedId = count($options) + 1;
38
39
        /**
40
         * The option that was processed last time.
41
         */
42 1098
        $lastOption = null;
43
44
        /**
45
         * The index of the option that was processed last time.
46
         */
47 1098
        $lastOptionId = 0;
48
49
        /**
50
         * Counts brackets.
51
         */
52 1098
        $brackets = 0;
53
54
        /**
55
         * The state of the parser.
56
         *
57
         * Below are the states of the parser.
58
         *
59
         *      0 ---------------------[ option ]----------------------> 1
60
         *
61
         *      1 -------------------[ = (optional) ]------------------> 2
62
         *
63
         *      2 ----------------------[ value ]----------------------> 0
64
         */
65 1098
        $state = 0;
66
67 1098
        for (; $list->idx < $list->count; ++$list->idx) {
68
            /**
69
             * Token parsed at this moment.
70
             */
71 1098
            $token = $list->tokens[$list->idx];
72
73
            // End of statement.
74 1098
            if ($token->type === TokenType::Delimiter) {
75 284
                break;
76
            }
77
78
            // Skipping comments.
79 1092
            if ($token->type === TokenType::Comment) {
80 26
                continue;
81
            }
82
83
            // Skipping whitespace if not parsing value.
84 1092
            if (($token->type === TokenType::Whitespace) && ($brackets === 0)) {
85 1044
                continue;
86
            }
87
88 1092
            if ($lastOption === null) {
89 1092
                $upper = strtoupper($token->token);
90 1092
                if (! isset($options[$upper])) {
91
                    // There is no option to be processed.
92 1052
                    break;
93
                }
94
95 642
                $lastOption = $options[$upper];
96 642
                $lastOptionId = is_array($lastOption) ?
97 642
                    $lastOption[0] : $lastOption;
98 642
                $state = 0;
99
100
                // Checking for option conflicts.
101
                // For example, in `SELECT` statements the keywords `ALL`
102
                // and `DISTINCT` conflict and if used together, they
103
                // produce an invalid query.
104
                //
105
                // Usually, tokens can be identified in the array by the
106
                // option ID, but if conflicts occur, a generated option ID
107
                // is used.
108
                //
109
                // The first pseudo duplicate ID is the maximum value of the
110
                // real options (e.g.  if there are 5 options, the first
111
                // fake ID is 6).
112 642
                if (isset($ret->options[$lastOptionId])) {
113 16
                    $parser->error(
114 16
                        sprintf(
115 16
                            Translator::gettext('This option conflicts with "%1$s".'),
116 16
                            is_array($ret->options[$lastOptionId])
117 10
                            ? $ret->options[$lastOptionId]['name']
118 16
                            : $ret->options[$lastOptionId],
119 16
                        ),
120 16
                        $token,
121 16
                    );
122 16
                    $lastOptionId = $lastAssignedId++;
123
                }
124
            }
125
126 642
            if ($state === 0) {
127 642
                if (! is_array($lastOption)) {
128
                    // This is a just keyword option without any value.
129
                    // This is the beginning and the end of it.
130 582
                    $ret->options[$lastOptionId] = $token->value;
131 582
                    $lastOption = null;
132 582
                    $state = 0;
133 284
                } elseif (($lastOption[1] === 'var') || ($lastOption[1] === 'var=')) {
134
                    // This is a keyword that is followed by a value.
135
                    // This is only the beginning. The value is parsed in state
136
                    // 1 and 2. State 1 is used to skip the first equals sign
137
                    // and state 2 to parse the actual value.
138 212
                    $ret->options[$lastOptionId] = [
139
                        // @var string The name of the option.
140 212
                        'name' => $token->value,
141
                        // @var bool Whether it contains an equal sign.
142
                        //           This is used by the builder to rebuild it.
143 212
                        'equals' => $lastOption[1] === 'var=',
144
                        // @var string Raw value.
145 212
                        'expr' => '',
146
                        // @var string Processed value.
147 212
                        'value' => '',
148 212
                    ];
149 212
                    $state = 1;
150 142
                } elseif ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
151
                    // This is a keyword that is followed by an expression.
152
                    // The expression is used by the specialized parser.
153
154
                    // Skipping this option in order to parse the expression.
155 142
                    ++$list->idx;
156 142
                    $ret->options[$lastOptionId] = [
157
                        // @var string The name of the option.
158 142
                        'name' => $token->value,
159
                        // @var bool Whether it contains an equal sign.
160
                        //           This is used by the builder to rebuild it.
161 142
                        'equals' => $lastOption[1] === 'expr=',
162
                        // @var Expression The parsed expression.
163 142
                        'expr' => '',
164 142
                    ];
165 142
                    $state = 1;
166
                }
167 276
            } elseif ($state === 1) {
168 276
                $state = 2;
169 276
                if ($token->token === '=') {
170 114
                    $ret->options[$lastOptionId]['equals'] = true;
171 114
                    continue;
172
                }
173
            }
174
175
            // This is outside the `elseif` group above because the change might
176
            // change this iteration.
177 642
            if ($state !== 2) {
178 642
                continue;
179
            }
180
181 276
            if ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
182 142
                $ret->options[$lastOptionId]['expr'] = Expressions::parse(
183 142
                    $parser,
184 142
                    $list,
185 142
                    empty($lastOption[2]) ? [] : $lastOption[2],
186 142
                );
187 142
                if ($ret->options[$lastOptionId]['expr'] !== null) {
188 142
                    $ret->options[$lastOptionId]['value']
189 142
                        = $ret->options[$lastOptionId]['expr']->expr;
190
                }
191
192 142
                $lastOption = null;
193 142
                $state = 0;
194
            } else {
195 204
                if ($token->token === '(') {
196 6
                    ++$brackets;
197 204
                } elseif ($token->token === ')') {
198 6
                    --$brackets;
199
                }
200
201 204
                $ret->options[$lastOptionId]['expr'] .= $token->token;
202
203
                if (
204 204
                    ! (($token->token === '(') && ($brackets === 1)
205 204
                    || (($token->token === ')') && ($brackets === 0)))
206
                ) {
207
                    // First pair of brackets is being skipped.
208 204
                    $ret->options[$lastOptionId]['value'] .= $token->value;
209
                }
210
211
                // Checking if we finished parsing.
212 204
                if ($brackets === 0) {
213 204
                    $lastOption = null;
214
                }
215
            }
216
        }
217
218
        /*
219
         * We reached the end of statement without getting a value
220
         * for an option for which a value was required
221
         */
222
        if (
223 1098
            $state === 1
0 ignored issues
show
introduced by
The condition $state === 1 is always false.
Loading history...
224
            && $lastOption
225 1098
            && ($lastOption[1] === 'expr'
226 1098
            || $lastOption[1] === 'var'
227 1098
            || $lastOption[1] === 'var='
228 1098
            || $lastOption[1] === 'expr=')
229
        ) {
230 8
            $parser->error(
231 8
                sprintf(
232 8
                    'Value/Expression for the option %1$s was expected.',
233 8
                    $ret->options[$lastOptionId]['name'],
234 8
                ),
235 8
                $list->tokens[$list->idx - 1],
236 8
            );
237
        }
238
239 1098
        if (empty($options['_UNSORTED'])) {
240 1098
            ksort($ret->options);
241
        }
242
243 1098
        --$list->idx;
244
245 1098
        return $ret;
246
    }
247
}
248