Passed
Pull Request — master (#334)
by Antoine
13:06
created

WithStatement::parse()   C

Complexity

Conditions 14
Paths 12

Size

Total Lines 87
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 41
nc 12
nop 2
dl 0
loc 87
rs 6.2666
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
 * `WITH` statement.
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\SqlParser\Statements;
9
10
use PhpMyAdmin\SqlParser\Components\Array2d;
11
use PhpMyAdmin\SqlParser\Components\OptionsArray;
12
use PhpMyAdmin\SqlParser\Components\WithKeyword;
13
use PhpMyAdmin\SqlParser\Exceptions\ParserException;
14
use PhpMyAdmin\SqlParser\Parser;
15
use PhpMyAdmin\SqlParser\Statement;
16
use PhpMyAdmin\SqlParser\Token;
17
use PhpMyAdmin\SqlParser\TokensList;
18
use PhpMyAdmin\SqlParser\Translator;
19
20
use function array_slice;
21
use function count;
22
23
/**
24
 * `WITH` statement.
25
26
 *  WITH [RECURSIVE] query_name [ (column_name [,...]) ] AS (SELECT ...) [, ...]
27
 */
28
final class WithStatement extends Statement
29
{
30
    /**
31
     * Options for `WITH` statements and their slot ID.
32
     *
33
     * @var mixed[]
34
     */
35
    public static $OPTIONS = ['RECURSIVE' => 1];
36
37
    /**
38
     * The clauses of this statement, in order.
39
     *
40
     * @see Statement::$CLAUSES
41
     *
42
     * @var mixed[]
43
     */
44
    public static $CLAUSES = [
45
        'WITH' => [
46
            'WITH',
47
            2,
48
        ],
49
        // Used for options.
50
        '_OPTIONS' => [
51
            '_OPTIONS',
52
            1,
53
        ],
54
        'AS' => [
55
            'AS',
56
            2,
57
        ],
58
    ];
59
60
    /** @var WithKeyword[] */
61
    public $withers = [];
62
63
    /**
64
     * @param Parser     $parser the instance that requests parsing
65
     * @param TokensList $list   the list of tokens to be parsed
66
     */
67
    public function parse(Parser $parser, TokensList $list)
68
    {
69
        ++$list->idx; // Skipping `WITH`.
70
71
        // parse any options if provided
72
        $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
73
        ++$list->idx;
74
75
        /**
76
         * The state of the parser.
77
         *
78
         * Below are the states of the parser.
79
         *
80
         *      0 ---------------- [ name ] -----------------> 1
81
         *      1 -------------- [( columns )] AS ----------------> 2
82
         *      2 ------------------ [ , ] --------------------> 0
83
         *
84
         * @var int
85
         */
86
        $state = 0;
87
        $wither = null;
88
89
        for (; $list->idx < $list->count; ++$list->idx) {
90
            /**
91
             * Token parsed at this moment.
92
             *
93
             * @var Token
94
             */
95
            $token = $list->tokens[$list->idx];
96
97
            // Skipping whitespaces and comments.
98
            if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
99
                continue;
100
            }
101
102
            if ($token->type === Token::TYPE_NONE) {
103
                $wither = $token->value;
104
                $this->withers[$wither] = new WithKeyword($wither);
105
                $state = 1;
106
                continue;
107
            }
108
109
            if ($state === 1) {
110
                if ($token->value === '(') {
111
                    $this->withers[$wither]->columns = Array2d::parse($parser, $list);
112
                    continue;
113
                }
114
115
                if ($token->keyword === 'AS') {
116
                    ++$list->idx;
117
                    $state = 2;
118
                    continue;
119
                }
120
            } elseif ($state === 2) {
121
                if ($token->value === '(') {
122
                    ++$list->idx;
123
                    $subList = $this->getSubTokenList($list);
124
                    if ($subList instanceof ParserException) {
125
                        $parser->errors[] = $subList;
126
                        continue;
127
                    }
128
129
                    $subParser = new Parser($subList);
130
131
                    if (count($subParser->errors)) {
132
                        foreach ($subParser->errors as $error) {
133
                            $parser->errors[] = $error;
134
                        }
135
                    }
136
137
                    $this->withers[$wither]->statement = $subParser;
138
                    continue;
139
                }
140
141
                // There's another WITH expression to parse, go back to state=0
142
                if ($token->value === ',') {
143
                    $list->idx++;
144
                    $state = 0;
145
                    continue;
146
                }
147
148
                // No more WITH expressions, we're done with this statement
149
                break;
150
            }
151
        }
152
153
        --$list->idx;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function build()
160
    {
161
        $str = 'WITH ';
162
163
        foreach ($this->withers as $wither) {
164
            $str .= $str === 'WITH ' ? '' : ', ';
165
            $str .= WithKeyword::build($wither);
166
        }
167
168
        return $str;
169
    }
170
171
    /**
172
     * Get tokens within the WITH expression to use them in another parser
173
     *
174
     * @return ParserException|TokensList
175
     */
176
    private function getSubTokenList(TokensList $list)
177
    {
178
        $idx = $list->idx;
179
        /** @var Token $token */
180
        $token = $list->tokens[$list->idx];
181
        $openParenthesis = 0;
182
183
        while ($list->idx < $list->count) {
184
            if ($token->value === '(') {
185
                ++$openParenthesis;
186
            } elseif ($token->value === ')') {
187
                if (--$openParenthesis === -1) {
188
                    break;
189
                }
190
            }
191
192
            ++$list->idx;
193
            if (! isset($list->tokens[$list->idx])) {
194
                break;
195
            }
196
197
            $token = $list->tokens[$list->idx];
198
        }
199
200
        // performance improvement: return the error to avoid a try/catch in the loop
201
        if ($list->idx === $list->count) {
202
            --$list->idx;
203
204
            return new ParserException(
205
                Translator::gettext('A closing bracket was expected.'),
206
                $token
207
            );
208
        }
209
210
        $length = $list->idx - $idx;
211
212
        return new TokensList(array_slice($list->tokens, $idx, $length), $length);
213
    }
214
}
215