Passed
Pull Request — master (#483)
by
unknown
03:28
created

Condition::buildAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Components;
6
7
use PhpMyAdmin\SqlParser\Component;
8
use PhpMyAdmin\SqlParser\Parser;
9
use PhpMyAdmin\SqlParser\Token;
10
use PhpMyAdmin\SqlParser\TokensList;
11
12
use function implode;
13
use function in_array;
14
use function trim;
15
16
/**
17
 * `WHERE` keyword parser.
18
 */
19
final class Condition implements Component
20
{
21
    /**
22
     * Logical operators that can be used to delimit expressions.
23
     *
24
     * @var string[]
25
     */
26
    public static $delimiters = [
27
        '&&',
28
        '||',
29
        'AND',
30
        'OR',
31
        'XOR',
32
    ];
33
34
    /**
35
     * List of allowed reserved keywords in conditions.
36
     *
37
     * @var array<string, int>
38
     */
39
    public static $allowedKeywords = [
40
        'ALL' => 1,
41
        'AND' => 1,
42
        'BETWEEN' => 1,
43
        'EXISTS' => 1,
44
        'IF' => 1,
45
        'IN' => 1,
46
        'INTERVAL' => 1,
47
        'IS' => 1,
48
        'LIKE' => 1,
49
        'MATCH' => 1,
50
        'NOT IN' => 1,
51
        'NOT NULL' => 1,
52
        'NOT' => 1,
53
        'NULL' => 1,
54
        'OR' => 1,
55
        'REGEXP' => 1,
56
        'RLIKE' => 1,
57
        'SOUNDS' => 1,
58
        'XOR' => 1,
59
    ];
60
61
    /**
62
     * Identifiers recognized.
63
     *
64
     * @var array<int, mixed>
65
     */
66
    public $identifiers = [];
67
68
    /**
69
     * Whether this component is an operator.
70
     *
71
     * @var bool
72
     */
73
    public $isOperator = false;
74
75
    /**
76
     * The condition.
77
     *
78
     * @var string
79
     */
80
    public $expr;
81
82
    /**
83
     * @param string $expr the condition or the operator
84
     */
85 194
    public function __construct($expr = null)
86
    {
87 194
        $this->expr = trim((string) $expr);
88
    }
89
90
    /**
91
     * @param Parser               $parser  the parser that serves as context
92
     * @param TokensList           $list    the list of tokens that are being parsed
93
     * @param array<string, mixed> $options parameters for parsing
94
     *
95
     * @return Condition[]
96
     */
97 192
    public static function parse(Parser $parser, TokensList $list, array $options = []): array
98
    {
99 192
        $ret = [];
100
101 192
        $expr = new static();
102
103
        /**
104
         * Counts brackets.
105
         *
106
         * @var int
107
         */
108 192
        $brackets = 0;
109
110
        /**
111
         * Whether there was a `BETWEEN` keyword before or not.
112
         *
113
         * It is required to keep track of them because their structure contains
114
         * the keyword `AND`, which is also an operator that delimits
115
         * expressions.
116
         *
117
         * @var bool
118
         */
119 192
        $betweenBefore = false;
120
121 192
        for (; $list->idx < $list->count; ++$list->idx) {
122
            /**
123
             * Token parsed at this moment.
124
             */
125 192
            $token = $list->tokens[$list->idx];
126
127
            // End of statement.
128 192
            if ($token->type === Token::TYPE_DELIMITER) {
129 90
                break;
130
            }
131
132
            // Skipping whitespaces and comments.
133 192
            if ($token->type === Token::TYPE_COMMENT) {
134 12
                continue;
135
            }
136
137
            // Replacing all whitespaces (new lines, tabs, etc.) with a single
138
            // space character.
139 192
            if ($token->type === Token::TYPE_WHITESPACE) {
140 188
                $expr->expr .= ' ';
141 188
                continue;
142
            }
143
144
            // Conditions are delimited by logical operators.
145 192
            if (in_array($token->value, static::$delimiters, true)) {
146 34
                if ($betweenBefore && ($token->value === 'AND')) {
147
                    // The syntax of keyword `BETWEEN` is hard-coded.
148 4
                    $betweenBefore = false;
149
                } else {
150
                    // The expression ended.
151 34
                    $expr->expr = trim($expr->expr);
152 34
                    if (! empty($expr->expr)) {
153 34
                        $ret[] = $expr;
154
                    }
155
156
                    // Adding the operator.
157 34
                    $expr = new static($token->value);
158 34
                    $expr->isOperator = true;
159 34
                    $ret[] = $expr;
160
161
                    // Preparing to parse another condition.
162 34
                    $expr = new static();
163 34
                    continue;
164
                }
165
            }
166
167
            if (
168 192
                ($token->type === Token::TYPE_KEYWORD)
169 192
                && ($token->flags & Token::FLAG_KEYWORD_RESERVED)
170 192
                && ! ($token->flags & Token::FLAG_KEYWORD_FUNCTION)
171
            ) {
172 122
                if ($token->value === 'BETWEEN') {
173 4
                    $betweenBefore = true;
174
                }
175
176 122
                if (($brackets === 0) && empty(static::$allowedKeywords[$token->value])) {
177 116
                    break;
178
                }
179
            }
180
181 190
            if ($token->type === Token::TYPE_OPERATOR) {
182 182
                if ($token->value === '(') {
183 34
                    ++$brackets;
184 182
                } elseif ($token->value === ')') {
185 36
                    if ($brackets === 0) {
186 2
                        break;
187
                    }
188
189 34
                    --$brackets;
190
                }
191
            }
192
193 190
            $expr->expr .= $token->token;
194
            if (
195 190
                ($token->type !== Token::TYPE_NONE)
196 190
                && (($token->type !== Token::TYPE_KEYWORD)
197 190
                || ($token->flags & Token::FLAG_KEYWORD_RESERVED))
198 190
                && ($token->type !== Token::TYPE_STRING)
199 190
                && ($token->type !== Token::TYPE_SYMBOL)
200
            ) {
201 190
                continue;
202
            }
203
204 142
            if (in_array($token->value, $expr->identifiers)) {
205 34
                continue;
206
            }
207
208 142
            $expr->identifiers[] = $token->value;
209
        }
210
211
        // Last iteration was not processed.
212 192
        $expr->expr = trim($expr->expr);
213 192
        if (! empty($expr->expr)) {
214 190
            $ret[] = $expr;
215
        }
216
217 192
        --$list->idx;
218
219 192
        return $ret;
220
    }
221
222
    /**
223
     * @param Condition $component the component to be built
224
     */
225 62
    public static function build($component): string
226
    {
227 62
        return $component->expr;
228
    }
229
230
    /**
231
     * @param Condition[] $component the component to be built
232
     */
233 62
    public static function buildAll(array $component): string
234
    {
235 62
        return implode(' ', $component);
236
    }
237
238 62
    public function __toString(): string
239
    {
240 62
        return static::build($this);
241
    }
242
}
243