Passed
Push — master ( b16987...8b6d77 )
by Maurício
03:49 queued 13s
created

Conditions::parse()   D

Complexity

Conditions 27
Paths 24

Size

Total Lines 119
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 27

Importance

Changes 0
Metric Value
cc 27
eloc 56
nc 24
nop 3
dl 0
loc 119
ccs 54
cts 54
cp 1
crap 27
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\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',
39
        'AND',
40
        'BETWEEN',
41
        'COLLATE',
42
        'EXISTS',
43
        'IF',
44
        'IN',
45
        'INTERVAL',
46
        'IS',
47
        'LIKE',
48
        'MATCH',
49
        'NOT IN',
50
        'NOT NULL',
51
        'NOT',
52
        'NULL',
53
        'OR',
54
        'REGEXP',
55
        'RLIKE',
56
        'SOUNDS',
57
        'XOR',
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 204
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
68
    {
69 204
        $ret = [];
70
71 204
        $expr = new Condition();
72
73
        /**
74
         * Counts brackets.
75
         */
76 204
        $brackets = 0;
77
78
        /**
79
         * Whether there was a `BETWEEN` keyword before or not.
80
         *
81
         * It is required to keep track of them because their structure contains
82
         * the keyword `AND`, which is also an operator that delimits
83
         * expressions.
84
         */
85 204
        $betweenBefore = false;
86
87 204
        for (; $list->idx < $list->count; ++$list->idx) {
88
            /**
89
             * Token parsed at this moment.
90
             */
91 204
            $token = $list->tokens[$list->idx];
92
93
            // End of statement.
94 204
            if ($token->type === TokenType::Delimiter) {
95 100
                break;
96
            }
97
98
            // Skipping whitespaces and comments.
99 204
            if ($token->type === TokenType::Comment) {
100 12
                continue;
101
            }
102
103
            // Replacing all whitespaces (new lines, tabs, etc.) with a single
104
            // space character.
105 204
            if ($token->type === TokenType::Whitespace) {
106 200
                $expr->expr .= ' ';
107 200
                continue;
108
            }
109
110
            // Conditions are delimited by logical operators.
111 204
            if (in_array($token->value, self::DELIMITERS, true)) {
112 36
                if ($betweenBefore && ($token->value === 'AND')) {
113
                    // The syntax of keyword `BETWEEN` is hard-coded.
114 4
                    $betweenBefore = false;
115
                } else {
116
                    // The expression ended.
117 36
                    $expr->expr = trim($expr->expr);
118 36
                    if (! empty($expr->expr)) {
119 36
                        $ret[] = $expr;
120
                    }
121
122
                    // Adding the operator.
123 36
                    $expr = new Condition($token->value);
124 36
                    $expr->isOperator = true;
125 36
                    $ret[] = $expr;
126
127
                    // Preparing to parse another condition.
128 36
                    $expr = new Condition();
129 36
                    continue;
130
                }
131
            }
132
133
            if (
134 204
                ($token->type === TokenType::Keyword)
135 204
                && ($token->flags & Token::FLAG_KEYWORD_RESERVED)
136 204
                && ! ($token->flags & Token::FLAG_KEYWORD_FUNCTION)
137
            ) {
138 126
                if ($token->value === 'BETWEEN') {
139 4
                    $betweenBefore = true;
140
                }
141
142 126
                if ($brackets === 0 && ! in_array($token->value, self::ALLOWED_KEYWORDS, true)) {
143 118
                    break;
144
                }
145
            }
146
147 202
            if ($token->type === TokenType::Operator) {
148 194
                if ($token->value === '(') {
149 34
                    ++$brackets;
150 194
                } elseif ($token->value === ')') {
151 36
                    if ($brackets === 0) {
152 2
                        break;
153
                    }
154
155 34
                    --$brackets;
156
                }
157
            }
158
159 202
            $expr->expr .= $token->token;
160
            if (
161 202
                ($token->type !== TokenType::None)
162 202
                && (($token->type !== TokenType::Keyword)
163 202
                || ($token->flags & Token::FLAG_KEYWORD_RESERVED))
164 202
                && ($token->type !== TokenType::String)
165 202
                && ($token->type !== TokenType::Symbol || ($token->flags & Token::FLAG_SYMBOL_PARAMETER))
166
            ) {
167 202
                continue;
168
            }
169
170 154
            if (in_array($token->value, $expr->identifiers)) {
171 38
                continue;
172
            }
173
174 154
            $expr->identifiers[] = $token->value;
175
        }
176
177
        // Last iteration was not processed.
178 204
        $expr->expr = trim($expr->expr);
179 204
        if (! empty($expr->expr)) {
180 202
            $ret[] = $expr;
181
        }
182
183 204
        --$list->idx;
184
185 204
        return $ret;
186
    }
187
188
    /** @param Condition[] $component the component to be built */
189 62
    public static function buildAll(array $component): string
190
    {
191 62
        return implode(' ', $component);
192
    }
193
}
194