Passed
Push — master ( 8e2215...5aab64 )
by Michal
03:14
created

Context::isComment()   C

Complexity

Conditions 19
Paths 10

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 19.0733

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
ccs 16
cts 17
cp 0.9412
rs 5.2455
cc 19
eloc 17
nc 10
nop 2
crap 19.0733

How to fix   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
/**
4
 * Defines a context class that is later extended to define other contexts.
5
 *
6
 * A context is a collection of keywords, operators and functions used for
7
 * parsing.
8
 */
9
10
namespace PhpMyAdmin\SqlParser;
11
12
/**
13
 * Holds the configuration of the context that is currently used.
14
 *
15
 * @category Contexts
16
 *
17
 * @license  https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
18
 */
19
abstract class Context
20
{
21
    /**
22
     * The maximum length of a keyword.
23
     *
24
     * @see static::$TOKEN_KEYWORD
25
     *
26
     * @var int
27
     */
28
    const KEYWORD_MAX_LENGTH = 30;
29
30
    /**
31
     * The maximum length of a label.
32
     *
33
     * @see static::$TOKEN_LABEL
34
     * Ref: https://dev.mysql.com/doc/refman/5.7/en/statement-labels.html
35
     *
36
     * @var int
37
     */
38
    const LABEL_MAX_LENGTH = 16;
39
40
    /**
41
     * The maximum length of an operator.
42
     *
43
     * @see static::$TOKEN_OPERATOR
44
     *
45
     * @var int
46
     */
47
    const OPERATOR_MAX_LENGTH = 4;
48
49
    /**
50
     * The name of the default content.
51
     *
52
     * @var string
53
     */
54
    public static $defaultContext = '\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700';
55
56
    /**
57
     * The name of the loaded context.
58
     *
59
     * @var string
60
     */
61
    public static $loadedContext = '\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700';
62
63
    /**
64
     * The prefix concatenated to the context name when an incomplete class name
65
     * is specified.
66
     *
67
     * @var string
68
     */
69
    public static $contextPrefix = '\\PhpMyAdmin\\SqlParser\\Contexts\\Context';
70
71
    /**
72
     * List of keywords.
73
     *
74
     * Because, PHP's associative arrays are basically hash tables, it is more
75
     * efficient to store keywords as keys instead of values.
76
     *
77
     * The value associated to each keyword represents its flags.
78
     *
79
     * @see Token::FLAG_KEYWORD_*
80
     *
81
     * Elements are sorted by flags, length and keyword.
82
     *
83
     * @var array
84
     */
85
    public static $KEYWORDS = array();
86
87
    /**
88
     * List of operators and their flags.
89
     *
90
     * @var array
91
     */
92
    public static $OPERATORS = array(
93
        // Some operators (*, =) may have ambiguous flags, because they depend on
94
        // the context they are being used in.
95
        // For example: 1. SELECT * FROM table; # SQL specific (wildcard)
96
        //                 SELECT 2 * 3;        # arithmetic
97
        //              2. SELECT * FROM table WHERE foo = 'bar';
98
        //                 SET @i = 0;
99
100
        // @see Token::FLAG_OPERATOR_ARITHMETIC
101
        '%' => 1, '*' => 1, '+' => 1, '-' => 1, '/' => 1,
102
103
        // @see Token::FLAG_OPERATOR_LOGICAL
104
        '!' => 2, '!=' => 2, '&&' => 2, '<' => 2, '<=' => 2,
105
        '<=>' => 2, '<>' => 2, '=' => 2, '>' => 2, '>=' => 2,
106
        '||' => 2,
107
108
        // @see Token::FLAG_OPERATOR_BITWISE
109
        '&' => 4, '<<' => 4, '>>' => 4, '^' => 4, '|' => 4,
110
        '~' => 4,
111
112
        // @see Token::FLAG_OPERATOR_ASSIGNMENT
113
        ':=' => 8,
114
115
        // @see Token::FLAG_OPERATOR_SQL
116
        '(' => 16, ')' => 16, '.' => 16, ',' => 16, ';' => 16,
117
    );
118
119
    /**
120
     * The mode of the MySQL server that will be used in lexing, parsing and
121
     * building the statements.
122
     *
123
     * @var int
124
     */
125
    public static $MODE = 0;
126
127
    /*
128
     * Server SQL Modes
129
     * https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html
130
     */
131
132
    // Compatibility mode for Microsoft's SQL server.
133
    // This is the equivalent of ANSI_QUOTES.
134
    const SQL_MODE_COMPAT_MYSQL = 2;
135
136
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_allow_invalid_dates
137
    const SQL_MODE_ALLOW_INVALID_DATES = 1;
138
139
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ansi_quotes
140
    const SQL_MODE_ANSI_QUOTES = 2;
141
142
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_error_for_division_by_zero
143
    const SQL_MODE_ERROR_FOR_DIVISION_BY_ZERO = 4;
144
145
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_high_not_precedence
146
    const SQL_MODE_HIGH_NOT_PRECEDENCE = 8;
147
148
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ignore_space
149
    const SQL_MODE_IGNORE_SPACE = 16;
150
151
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_auto_create_user
152
    const SQL_MODE_NO_AUTO_CREATE_USER = 32;
153
154
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_auto_value_on_zero
155
    const SQL_MODE_NO_AUTO_VALUE_ON_ZERO = 64;
156
157
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_backslash_escapes
158
    const SQL_MODE_NO_BACKSLASH_ESCAPES = 128;
159
160
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_dir_in_create
161
    const SQL_MODE_NO_DIR_IN_CREATE = 256;
162
163
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_dir_in_create
164
    const SQL_MODE_NO_ENGINE_SUBSTITUTION = 512;
165
166
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_field_options
167
    const SQL_MODE_NO_FIELD_OPTIONS = 1024;
168
169
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_key_options
170
    const SQL_MODE_NO_KEY_OPTIONS = 2048;
171
172
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_table_options
173
    const SQL_MODE_NO_TABLE_OPTIONS = 4096;
174
175
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_unsigned_subtraction
176
    const SQL_MODE_NO_UNSIGNED_SUBTRACTION = 8192;
177
178
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_zero_date
179
    const SQL_MODE_NO_ZERO_DATE = 16384;
180
181
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_zero_in_date
182
    const SQL_MODE_NO_ZERO_IN_DATE = 32768;
183
184
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_only_full_group_by
185
    const SQL_MODE_ONLY_FULL_GROUP_BY = 65536;
186
187
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_pipes_as_concat
188
    const SQL_MODE_PIPES_AS_CONCAT = 131072;
189
190
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_real_as_float
191
    const SQL_MODE_REAL_AS_FLOAT = 262144;
192
193
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_all_tables
194
    const SQL_MODE_STRICT_ALL_TABLES = 524288;
195
196
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_trans_tables
197
    const SQL_MODE_STRICT_TRANS_TABLES = 1048576;
198
199
    // Custom modes.
200
201
    // The table and column names and any other field that must be escaped will
202
    // not be.
203
    // Reserved keywords are being escaped regardless this mode is used or not.
204
    const SQL_MODE_NO_ENCLOSING_QUOTES = 1073741824;
205
206
    /*
207
     * Combination SQL Modes
208
     * https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-combo
209
     */
210
211
    // REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE
212
    const SQL_MODE_ANSI = 393234;
213
214
    // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
215
    // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS,
216
    const SQL_MODE_DB2 = 138258;
217
218
    // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
219
    // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER
220
    const SQL_MODE_MAXDB = 138290;
221
222
    // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
223
    // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS
224
    const SQL_MODE_MSSQL = 138258;
225
226
    // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
227
    // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER
228
    const SQL_MODE_ORACLE = 138290;
229
230
    // PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS,
231
    // NO_TABLE_OPTIONS, NO_FIELD_OPTIONS
232
    const SQL_MODE_POSTGRESQL = 138258;
233
234
    // STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE,
235
    // ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER
236
    const SQL_MODE_TRADITIONAL = 1622052;
237
238
    // -------------------------------------------------------------------------
239
    // Keyword.
240
241
    /**
242
     * Checks if the given string is a keyword.
243
     *
244
     * @param string $str        string to be checked
245
     * @param bool   $isReserved checks if the keyword is reserved
246
     *
247
     * @return int
248
     */
249 363
    public static function isKeyword($str, $isReserved = false)
250
    {
251 363
        $str = strtoupper($str);
252
253 363
        if (isset(static::$KEYWORDS[$str])) {
254 347
            if ($isReserved) {
255 1
                if (!(static::$KEYWORDS[$str] & Token::FLAG_KEYWORD_RESERVED)) {
256 1
                    return null;
257
                }
258
            }
259
260 347
            return static::$KEYWORDS[$str];
261
        }
262
263 360
        return null;
264
    }
265
266
    // -------------------------------------------------------------------------
267
    // Operator.
268
269
    /**
270
     * Checks if the given string is an operator.
271
     *
272
     * @param string $str string to be checked
273
     *
274
     * @return int the appropriate flag for the operator
275
     */
276 372
    public static function isOperator($str)
277
    {
278 372
        if (!isset(static::$OPERATORS[$str])) {
279 370
            return null;
280
        }
281
282 273
        return static::$OPERATORS[$str];
283
    }
284
285
    // -------------------------------------------------------------------------
286
    // Whitespace.
287
288
    /**
289
     * Checks if the given character is a whitespace.
290
     *
291
     * @param string $str string to be checked
292
     *
293
     * @return bool
294
     */
295 377
    public static function isWhitespace($str)
296
    {
297 377
        return ($str === ' ') || ($str === "\r") || ($str === "\n") || ($str === "\t");
298
    }
299
300
    // -------------------------------------------------------------------------
301
    // Comment.
302
303
    /**
304
     * Checks if the given string is the beginning of a whitespace.
305
     *
306
     * @param string $str string to be checked
307
     *
308
     * @return int the appropriate flag for the comment type
309
     */
310 372
    public static function isComment($str, $end=false)
311
    {
312 372
        $len = strlen($str);
313 372
        if ($len == 0) {
314
            return null;
315
        }
316 372
        if ($str[0] === '#') {
317 4
            return Token::FLAG_COMMENT_BASH;
318 372
        } elseif (($len > 1) && ($str[0] === '/') && ($str[1] === '*')) {
319 24
            return (($len > 2) && ($str[2] == '!')) ?
320 24
                Token::FLAG_COMMENT_MYSQL_CMD : Token::FLAG_COMMENT_C;
321 372
        } elseif (($len > 1) && ($str[0] === '*') && ($str[1] === '/')) {
322 3
            return Token::FLAG_COMMENT_C;
323 372
        } elseif (($len > 2) && ($str[0] === '-')
324 372
            && ($str[1] === '-') && (static::isWhitespace($str[2]))
325
        ) {
326 7
            return Token::FLAG_COMMENT_SQL;
327 372
        } elseif (($len == 2) && $end && ($str[0] === '-') && ($str[1] === '-')) {
328 1
            return Token::FLAG_COMMENT_SQL;
329
        }
330
331 372
        return null;
332
    }
333
334
    // -------------------------------------------------------------------------
335
    // Bool.
336
337
    /**
338
     * Checks if the given string is a boolean value.
339
     * This actually check only for `TRUE` and `FALSE` because `1` or `0` are
340
     * actually numbers and are parsed by specific methods.
341
     *
342
     * @param string $str string to be checked
343
     *
344
     * @return bool
345
     */
346 363
    public static function isBool($str)
347
    {
348 363
        $str = strtoupper($str);
349
350 363
        return ($str === 'TRUE') || ($str === 'FALSE');
351
    }
352
353
    // -------------------------------------------------------------------------
354
    // Number.
355
356
    /**
357
     * Checks if the given character can be a part of a number.
358
     *
359
     * @param string $str string to be checked
360
     *
361
     * @return bool
362
     */
363 1
    public static function isNumber($str)
364
    {
365 1
        return (($str >= '0') && ($str <= '9')) || ($str === '.')
366 1
            || ($str === '-') || ($str === '+') || ($str === 'e') || ($str === 'E');
367
    }
368
369
    // -------------------------------------------------------------------------
370
    // Symbol.
371
372
    /**
373
     * Checks if the given character is the beginning of a symbol. A symbol
374
     * can be either a variable or a field name.
375
     *
376
     * @param string $str string to be checked
377
     *
378
     * @return int the appropriate flag for the symbol type
379
     */
380 363 View Code Duplication
    public static function isSymbol($str)
381
    {
382 363
        if (strlen($str) == 0) {
383
            return null;
384
        }
385 363
        if ($str[0] === '@') {
386 29
            return Token::FLAG_SYMBOL_VARIABLE;
387 362
        } elseif ($str[0] === '`') {
388 77
            return Token::FLAG_SYMBOL_BACKTICK;
389
        }
390
391 362
        return null;
392
    }
393
394
    // -------------------------------------------------------------------------
395
    // String.
396
397
    /**
398
     * Checks if the given character is the beginning of a string.
399
     *
400
     * @param string $str string to be checked
401
     *
402
     * @return int the appropriate flag for the string type
403
     */
404 363 View Code Duplication
    public static function isString($str)
405
    {
406 363
        if (strlen($str) == 0) {
407
            return null;
408
        }
409 363
        if ($str[0] === '\'') {
410 68
            return Token::FLAG_STRING_SINGLE_QUOTES;
411 363
        } elseif ($str[0] === '"') {
412 48
            return Token::FLAG_STRING_DOUBLE_QUOTES;
413
        }
414
415 363
        return null;
416
    }
417
418
    // -------------------------------------------------------------------------
419
    // Delimiter.
420
421
    /**
422
     * Checks if the given character can be a separator for two lexeme.
423
     *
424
     * @param string $str string to be checked
425
     *
426
     * @return bool
427
     */
428 363
    public static function isSeparator($str)
429
    {
430
        // NOTES:   Only non alphanumeric ASCII characters may be separators.
431
        //          `~` is the last printable ASCII character.
432 363
        return ($str <= '~') && ($str !== '_')
433 363
            && (($str < '0') || ($str > '9'))
434 363
            && (($str < 'a') || ($str > 'z'))
435 363
            && (($str < 'A') || ($str > 'Z'));
436
    }
437
438
    /**
439
     * Loads the specified context.
440
     *
441
     * Contexts may be used by accessing the context directly.
442
     *
443
     * @param string $context name of the context or full class name that
444
     *                        defines the context
445
     *
446
     * @throws \Exception if the specified context doesn't exist
447
     */
448 2
    public static function load($context = '')
449
    {
450 2
        if (empty($context)) {
451 1
            $context = self::$defaultContext;
452
        }
453 2
        if ($context[0] !== '\\') {
454
            // Short context name (must be formatted into class name).
455 2
            $context = self::$contextPrefix . $context;
456
        }
457 2
        if (!class_exists($context)) {
458 2
            throw new \Exception(
459 2
                'Specified context ("' . $context . '") does not exist.'
460
            );
461
        }
462 1
        self::$loadedContext = $context;
463 1
        self::$KEYWORDS = $context::$KEYWORDS;
464 1
    }
465
466
    /**
467
     * Loads the context with the closest version to the one specified.
468
     *
469
     * The closest context is found by replacing last digits with zero until one
470
     * is loaded successfully.
471
     *
472
     * @see Context::load()
473
     *
474
     * @param string $context name of the context or full class name that
475
     *                        defines the context
476
     *
477
     * @return string The loaded context. `null` if no context was loaded.
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
478
     */
479 1
    public static function loadClosest($context = '')
480
    {
481
        /**
482
         * The number of replaces done by `preg_replace`.
483
         * This actually represents whether a new context was generated or not.
484
         *
485
         * @var int
486
         */
487 1
        $count = 0;
488
489
        // As long as a new context can be generated, we try to load it.
490
        do {
491
            try {
492
                // Trying to load the new context.
493 1
                static::load($context);
494 1
            } catch (\Exception $e) {
495
                // If it didn't work, we are looking for a new one and skipping
496
                // over to the next generation that will try the new context.
497 1
                $context = preg_replace(
498 1
                    '/[1-9](0*)$/',
499 1
                    '0$1',
500
                    $context,
501 1
                    -1,
502
                    $count
503
                );
504 1
                continue;
505
            }
506
507
            // Last generated context was valid (did not throw any exceptions).
508
            // So we return it, to let the user know what context was loaded.
509 1
            return $context;
510 1
        } while ($count !== 0);
511
512 1
        return null;
513
    }
514
515
    /**
516
     * Sets the SQL mode.
517
     *
518
     * @param string $mode The list of modes. If empty, the mode is reset.
519
     */
520 2
    public static function setMode($mode = '')
521
    {
522 2
        static::$MODE = 0;
523 2
        if (empty($mode)) {
524 2
            return;
525
        }
526 2
        $mode = explode(',', $mode);
527 2
        foreach ($mode as $m) {
528 2
            static::$MODE |= constant('static::SQL_MODE_' . $m);
529
        }
530 2
    }
531
532
    /**
533
     * Escapes the symbol by adding surrounding backticks.
534
     *
535
     * @param array|string $str   the string to be escaped
536
     * @param string       $quote quote to be used when escaping
537
     *
538
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
539
     */
540 19
    public static function escape($str, $quote = '`')
541
    {
542 19
        if (is_array($str)) {
543 11
            foreach ($str as $key => $value) {
544 11
                $str[$key] = static::escape($value);
545
            }
546
547 11
            return $str;
548
        }
549
550 19
        if ((static::$MODE & self::SQL_MODE_NO_ENCLOSING_QUOTES)
551 19
            && (!static::isKeyword($str, true))
552
        ) {
553 1
            return $str;
554
        }
555
556 19
        if (static::$MODE & self::SQL_MODE_ANSI_QUOTES) {
557 1
            $quote = '"';
558
        }
559
560 19
        return $quote . str_replace($quote, $quote . $quote, $str) . $quote;
561
    }
562
}
563
564
// Initializing the default context.
565
Context::load();
566