Completed
Push — master ( 65f66e...428edc )
by Michal
04:14
created

Context   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 533
Duplicated Lines 3.75 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 20
loc 533
ccs 103
cts 103
cp 1
rs 5.8893
c 0
b 0
f 0
wmc 63
lcom 2
cbo 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isKeyword() 0 16 4
A isOperator() 0 8 2
A isWhitespace() 0 4 4
B isComment() 0 18 14
A isBool() 0 6 2
C isNumber() 0 5 7
A isSymbol() 10 10 3
A isString() 10 10 3
B isSeparator() 0 9 8
A load() 0 17 4
B loadClosest() 0 35 3
A setMode() 0 11 3
B escape() 0 22 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Context often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Context, and based on these observations, apply Extract Interface, too.

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 COMPAT_MYSQL = 2;
135
136
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_allow_invalid_dates
137
    const ALLOW_INVALID_DATES = 1;
138
139
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ansi_quotes
140
    const 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 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 HIGH_NOT_PRECEDENCE = 8;
147
148
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_ignore_space
149
    const IGNORE_SPACE = 16;
150
151
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_auto_create_user
152
    const 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 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 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 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 NO_ENGINE_SUBSTITUTION = 512;
165
166
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_field_options
167
    const NO_FIELD_OPTIONS = 1024;
168
169
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_key_options
170
    const NO_KEY_OPTIONS = 2048;
171
172
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_table_options
173
    const NO_TABLE_OPTIONS = 4096;
174
175
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_unsigned_subtraction
176
    const NO_UNSIGNED_SUBTRACTION = 8192;
177
178
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_no_zero_date
179
    const 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 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 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 PIPES_AS_CONCAT = 131072;
189
190
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_real_as_float
191
    const REAL_AS_FLOAT = 262144;
192
193
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_all_tables
194
    const STRICT_ALL_TABLES = 524288;
195
196
    // https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sqlmode_strict_trans_tables
197
    const 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 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 339
    public static function isKeyword($str, $isReserved = false)
