Completed
Pull Request — master (#75)
by Deven
162:47 queued 92:52
created

OptionsArray   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 97.84%

Importance

Changes 9
Bugs 6 Features 1
Metric Value
c 9
b 6
f 1
dl 0
loc 345
ccs 136
cts 139
cp 0.9784
rs 6.5957
wmc 56
lcom 1
cbo 5

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
D parse() 0 217 34
B build() 0 18 6
B has() 0 13 6
B remove() 0 15 5
A merge() 0 8 3
A isEmpty() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like OptionsArray 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 OptionsArray, and based on these observations, apply Extract Interface, too.

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