Completed
Push — master ( 14c8dc...87c59f )
by Michal
23:27
created

OptionsArray::parse()   D

Complexity

Conditions 37
Paths 12

Size

Total Lines 222
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 89
CRAP Score 37.0145

Importance

Changes 0
Metric Value
dl 0
loc 222
ccs 89
cts 91
cp 0.978
rs 4.2495
c 0
b 0
f 0
cc 37
eloc 93
nc 12
nop 3
crap 37.0145

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