250
    {
251 339
        $str = strtoupper($str);
252
253 339
        if (isset(static::$KEYWORDS[$str])) {
254 323
            if ($isReserved) {
255 1
                if (!(static::$KEYWORDS[$str] & Token::FLAG_KEYWORD_RESERVED)) {
256 1
                    return null;
257
                }
258 1
            }
259
260 323
            return static::$KEYWORDS[$str];
261
        }
262
263 336
        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 348
    public static function isOperator($str)
277
    {
278 348
        if (!isset(static::$OPERATORS[$str])) {
279 346
            return null;
280
        }
281
282 257
        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 353
    public static function isWhitespace($str)
296
    {
297 353
        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 348
    public static function isComment($str)
311
    {
312 348
        $len = strlen($str);
313 348
        if ($str[0] === '#') {
314 4
            return Token::FLAG_COMMENT_BASH;
315 348
        } elseif (($len > 1) && ($str[0] === '/') && ($str[1] === '*')) {
316 23
            return (($len > 2) && ($str[2] == '!')) ?
317 23
                Token::FLAG_COMMENT_MYSQL_CMD : Token::FLAG_COMMENT_C;
318 348
        } elseif (($len > 1) && ($str[0] === '*') && ($str[1] === '/')) {
319 3
            return Token::FLAG_COMMENT_C;
320 348
        } elseif (($len > 2) && ($str[0] === '-')
321 348
            && ($str[1] === '-') && (static::isWhitespace($str[2]))
322 348
        ) {
323 7
            return Token::FLAG_COMMENT_SQL;
324
        }
325
326 348
        return null;
327
    }
328
329
    // -------------------------------------------------------------------------
330
    // Bool.
331
332
    /**
333
     * Checks if the given string is a boolean value.
334
     * This actually check only for `TRUE` and `FALSE` because `1` or `0` are
335
     * actually numbers and are parsed by specific methods.
336
     *
337
     * @param string $str string to be checked
338
     *
339
     * @return bool
340
     */
341 339
    public static function isBool($str)
342
    {
343 339
        $str = strtoupper($str);
344
345 339
        return ($str === 'TRUE') || ($str === 'FALSE');
346
    }
347
348
    // -------------------------------------------------------------------------
349
    // Number.
350
351
    /**
352
     * Checks if the given character can be a part of a number.
353
     *
354
     * @param string $str string to be checked
355
     *
356
     * @return bool
357
     */
358 1
    public static function isNumber($str)
359
    {
360 1
        return (($str >= '0') && ($str <= '9')) || ($str === '.')
361 1
            || ($str === '-') || ($str === '+') || ($str === 'e') || ($str === 'E');
362
    }
363
364
    // -------------------------------------------------------------------------
365
    // Symbol.
366
367
    /**
368
     * Checks if the given character is the beginning of a symbol. A symbol
369
     * can be either a variable or a field name.
370
     *
371
     * @param string $str string to be checked
372
     *
373
     * @return int the appropriate flag for the symbol type
374
     */
375 339 View Code Duplication
    public static function isSymbol($str)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
    {
377 339
        if ($str[0] === '@') {
378 22
            return Token::FLAG_SYMBOL_VARIABLE;
379 338
        } elseif ($str[0] === '`') {
380 76
            return Token::FLAG_SYMBOL_BACKTICK;
381
        }
382
383 338
        return null;
384
    }
385
386
    // -------------------------------------------------------------------------
387
    // String.
388
389
    /**
390
     * Checks if the given character is the beginning of a string.
391
     *
392
     * @param string $str string to be checked
393
     *
394
     * @return int the appropriate flag for the string type
395
     */
396 339 View Code Duplication
    public static function isString($str)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
397
    {
398 339
        if ($str[0] === '\'') {
399 51
            return Token::FLAG_STRING_SINGLE_QUOTES;
400 339
        } elseif ($str[0] === '"') {
401 47
            return Token::FLAG_STRING_DOUBLE_QUOTES;
402
        }
403
404 339
        return null;
405
    }
406
407
    // -------------------------------------------------------------------------
408
    // Delimiter.
409
410
    /**
411
     * Checks if the given character can be a separator for two lexeme.
412
     *
413
     * @param string $str string to be checked
414
     *
415
     * @return bool
416
     */
417 339
    public static function isSeparator($str)
418
    {
419
        // NOTES:   Only non alphanumeric ASCII characters may be separators.
420
        //          `~` is the last printable ASCII character.
421 339
        return ($str <= '~') && ($str !== '_')
422 339
            && (($str < '0') || ($str > '9'))
423 339
            && (($str < 'a') || ($str > 'z'))
424 339
            && (($str < 'A') || ($str > 'Z'));
425
    }
426
427
    /**
428
     * Loads the specified context.
429
     *
430
     * Contexts may be used by accessing the context directly.
431
     *
432
     * @param string $context name of the context or full class name that
433
     *                        defines the context
434
     *
435
     * @throws \Exception if the specified context doesn't exist
436
     */
437 3
    public static function load($context = '')
438
    {
439 3
        if (empty($context)) {
440 2
            $context = self::$defaultContext;
441 2
        }
442 3
        if ($context[0] !== '\\') {
443
            // Short context name (must be formatted into class name).
444 2
            $context = self::$contextPrefix . $context;
445 2
        }
446 3
        if (!class_exists($context)) {
447 2
            throw new \Exception(
448 2
                'Specified context ("' . $context . '") does not exist.'
449 2
            );
450
        }
451 2
        self::$loadedContext = $context;
452 2
        self::$KEYWORDS = $context::$KEYWORDS;
453 2
    }
454
455
    /**
456
     * Loads the context with the closest version to the one specified.
457
     *
458
     * The closest context is found by replacing last digits with zero until one
459
     * is loaded successfully.
460
     *
461
     * @see Context::load()
462
     *
463
     * @param string $context name of the context or full class name that
464
     *                        defines the context
465
     *
466
     * @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...
467
     */
468 1
    public static function loadClosest($context = '')
469
    {
470
        /**
471
         * The number of replaces done by `preg_replace`.
472
         * This actually represents whether a new context was generated or not.
473
         *
474
         * @var int
475
         */
476 1
        $count = 0;
477
478
        // As long as a new context can be generated, we try to load it.
479
        do {
480
            try {
481
                // Trying to load the new context.
482 1
                static::load($context);
483 1
            } catch (\Exception $e) {
484
                // If it didn't work, we are looking for a new one and skipping
485
                // over to the next generation that will try the new context.
486 1
                $context = preg_replace(
487 1
                    '/[1-9](0*)$/',
488 1
                    '0$1',
489 1
                    $context,
490 1
                    -1,
491
                    $count
492 1
                );
493 1
                continue;
494
            }
495
496
            // Last generated context was valid (did not throw any exceptions).
497
            // So we return it, to let the user know what context was loaded.
498 1
            return $context;
499 1
        } while ($count !== 0);
500
501 1
        return null;
502
    }
503
504
    /**
505
     * Sets the SQL mode.
506
     *
507
     * @param string $mode The list of modes. If empty, the mode is reset.
508
     */
509 2
    public static function setMode($mode = '')
510
    {
511 2
        static::$MODE = 0;
512 2
        if (empty($mode)) {
513 2
            return;
514
        }
515 2
        $mode = explode(',', $mode);
516 2
        foreach ($mode as $m) {
517 2
            static::$MODE |= constant('static::' . $m);
518 2
        }
519 2
    }
520
521
    /**
522
     * Escapes the symbol by adding surrounding backticks.
523
     *
524
     * @param array|string $str   the string to be escaped
525
     * @param string       $quote quote to be used when escaping
526
     *
527
     * @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...
528
     */
529 19
    public static function escape($str, $quote = '`')
530
    {
531 19
        if (is_array($str)) {
532 11
            foreach ($str as $key => $value) {
533 11
                $str[$key] = static::escape($value);
534 11
            }
535
536 11
            return $str;
537
        }
538
539 19
        if ((static::$MODE & self::NO_ENCLOSING_QUOTES)
540 19
            && (!static::isKeyword($str, true))
541 19
        ) {
542 1
            return $str;
543
        }
544
545 19
        if (static::$MODE & self::ANSI_QUOTES) {
546 1
            $quote = '"';
547 1
        }
548
549 19
        return $quote . str_replace($quote, $quote . $quote, $str) . $quote;
550
    }
551
}
552
553
// Initializing the default context.
554
Context::load();
555