Completed
Push — master ( 65f66e...428edc )
by Michal
04:14
created

OptionsArray::parse()   D

Complexity

Conditions 37
Paths 12

Size

Total Lines 222
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 108
CRAP Score 37.001

Importance

Changes 0
Metric Value
cc 37
eloc 93
nc 12
nop 3
dl 0
loc 222
ccs 108
cts 109
cp 0.9908
crap 37.001
rs 4.2495
c 0
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
/**
4
 * Parses a list of options.
5
 */
6
7
namespace PhpMyAdmin\SqlParser\Components;
8
9
use PhpMyAdmin\SqlParser\Component;
10
use PhpMyAdmin\SqlParser\Parser;
11
use PhpMyAdmin\SqlParser\Token;
12
use PhpMyAdmin\SqlParser\TokensList;
13
14
/**
15
 * Parses a list of options.
16
 *
17
 * @category   Components
18
 *
19
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20
 */
21
class OptionsArray extends Component
22
{
23
    /**
24
     * ArrayObj of selected options.
25
     *
26
     * @var array
27
     */
28
    public $options = array();
29
30
    /**
31
     * Constructor.
32
     *
33
     * @param array $options The array of options. Options that have a value
34
     *                       must be an array with at least two keys `name` and
35
     *                       `expr` or `value`.
36
     */
37 253
    public function __construct(array $options = array())
38
    {
39 253
        $this->options = $options;
40 253
    }
41
42
    /**
43
     * @param Parser     $parser  the parser that serves as context
44
     * @param TokensList $list    the list of tokens that are being parsed
45
     * @param array      $options parameters for parsing
46
     *
47
     * @return OptionsArray
48
     */
49 249
    public static function parse(Parser $parser, TokensList $list, array $options = array())
50
    {
51 249
        $ret = new self();
52
53
        /**
54
         * The ID that will be assigned to duplicate options.
55
         *
56
         * @var int
57
         */
58 249
        $lastAssignedId = count($options) + 1;
59
60
        /**
61
         * The option that was processed last time.
62
         *
63
         * @var array
64
         */
65 249
        $lastOption = null;
66
67
        /**
68
         * The index of the option that was processed last time.
69
         *
70
         * @var int
71
         */
72 249
        $lastOptionId = 0;
73
74
        /**
75
         * Counts brackets.
76
         *
77
         * @var int
78
         */
79 249
        $brackets = 0;
80
81
        /**
82
         * The state of the parser.
83
         *
84
         * Below are the states of the parser.
85
         *
86
         *      0 ---------------------[ option ]----------------------> 1
87
         *
88
         *      1 -------------------[ = (optional) ]------------------> 2
89
         *
90
         *      2 ----------------------[ value ]----------------------> 0
91
         *
92
         * @var int
93
         */
94 249
        $state = 0;
95
96 249
        for (; $list->idx < $list->count; ++$list->idx) {
97
            /**
98
             * Token parsed at this moment.
99
             *
100
             * @var Token
101
             */
102 249
            $token = $list->tokens[$list->idx];
103
104
            // End of statement.
105 249
            if ($token->type === Token::TYPE_DELIMITER) {
106 62
                break;
107
            }
108
109
            // Skipping comments.
110 246
            if ($token->type === Token::TYPE_COMMENT) {
111 5
                continue;
112
            }
113
114
            // Skipping whitespace if not parsing value.
115 246
            if (($token->type === Token::TYPE_WHITESPACE) && ($brackets === 0)) {
116 237
                continue;
117
            }
118
119 246
            if ($lastOption === null) {
120 246
                $upper = strtoupper($token->token);
121 246
                if (isset($options[$upper])) {
122 137
                    $lastOption = $options[$upper];
123 137
                    $lastOptionId = is_array($lastOption) ?
124 137
                        $lastOption[0] : $lastOption;
125 137
                    $state = 0;
126
127
                    // Checking for option conflicts.
128
                    // For example, in `SELECT` statements the keywords `ALL`
129
                    // and `DISTINCT` conflict and if used together, they
130
                    // produce an invalid query.
131
                    //
132
                    // Usually, tokens can be identified in the array by the
133
                    // option ID, but if conflicts occur, a generated option ID
134
                    // is used.
135
                    //
136
                    // The first pseudo duplicate ID is the maximum value of the
137
                    // real options (e.g.  if there are 5 options, the first
138
                    // fake ID is 6).
139 137
                    if (isset($ret->options[$lastOptionId])) {
140 4
                        $parser->error(
141 4
                            sprintf(
142 4
                                __('This option conflicts with "%1$s".'),
143 4
                                is_array($ret->options[$lastOptionId])
144 4
                                ? $ret->options[$lastOptionId]['name']
145 4
                                : $ret->options[$lastOptionId]
146 4
                            ),
147
                            $token
148 4
                        );
149 4
                        $lastOptionId = $lastAssignedId++;
150 4
                    }
151 137
                } else {
152
                    // There is no option to be processed.
153 228
                    break;
154
                }
155 137
            }
156
157 137
            if ($state === 0) {
158 137
                if (!is_array($lastOption)) {
159
                    // This is a just keyword option without any value.
160
                    // This is the beginning and the end of it.
161 124
                    $ret->options[$lastOptionId] = $token->value;
162 124
                    $lastOption = null;
163 124
                    $state = 0;
164 137
                } elseif (($lastOption[1] === 'var') || ($lastOption[1] === 'var=')) {
165
                    // This is a keyword that is followed by a value.
166
                    // This is only the beginning. The value is parsed in state
167
                    // 1 and 2. State 1 is used to skip the first equals sign
168
                    // and state 2 to parse the actual value.
169 42
                    $ret->options[$lastOptionId] = array(
170
                        // @var string The name of the option.
171 42
                        'name' => $token->value,
172
                        // @var bool Whether it contains an equal sign.
173
                        //           This is used by the builder to rebuild it.
174 42
                        'equals' => $lastOption[1] === 'var=',
175
                        // @var string Raw value.
176 42
                        'expr' => '',
177
                        // @var string Processed value.
178 42
                        'value' => '',
179
                    );
180 42
                    $state = 1;
181 52
                } elseif ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
182
                    // This is a keyword that is followed by an expression.
183
                    // The expression is used by the specialized parser.
184
185
                    // Skipping this option in order to parse the expression.
186 26
                    ++$list->idx;
187 26
                    $ret->options[$lastOptionId] = array(
188
                        // @var string The name of the option.
189 26
                        'name' => $token->value,
190
                        // @var bool Whether it contains an equal sign.
191
                        //           This is used by the builder to rebuild it.
192 26
                        'equals' => $lastOption[1] === 'expr=',
193
                        // @var Expression The parsed expression.
194 26
                        'expr' => '',
195
                    );
196 26
                    $state = 1;
197 26
                }
198 137
            } elseif ($state === 1) {
199 49
                $state = 2;
200 49
                if ($token->token === '=') {
201 27
                    $ret->options[$lastOptionId]['equals'] = true;
202 27
                    continue;
203
                }
204 37
            }
205
206
            // This is outside the `elseif` group above because the change might
207
            // change this iteration.
208 137
            if ($state === 2) {
209 49
                if ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
210 26
                    $ret->options[$lastOptionId]['expr'] = Expression::parse(
211 26
                        $parser,
212 26
                        $list,
213 26
                        empty($lastOption[2]) ? array() : $lastOption[2]
214 26
                    );
215 26
                    $ret->options[$lastOptionId]['value']
216 26
                        = $ret->options[$lastOptionId]['expr']->expr;
217 26
                    $lastOption = null;
218 26
                    $state = 0;
219 26
                } else {
220 39
                    if ($token->token === '(') {
221 3
                        ++$brackets;
222 39
                    } elseif ($token->token === ')') {
223 3
                        --$brackets;
224 3
                    }
225
226 39
                    $ret->options[$lastOptionId]['expr'] .= $token->token;
227
228 39
                    if (!((($token->token === '(') && ($brackets === 1))
229 39
                        || (($token->token === ')') && ($brackets === 0)))
230 39
                    ) {
231
                        // First pair of brackets is being skipped.
232 39
                        $ret->options[$lastOptionId]['value'] .= $token->value;
233 39
                    }
234
235
                    // Checking if we finished parsing.
236 39
                    if ($brackets === 0) {
237 39
                        $lastOption = null;
238 39
                    }
239
                }
240 49
            }
241 137
        }
