Completed
Push — master ( de8b3e...66b528 )
by Dan
03:08
created

Expression::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 9
nc 2
nop 4
crap 3
1
<?php
2
3
/**
4
 * Parses a reference to an expression (column, table or database name, function
5
 * call, mathematical expression, etc.).
6
 *
7
 * @package    SqlParser
8
 * @subpackage Components
9
 */
10
namespace SqlParser\Components;
11
12
use SqlParser\Context;
13
use SqlParser\Component;
14
use SqlParser\Parser;
15
use SqlParser\Token;
16
use SqlParser\TokensList;
17
18
/**
19
 * Parses a reference to an expression (column, table or database name, function
20
 * call, mathematical expression, etc.).
21
 *
22
 * @category   Components
23
 * @package    SqlParser
24
 * @subpackage Components
25
 * @author     Dan Ungureanu <[email protected]>
26
 * @license    http://opensource.org/licenses/GPL-2.0 GNU Public License
27
 */
28
class Expression extends Component
0 ignored issues
show
Coding Style introduced by
The property $ALLOWED_KEYWORDS is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
29
{
30
31
    /**
32
     * List of allowed reserved keywords in expressions.
33
     *
34
     * @var array
35
     */
36
    private static $ALLOWED_KEYWORDS = array(
37
        'AS' => 1, 'DUAL' => 1, 'NULL' => 1, 'REGEXP' => 1
38
    );
39
40
    /**
41
     * The name of this database.
42
     *
43
     * @var string
44
     */
45
    public $database;
46
47
    /**
48
     * The name of this table.
49
     *
50
     * @var string
51
     */
52
    public $table;
53
54
    /**
55
     * The name of the column.
56
     *
57
     * @var string
58
     */
59
    public $column;
60
61
    /**
62
     * The sub-expression.
63
     *
64
     * @var string
65
     */
66
    public $expr = '';
67
68
    /**
69
     * The alias of this expression.
70
     *
71
     * @var string
72
     */
73
    public $alias;
74
75
    /**
76
     * The name of the function.
77
     *
78
     * @var mixed
79
     */
80
    public $function;
81
82
    /**
83
     * The type of subquery.
84
     *
85
     * @var string
86
     */
87
    public $subquery;
88
89
    /**
90
     * Constructor.
91
     *
92
     * Syntax:
93
     *     new Expression('expr')
94
     *     new Expression('expr', 'alias')
95
     *     new Expression('database', 'table', 'column')
96
     *     new Expression('database', 'table', 'column', 'alias')
97
     *
98
     * If the database, table or column name is not required, pass an empty
99
     * string.
100
     *
101
     * @param string $database The name of the database or the the expression.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $database not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
102
     *                          the the expression.
103
     * @param string $table    The name of the table or the alias of the expression.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $table not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
104
     *                          the alias of the expression.
105
     * @param string $column   The name of the column.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $column not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
106
     * @param string $alias    The name of the alias.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $alias not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
107
     */
108 139
    public function __construct($database = null, $table = null, $column = null, $alias = null)
109
    {
110 139
        if (($column === null) && ($alias === null)) {
111 139
            $this->expr = $database; // case 1
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
112 139
            $this->alias = $table; // case 2
113 139
        } else {
114 2
            $this->database = $database; // case 3
115 2
            $this->table = $table; // case 3
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
116 2
            $this->column = $column; // case 3
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
117 2
            $this->alias = $alias; // case 4
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
118
        }
119 139
    }
120
121
    /**
122
     * Possible options:
123
     *
124
     *      `field`
125
     *
126
     *          First field to be filled.
127
     *          If this is not specified, it takes the value of `parseField`.
128
     *
129
     *      `parseField`
130
     *
131
     *          Specifies the type of the field parsed. It may be `database`,
132
     *          `table` or `column`. These expressions may not include
133
     *          parentheses.
134
     *
135
     *      `breakOnAlias`
136
     *
137
     *          If not empty, breaks when the alias occurs (it is not included).
138
     *
139
     *      `breakOnParentheses`
140
     *
141
     *          If not empty, breaks when the first parentheses occurs.
142
     *
143
     *      `parenthesesDelimited`
144
     *
145
     *          If not empty, breaks after last parentheses occurred.
146
     *
147
     * @param Parser     $parser  The parser that serves as context.
148
     * @param TokensList $list    The list of tokens that are being parsed.
149
     * @param array      $options Parameters for parsing.
150
     *
151
     * @return Expression
0 ignored issues
show
Documentation introduced by
Should the return type not be Expression|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...
152
     */
153 134
    public static function parse(Parser $parser, TokensList $list, array $options = array())
154
    {
155 134
        $ret = new Expression();
156
157
        /**
158
         * Whether current tokens make an expression or a table reference.
159
         *
160
         * @var bool $isExpr
161
         */
162 134
        $isExpr = false;
163
164
        /**
165
         * Whether a period was previously found.
166
         *
167
         * @var bool $dot
168
         */
169 134
        $dot = false;
170
171
        /**
172
         * Whether an alias is expected. Is 2 if `AS` keyword was found.
173
         *
174
         * @var bool $alias
175
         */
176 134
        $alias = false;
177
178
        /**
179
         * Counts brackets.
180
         *
181
         * @var int $brackets
182
         */
183 134
        $brackets = 0;
184
185
        /**
186
         * Keeps track of the last two previous tokens.
187
         *
188
         * @var Token[] $prev
189
         */
190 134
        $prev = array(null, null);
191
192
        // When a field is parsed, no parentheses are expected.
193 134
        if (!empty($options['parseField'])) {
194 113
            $options['breakOnParentheses'] = true;
195 113
            $options['field'] = $options['parseField'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 14 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
196 113
        }
197
198 134
        for (; $list->idx < $list->count; ++$list->idx) {
199
200
            /**
201
             * Token parsed at this moment.
202
             *
203
             * @var Token $token
204
             */
205 134
            $token = $list->tokens[$list->idx];
206
207
            // End of statement.
208 134
            if ($token->type === Token::TYPE_DELIMITER) {
209 47
                break;
210
            }
211
212
            // Skipping whitespaces and comments.
213 133
            if (($token->type === Token::TYPE_WHITESPACE)
214 133
                || ($token->type === Token::TYPE_COMMENT)
215 133
            ) {
216 93
                if ($isExpr) {
217 48
                    $ret->expr .= $token->token;
218 48
                }
219 93
                continue;
220
            }
221
222 133
            if ($token->type === Token::TYPE_KEYWORD) {
223 80
                if (($brackets > 0) && (empty($ret->subquery))
224 80
                    && (!empty(Parser::$STATEMENT_PARSERS[$token->value]))
225 80
                ) {
226
                    // A `(` was previously found and this keyword is the
227
                    // beginning of a statement, so this is a subquery.
228 6
                    $ret->subquery = $token->value;
229 80
                } elseif (($token->flags & Token::FLAG_KEYWORD_FUNCTION)
230 79
                    && (empty($options['parseField']))
231 79
                ) {
232 13
                    $isExpr = true;
233 79
                } elseif (($token->flags & Token::FLAG_KEYWORD_RESERVED)
234 75
                    && ($brackets === 0)
235 75
                ) {
236 72
                    if (empty(self::$ALLOWED_KEYWORDS[$token->value])) {
237
                        // A reserved keyword that is not allowed in the
238
                        // expression was found so the expression must have
239
                        // ended and a new clause is starting.
240 68
                        break;
241
                    }
242 14 View Code Duplication
                    if ($token->value === 'AS') {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
243 9
                        if (!empty($options['breakOnAlias'])) {
244 1
                            break;
245
                        }
246 8
                        if (!empty($ret->alias)) {
247 1
                            $parser->error(
248 1
                                __('An alias was previously found.'),
249
                                $token
250 1
                            );
251 1
                            break;
252
                        }
253 8
                        $alias = true;
254 8
                        continue;
255
                    }
256 5
                    $isExpr = true;
257 5
                }
258 26
            }
259
260 131
            if (($token->type === Token::TYPE_NUMBER)
261 127
                || ($token->type === Token::TYPE_BOOL)
262 127
                || (($token->type === Token::TYPE_SYMBOL)
263 127
                && ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
264 127
                || (($token->type === Token::TYPE_OPERATOR)
265 127
                && ($token->value !== '.'))
266 131
            ) {
267 96
                if (!empty($options['parseField'])) {
268 46
                    break;
269
                }
270
271
                // Numbers, booleans and operators (except dot) are usually part
272
                // of expressions.
273 67
                $isExpr = true;
274 67
            }
275
276 131
            if ($token->type === Token::TYPE_OPERATOR) {
277 63
                if ((!empty($options['breakOnParentheses']))
278 63
                    && (($token->value === '(') || ($token->value === ')'))
279 63
                ) {
280
                    // No brackets were expected.
281 2
                    break;
282
                }
283 62
                if ($token->value === '(') {
284 21
                    ++$brackets;
285 21
                    if ((empty($ret->function)) && ($prev[1] !== null)
286 21
                        && (($prev[1]->type === Token::TYPE_NONE)
287 7
                        || ($prev[1]->type === Token::TYPE_SYMBOL)
288 7
                        || (($prev[1]->type === Token::TYPE_KEYWORD)
289 7
                        && ($prev[1]->flags & Token::FLAG_KEYWORD_FUNCTION)))
290 21
                    ) {
291 7
                        $ret->function = $prev[1]->value;
292 7
                    }
293 62
                } elseif ($token->value === ')') {
294 24
                    --$brackets;
295 24
                    if ($brackets === 0) {
296 21
                        if (!empty($options['parenthesesDelimited'])) {
297
                            // The current token is the last bracket, the next
298
                            // one will be outside the expression.
299 7
                            $ret->expr .= $token->token;
300 7
                            ++$list->idx;
301 7
                            break;
302
                        }
303 19
                    } elseif ($brackets < 0) {
304
                        // $parser->error(__('Unexpected closing bracket.'), $token);
1 ignored issue
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
305
                        // $brackets = 0;
1 ignored issue
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
306 3
                        break;
307
                    }
308 56
                } elseif ($token->value === ',') {
309
                    // Expressions are comma-delimited.
310 29
                    if ($brackets === 0) {
311 25
                        break;
312
                    }
313 4
                }
314 48
            }
315
316
            // Saving the previous tokens.
317 130
            $prev[0] = $prev[1];
318 130
            $prev[1] = $token;
319
320 130
            if ($alias) {
321
                // An alias is expected (the keyword `AS` was previously found).
322 8
                if (!empty($ret->alias)) {
323
                    $parser->error(__('An alias was previously found.'), $token);
324
                    break;
325
                }
326 8
                $ret->alias = $token->value;
327 8
                $alias = false;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
328 130
            } elseif ($isExpr) {
329
                // Handling aliases.
330 60
                if (/* (empty($ret->alias)) && */ ($brackets === 0)
1 ignored issue
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
331 60
                    && (($prev[0] === null)
332 20
                    || ((($prev[0]->type !== Token::TYPE_OPERATOR)
333 10
                    || ($prev[0]->token === ')'))
334 20
                    && (($prev[0]->type !== Token::TYPE_KEYWORD)
335 20
                    || (!($prev[0]->flags & Token::FLAG_KEYWORD_RESERVED)))))
336 60
                    && (($prev[1]->type === Token::TYPE_STRING)
337 52
                    || (($prev[1]->type === Token::TYPE_SYMBOL)
338 52
                    && (!($prev[1]->flags & Token::FLAG_SYMBOL_VARIABLE)))
339 52
                    || ($prev[1]->type === Token::TYPE_NONE))
340 60
                ) {
341 2
                    if (!empty($ret->alias)) {
342 1
                        $parser->error(__('An alias was previously found.'), $token);
343 1
                        break;
344
                    }
345 2
                    $ret->alias = $prev[1]->value;
346 2
                } else {
347 60
                    $ret->expr .= $token->token;
348
                }
349 130
            } elseif (!$isExpr) {
350 115
                if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
351
                    // Found a `.` which means we expect a column name and
352
                    // the column name we parsed is actually the table name
353
                    // and the table name is actually a database name.
354 13
                    if ((!empty($ret->database)) || ($dot)) {
355 2
                        $parser->error(__('Unexpected dot.'), $token);
356 2
                    }
357 13
                    $ret->database = $ret->table;
358 13
                    $ret->table = $ret->column;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
359 13
                    $ret->column = null;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
360 13
                    $dot = true;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
361 13
                    $ret->expr .= $token->token;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
362 13
                } else {
363 115
                    $field = empty($options['field']) ? 'column' : $options['field'];
364 115
                    if (empty($ret->$field)) {
365 115
                        $ret->$field = $token->value;
366 115
                        $ret->expr .= $token->token;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
367 115
                        $dot = false;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
368 115 View Code Duplication
                    } else {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
369
                        // No alias is expected.
370 10
                        if (!empty($options['breakOnAlias'])) {
371 2
                            break;
372
                        }
373 8
                        if (!empty($ret->alias)) {
374 1
                            $parser->error(__('An alias was previously found.'), $token);
375 1
                            break;
376
                        }
377 8
                        $ret->alias = $token->value;
378
                    }
379
                }
380 115
            }
381 130
        }
382
383 134
        if ($alias) {
384 1
            $parser->error(
385 1
                __('An alias was expected.'),
386 1
                $list->tokens[$list->idx - 1]
387 1
            );
388 1
        }
389
390
        // White-spaces might be added at the end.
391 134
        $ret->expr = trim($ret->expr);
392
393 134
        if (empty($ret->expr)) {
394 11
            return null;
395
        }
396
397 130
        --$list->idx;
398 130
        return $ret;
399
    }
400
401
    /**
402
     * @param Expression|Expression[] $component The component to be built.
403
     * @param array                   $options   Parameters for building.
404
     *
405
     * @return string
406
     */
407 27
    public static function build($component, array $options = array())
408
    {
409 27
        if (is_array($component)) {
410 1
            return implode($component, ', ');
411
        } else {
412 27
            if (!empty($component->expr)) {
413 21
                $ret = $component->expr;
414 21
            } else {
415 8
                $fields = array();
416 8
                if ((isset($component->database)) && ($component->database !== '')) {
417 1
                    $fields[] = $component->database;
418 1
                }
419 8
                if ((isset($component->table)) && ($component->table !== '')) {
420 8
                    $fields[] = $component->table;
421 8
                }
422 8
                if ((isset($component->column)) && ($component->column !== '')) {
423 1
                    $fields[] = $component->column;
424 1
                }
425 8
                $ret = implode('.', Context::escape($fields));
426
            }
427
428 27
            if (!empty($component->alias)) {
429 2
                $ret .= ' AS ' . Context::escape($component->alias);
430 2
            }
431
432 27
            return $ret;
433
        }
434
    }
435
}
436