Passed
Pull Request — master (#535)
by
unknown
02:55
created

Conditions::parse()   D

Complexity

Conditions 27
Paths 24

Size

Total Lines 123
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 27

Importance

Changes 0
Metric Value
cc 27
eloc 56
c 0
b 0
f 0
nc 24
nop 3
dl 0
loc 123
rs 4.1666
ccs 54
cts 54
cp 1
crap 27

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\Condition;
8
use PhpMyAdmin\SqlParser\Parseable;
9
use PhpMyAdmin\SqlParser\Parser;
10
use PhpMyAdmin\SqlParser\Token;
11
use PhpMyAdmin\SqlParser\TokensList;
12
use PhpMyAdmin\SqlParser\TokenType;
13
14
use function implode;
15
use function in_array;
16
use function trim;
17
18
/**
19
 * `WHERE` keyword parser.
20
 */
21
final class Conditions implements Parseable
22
{
23
    /**
24
     * Logical operators that can be used to delimit expressions.
25
     */
26
    private const DELIMITERS = [
27
        '&&',
28
        '||',
29
        'AND',
30
        'OR',
31
        'XOR',
32
    ];
33
34
    /**
35
     * List of allowed reserved keywords in conditions.
36
     */
37
    private const ALLOWED_KEYWORDS = [
38
        'ALL' => 1,
39
        'AND' => 1,
40
        'BETWEEN' => 1,
41
        'COLLATE' => 1,
42
        'EXISTS' => 1,
43
        'IF' => 1,
44
        'IN' => 1,
45
        'INTERVAL' => 1,
46
        'IS' => 1,
47
        'LIKE' => 1,
48
        'MATCH' => 1,
49
        'NOT IN' => 1,
50
        'NOT NULL' => 1,
51
        'NOT' => 1,
52
        'NULL' => 1,
53
        'OR' => 1,
54
        'REGEXP' => 1,
55
        'RLIKE' => 1,
56
        'SOUNDS' => 1,
57
        'XOR' => 1,
58
    ];
59
60
    /**
61
     * @param Parser               $parser  the parser that serves as context
62
     * @param TokensList           $list    the list of tokens that are being parsed
63
     * @param array<string, mixed> $options parameters for parsing
64
     *
65
     * @return Condition[]
66
     */
67 202
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
68
    {
69 202
        $ret = [];
70
71 202
        $expr = new Condition();
72
73
        /**
74
         * Counts brackets.
75
         *
76
         * @var int
77
         */
78 202
        $brackets = 0;
79
80
        /**
81
         * Whether there was a `BETWEEN` keyword before or not.
82
         *
83
         * It is required to keep track of them because their structure contains
84
         * the keyword `AND`, which is also an operator that delimits
85
         * expressions.
86
         *
87
         * @var bool
88
         */
89 202
        $betweenBefore = false;
90
91 202
        for (; $list->idx < $list->count; ++$list->idx) {
92
            /**
93
             * Token parsed at this moment.
94
             */
95 202
            $token = $list->tokens[$list->idx];
96
97
            // End of statement.
98 202
            if ($token->type === TokenType::Delimiter) {
99 98
                break;
100
            }
101
102
            // Skipping whitespaces and comments.
103 202
            if ($token->type === TokenType::Comment) {
104 12
                continue;
105
            }
106
107
            // Replacing all whitespaces (new lines, tabs, etc.) with a single
108
            // space character.
109 202
            if ($token->type === TokenType::Whitespace) {
110 198
                $expr->expr .= ' ';
111 198
                continue;
112
            }
113
114
            // Conditions are delimited by logical operators.
115 202
            if (in_array($token->value, self::DELIMITERS, true)) {
116 36
                if ($betweenBefore && ($token->value === 'AND')) {
117
                    // The syntax of keyword `BETWEEN` is hard-coded.
118 4
                    $betweenBefore = false;
119
                } else {
120
                    // The expression ended.
121 36
                    $expr->expr = trim($expr->expr);
122 36
                    if (! empty($expr->expr)) {
123 36
                        $ret[] = $expr;
124
                    }
125
126
                    // Adding the operator.
127 36
                    $expr = new Condition($token->value);
128 36
                    $expr->isOperator = true;
129 36
                    $ret[] = $expr;
130
131
                    // Preparing to parse another condition.
132 36
                    $expr = new Condition();
133 36
                    continue;
134
                }
135
            }
136
137
            if (
138 202
                ($token->type === TokenType::Keyword)
139 202
                && ($token->flags & Token::FLAG_KEYWORD_RESERVED)
140 202
                && ! ($token->flags & Token::FLAG_KEYWORD_FUNCTION)
141
            ) {
142 126
                if ($token->value === 'BETWEEN') {
143 4
                    $betweenBefore = true;
144
                }
145
146 126
                if (($brackets === 0) && empty(self::ALLOWED_KEYWORDS[$token->value])) {
147 118
                    break;
148
                }
149
            }
150
151 200
            if ($token->type === TokenType::Operator) {
152 192
                if ($token->value === '(') {
153 34
                    ++$brackets;
154 192
                } elseif ($token->value === ')') {
155 36
                    if ($brackets === 0) {
156 2
                        break;
157
                    }
158
159 34
                    --$brackets;
160
                }
161
            }
162
163 200
            $expr->expr .= $token->token;
164
            if (
165 200
                ($token->type !== TokenType::None)
166 200
                && (($token->type !== TokenType::Keyword)
167 200
                || ($token->flags & Token::FLAG_KEYWORD_RESERVED))
168 200
                && ($token->type !== TokenType::String)
169 200
                && ($token->type !== TokenType::Symbol || ($token->flags & Token::FLAG_SYMBOL_PARAMETER))
170
            ) {
171 200
                continue;
172
            }
173
174 152
            if (in_array($token->value, $expr->identifiers)) {
175 36
                continue;
176
            }
177
178 152
            $expr->identifiers[] = $token->value;
179
        }
180
181
        // Last iteration was not processed.
182 202
        $expr->expr = trim($expr->expr);
183 202
        if (! empty($expr->expr)) {
184 200
            $ret[] = $expr;
185
        }
186
187 202
        --$list->idx;
188
189 202
        return $ret;
190
    }
191
192
    /** @param Condition[] $component the component to be built */
193 62
    public static function buildAll(array $component): string
194
    {
195 62
        return implode(' ', $component);
196
    }
197
}
198