Completed
Push — master ( 578d3a...04a929 )
by Maurício
34s queued 14s
created

CreateDefinitions::parse()   D

Complexity

Conditions 31
Paths 30

Size

Total Lines 132
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 68
CRAP Score 31

Importance

Changes 0
Metric Value
cc 31
eloc 70
nc 30
nop 3
dl 0
loc 132
ccs 68
cts 68
cp 1
crap 31
rs 4.1666
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
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components\Parsers;
6
7
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
8
use PhpMyAdmin\SqlParser\Components\DataType;
9
use PhpMyAdmin\SqlParser\Components\Key;
10
use PhpMyAdmin\SqlParser\Components\OptionsArray;
11
use PhpMyAdmin\SqlParser\Components\Reference;
12
use PhpMyAdmin\SqlParser\Parseable;
13
use PhpMyAdmin\SqlParser\Parser;
14
use PhpMyAdmin\SqlParser\Token;
15
use PhpMyAdmin\SqlParser\TokensList;
16
use PhpMyAdmin\SqlParser\TokenType;
17
18
use function implode;
19
20
/**
21
 * Parses the create definition of a column or a key.
22
 *
23
 * Used for parsing `CREATE TABLE` statement.
24
 */
25
final class CreateDefinitions implements Parseable
26
{
27
    /**
28
     * All field options.
29
     */
30
    private const FIELD_OPTIONS = [
31
        // Tells the `OptionsArray` to not sort the options.
32
        // See the note below.
33
        '_UNSORTED' => true,
34
35
        'NOT NULL' => 1,
36
        'NULL' => 1,
37
        'DEFAULT' => [
38
            2,
39
            'expr',
40
            ['breakOnAlias' => true],
41
        ],
42
        /* Following are not according to grammar, but MySQL happily accepts
43
         * these at any location */
44
        'CHARSET' => [
45
            2,
46
            'var',
47
        ],
48
        'COLLATE' => [
49
            3,
50
            'var',
51
        ],
52
        'AUTO_INCREMENT' => 3,
53
        'KEY' => 4,
54
        'PRIMARY' => 4,
55
        'PRIMARY KEY' => 4,
56
        'UNIQUE' => 4,
57
        'UNIQUE KEY' => 4,
58
        'COMMENT' => [
59
            5,
60
            'var',
61
        ],
62
        'COLUMN_FORMAT' => [
63
            6,
64
            'var',
65
        ],
66
        'ON UPDATE' => [
67
            7,
68
            'expr',
69
        ],
70
71
        // Generated columns options.
72
        'GENERATED ALWAYS' => 8,
73
        'AS' => [
74
            9,
75
            'expr',
76
            ['parenthesesDelimited' => true],
77
        ],
78
        'VIRTUAL' => 10,
79
        'PERSISTENT' => 11,
80
        'STORED' => 11,
81
        'CHECK' => [
82
            12,
83
            'expr',
84
            ['parenthesesDelimited' => true],
85
        ],
86
        'INVISIBLE' => 13,
87
        'ENFORCED' => 14,
88
        'NOT' => 15,
89
        'COMPRESSED' => 16,
90
        // Common entries.
91
        //
92
        // NOTE: Some of the common options are not in the same order which
93
        // causes troubles when checking if the options are in the right order.
94
        // I should find a way to define multiple sets of options and make the
95
        // parser select the right set.
96
        //
97
        // 'UNIQUE'                        => 4,
98
        // 'UNIQUE KEY'                    => 4,
99
        // 'COMMENT'                       => [5, 'var'],
100
        // 'NOT NULL'                      => 1,
101
        // 'NULL'                          => 1,
102
        // 'PRIMARY'                       => 4,
103
        // 'PRIMARY KEY'                   => 4,
104
    ];
105
106
    /**
107
     * @param Parser               $parser  the parser that serves as context
108
     * @param TokensList           $list    the list of tokens that are being parsed
109
     * @param array<string, mixed> $options parameters for parsing
110
     *
111
     * @return CreateDefinition[]
112
     */
113 118
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
114
    {
115 118
        $ret = [];
116
117 118
        $expr = new CreateDefinition();
118
119
        /**
120
         * The state of the parser.
121
         *
122
         * Below are the states of the parser.
123
         *
124
         *      0 -----------------------[ ( ]------------------------> 1
125
         *
126
         *      1 --------------------[ CONSTRAINT ]------------------> 1
127
         *      1 -----------------------[ key ]----------------------> 2
128
         *      1 -------------[ constraint / column name ]-----------> 2
129
         *
130
         *      2 --------------------[ data type ]-------------------> 3
131
         *
132
         *      3 ---------------------[ options ]--------------------> 4
133
         *
134
         *      4 --------------------[ REFERENCES ]------------------> 4
135
         *
136
         *      5 ------------------------[ , ]-----------------------> 1
137
         *      5 ------------------------[ ) ]-----------------------> 6 (-1)
138
         */
139 118
        $state = 0;
140
141 118
        for (; $list->idx < $list->count; ++$list->idx) {
142
            /**
143
             * Token parsed at this moment.
144
             */
145 118
            $token = $list->tokens[$list->idx];
146
147
            // End of statement.
148 118
            if ($token->type === TokenType::Delimiter) {
149 4
                break;
150
            }
151
152
            // Skipping whitespaces and comments.
153 116
            if (($token->type === TokenType::Whitespace) || ($token->type === TokenType::Comment)) {
154 110
                continue;
155
            }
156
157 116
            if ($state === 0) {
158 116
                if (($token->type !== TokenType::Operator) || ($token->value !== '(')) {
159 2
                    $parser->error('An opening bracket was expected.', $token);
160
161 2
                    break;
162
                }
163
164 114
                $state = 1;
165 114
            } elseif ($state === 1) {
166 114
                if ($token->type === TokenType::Keyword && $token->keyword === 'CONSTRAINT') {
167 18
                    $expr->isConstraint = true;
168 114
                } elseif (($token->type === TokenType::Keyword) && ($token->flags & Token::FLAG_KEYWORD_KEY)) {
169 42
                    $expr->key = Key::parse($parser, $list);
170 42
                    $state = 4;
171 114
                } elseif ($token->type === TokenType::Symbol || $token->type === TokenType::None) {
172 108
                    $expr->name = $token->value;
173 108
                    if (! $expr->isConstraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expr->isConstraint of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
174 108
                        $state = 2;
175
                    }
176 14
                } elseif ($token->type === TokenType::Keyword) {
177 12
                    if ($token->flags & Token::FLAG_KEYWORD_RESERVED) {
178
                        // Reserved keywords can't be used
179
                        // as field names without backquotes
180 2
                        $parser->error(
181 2
                            'A symbol name was expected! '
182 2
                            . 'A reserved keyword can not be used '
183 2
                            . 'as a column name without backquotes.',
184 2
                            $token,
185 2
                        );
186
187 2
                        return $ret;
188
                    }
189
190
                    // Non-reserved keywords are allowed without backquotes
191 10
                    $expr->name = $token->value;
192 10
                    $state = 2;
193
                } else {
194 2
                    $parser->error('A symbol name was expected!', $token);
195
196 57
                    return $ret;
197
                }
198 110
            } elseif ($state === 2) {
199 110
                $expr->type = DataType::parse($parser, $list);
200 110
                $state = 3;
201 110
            } elseif ($state === 3) {
202 110
                $expr->options = OptionsArray::parse($parser, $list, self::FIELD_OPTIONS);
203 110
                $state = 4;
204 110
            } elseif ($state === 4) {
205 110
                if ($token->type === TokenType::Keyword && $token->keyword === 'REFERENCES') {
206 16
                    ++$list->idx; // Skipping keyword 'REFERENCES'.
207 16
                    $expr->references = Reference::parse($parser, $list);
208
                } else {
209 110
                    --$list->idx;
210
                }
211
212 110
                $state = 5;
213
            } else {
214 110
                if (! empty($expr->type) || ! empty($expr->key)) {
215 110
                    $ret[] = $expr;
216
                }
217
218 110
                $expr = new CreateDefinition();
219 110
                if ($token->value === ',') {
220 76
                    $state = 1;
221 108
                } elseif ($token->value === ')') {
222 106
                    $state = 6;
223 106
                    ++$list->idx;
224 106
                    break;
225
                } else {
226 2
                    $parser->error('A comma or a closing bracket was expected.', $token);
227 2
                    $state = 0;
228 2
                    break;
229
                }
230
            }
231
        }
232
233
        // Last iteration was not saved.
234 114
        if (! empty($expr->type) || ! empty($expr->key)) {
235 2
            $ret[] = $expr;
236
        }
237
238 114
        if (($state !== 0) && ($state !== 6)) {
239 2
            $parser->error('A closing bracket was expected.', $list->tokens[$list->idx - 1]);
240
        }
241
242 114
        --$list->idx;
243
244 114
        return $ret;
245
    }
246
247
    /** @param CreateDefinition[] $component the component to be built */
248 26
    public static function buildAll(array $component): string
249
    {
250 26
        return "(\n  " . implode(",\n  ", $component) . "\n)";
251
    }
252
}
253