242
243
        /*
244
         * We reached the end of statement without getting a value
245
         * for an option for which a value was required
246
         */
247
        if ($state === 1
248 249
            && $lastOption
249 249
            && ($lastOption[1] == 'expr'
250 3
            || $lastOption[1] == 'var'
251 3
            || $lastOption[1] == 'var='
252
            || $lastOption[1] == 'expr=')
253 249
        ) {
254 3
            $parser->error(
255 3
                sprintf(
256 3
                    __('Value/Expression for the option %1$s was expected.'),
257 3
                    $ret->options[$lastOptionId]['name']
258 3
                ),
259 3
                $list->tokens[$list->idx - 1]
260 3
            );
261 3
        }
262
263 249
        if (empty($options['_UNSORTED'])) {
264 249
            ksort($ret->options);
265 249
        }
266
267 249
        --$list->idx;
268
269 249
        return $ret;
270
    }
271
272
    /**
273
     * @param OptionsArray $component the component to be built
274
     * @param array        $options   parameters for building
275
     *
276
     * @return string
277
     */
278 29
    public static function build($component, array $options = array())
279
    {
280 29
        if (empty($component->options)) {
281 20
            return '';
282
        }
283
284 20
        $options = array();
285 20
        foreach ($component->options as $option) {
286 20
            if (!is_array($option)) {
287 18
                $options[] = $option;
288 18
            } else {
289 9
                $options[] = $option['name']
290 9
                    . ((!empty($option['equals']) && $option['equals']) ? '=' : ' ')
291 9
                    . (!empty($option['expr']) ? $option['expr'] : $option['value']);
292
            }
293 20
        }
294
295 20
        return implode(' ', $options);
296
    }
297
298
    /**
299
     * Checks if it has the specified option and returns it value or true.
300
     *
301
     * @param string $key     the key to be checked
302
     * @param bool   $getExpr Gets the expression instead of the value.
303
     *                        The value is the processed form of the expression.
304
     *
305
     * @return mixed
306
     */
307 87
    public function has($key, $getExpr = false)
308
    {
309 87
        foreach ($this->options as $option) {
310 75
            if (is_array($option)) {
311 15
                if (!strcasecmp($key, $option['name'])) {
312 8
                    return $getExpr ? $option['expr'] : $option['value'];
313
                }
314 75
            } elseif (!strcasecmp($key, $option)) {
315 66
                return true;
316
            }
317 83
        }
318
319 81
        return false;
320
    }
321
322
    /**
323
     * Removes the option from the array.
324
     *
325
     * @param string $key the key to be removed
326
     *
327
     * @return bool whether the key was found and deleted or not
328
     */
329 1
    public function remove($key)
330
    {
331 1
        foreach ($this->options as $idx => $option) {
332 1
            if (is_array($option)) {
333 1
                if (!strcasecmp($key, $option['name'])) {
334 1
                    unset($this->options[$idx]);
335
336 1
                    return true;
337
                }
338 1
            } elseif (!strcasecmp($key, $option)) {
339 1
                unset($this->options[$idx]);
340
341 1
                return true;
342
            }
343 1
        }
344
345 1
        return false;
346
    }
347
348
    /**
349
     * Merges the specified options with these ones. Values with same ID will be
350
     * replaced.
351
     *
352
     * @param array|OptionsArray $options the options to be merged
353
     */
354 5
    public function merge($options)
355
    {
356 5
        if (is_array($options)) {
357 1
            $this->options = array_merge_recursive($this->options, $options);
358 5
        } elseif ($options instanceof self) {
359 4
            $this->options = array_merge_recursive($this->options, $options->options);
360 4
        }
361 5
    }
362
363
    /**
364
     * Checks tf there are no options set.
365
     *
366
     * @return bool
367
     */
368 9
    public function isEmpty()
369
    {
370 9
        return empty($this->options);
371
    }
372
}
373