NodeFactory   F
last analyzed

Complexity

Total Complexity 152

Size/Duplication

Total Lines 791
Duplicated Lines 2.28 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 21
Bugs 3 Features 1
Metric Value
wmc 152
c 21
b 3
f 1
lcom 1
cbo 21
dl 18
loc 791
rs 1.263

8 Methods

Rating   Name   Duplication   Size   Complexity  
F toObject() 18 355 78
B buildFromSubtree() 0 20 6
F simplify() 0 219 45
A getOperatorPrecedence() 0 13 4
A nodeToInstanceDescriptor() 0 12 2
A array_map_deep() 0 17 4
C toSql() 0 38 11
A escapeDBItem() 0 8 2

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 NodeFactory 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 NodeFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * expression-types.php.
5
 *
6
 *
7
 * Copyright (c) 2010-2013, Justin Swanhart
8
 * with contributions by André Rothe <[email protected], [email protected]>
9
 * and David Négrier <[email protected]>
10
 *
11
 * All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without modification,
14
 * are permitted provided that the following conditions are met:
15
 *
16
 *   * Redistributions of source code must retain the above copyright notice,
17
 *     this list of conditions and the following disclaimer.
18
 *   * Redistributions in binary form must reproduce the above copyright notice,
19
 *     this list of conditions and the following disclaimer in the documentation
20
 *     and/or other materials provided with the distribution.
21
 *
22
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
23
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
25
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31
 * DAMAGE.
32
 */
33
namespace SQLParser\Node;
34
35
use Mouf\Database\MagicQueryException;
36
use Mouf\Database\MagicQueryParserException;
37
use SQLParser\SqlRenderInterface;
38
use Doctrine\DBAL\Connection;
39
use Mouf\MoufManager;
40
use SQLParser\Query\StatementFactory;
41
use SQLParser\ExpressionType;
42
43
/**
44
 * This class has the ability to create instances implementing NodeInterface based on a descriptive array.
45
 *
46
 * @author David Négrier <[email protected]>
47
 */
48
class NodeFactory
49
{
50
    public static function toObject(array $desc)
51
    {
52
        if (!isset($desc['expr_type'])) {
53
            throw new \Exception('Invalid array. Could not find expression type: '.var_export($desc, true));
54
        }
55
56
        switch ($desc['expr_type']) {
57
            case ExpressionType::LIMIT_CONST:
58
                if (substr($desc['base_expr'], 0, 1) == ':') {
59
                    $instance = new UnquotedParameter();
60
                    $instance->setName(substr($desc['base_expr'], 1));
61
                } else {
62
                    $instance = new LimitNode();
63
                    $expr = $desc['base_expr'];
64
                    if (strpos($expr, "'") === 0) {
65
                        $expr = substr($expr, 1);
66
                    }
67 View Code Duplication
                    if (strrpos($expr, "'") === strlen($expr) - 1) {
0 ignored issues
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...
68
                        $expr = substr($expr, 0, strlen($expr) - 1);
69
                    }
70
                    $expr = stripslashes($expr);
71
72
                    $instance->setValue($expr);
73
                }
74
                // Debug:
75
                unset($desc['base_expr']);
76
                unset($desc['expr_type']);
77
                unset($desc['sub_tree']);
78
                if (!empty($desc)) {
79
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
80
                }
81
82
                return $instance;
83
            case ExpressionType::CONSTANT:
84
                $const = new ConstNode();
85
                $expr = $desc['base_expr'];
86
                if (strpos($expr, "'") === 0) {
87
                    $expr = substr($expr, 1);
88
                } else {
89
                    $const->setIsString(false);
90
                }
91 View Code Duplication
                if (strrpos($expr, "'") === strlen($expr) - 1) {
0 ignored issues
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...
92
                    $expr = substr($expr, 0, -1);
93
                }
94
                $expr = stripslashes($expr);
95
96
                $const->setValue($expr);
97
98
                // If the constant has an alias, it is declared in the columns section.
99
                // If this is the case, let's wrap it in an "expression"
100
                if (isset($desc['alias'])) {
101
                    $expression = new Expression();
102
                    $expression->setBaseExpression($desc['base_expr']);
103
                    $expression->setSubTree($const);
104
                    $expression->setAlias($desc['alias']['name']);
105
                    $expression->setBrackets(false);
106
107
                    $const = $expression;
108
109
                    unset($desc['alias']);
110
                }
111
112
                // Debug:
113
                unset($desc['base_expr']);
114
                unset($desc['expr_type']);
115
                unset($desc['sub_tree']);
116
117
                if (!empty($desc)) {
118
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
119
                }
120
121
                return $const;
122
123
            case ExpressionType::OPERATOR:
124
                $operator = new Operator();
125
                $operator->setValue($desc['base_expr']);
126
                // Debug:
127
                unset($desc['base_expr']);
128
                unset($desc['expr_type']);
129 View Code Duplication
                if (!empty($desc['sub_tree'])) {
0 ignored issues
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...
130
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
131
                }
132
                unset($desc['sub_tree']);
133
                if (!empty($desc)) {
134
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
135
                }
136
137
                return $operator;
138
139
            case ExpressionType::COLREF:
140
                if (substr($desc['base_expr'], 0, 1) == ':') {
141
                    $instance = new Parameter();
142
                    $instance->setName(substr($desc['base_expr'], 1));
143
                } else {
144
                    $instance = new ColRef();
145
                    $lastDot = strrpos($desc['base_expr'], '.');
146
                    if ($lastDot === false) {
147
                        $instance->setColumn(str_replace('`', '', $desc['base_expr']));
148
                    } else {
149
                        $instance->setColumn(str_replace('`', '', substr($desc['base_expr'], $lastDot + 1)));
150
                        $instance->setTable(str_replace('`', '', substr($desc['base_expr'], 0, $lastDot)));
151
                    }
152
                    if (!empty($desc['alias'])) {
153
                        $instance->setAlias($desc['alias']['name']);
154
                    }
155
156
                    if (!empty($desc['direction'])) {
157
                        $instance->setDirection($desc['direction']);
158
                    }
159
                }
160
161
                // Debug:
162
                unset($desc['direction']);
163
                unset($desc['base_expr']);
164
                unset($desc['expr_type']);
165 View Code Duplication
                if (!empty($desc['sub_tree'])) {
0 ignored issues
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...
166
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
167
                }
168
                unset($desc['sub_tree']);
169
                unset($desc['alias']);
170
                if (!empty($desc)) {
171
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
172
                }
173
174
                return $instance;
175
            case ExpressionType::TABLE:
176
                $expr = new Table();
177
                $expr->setTable(str_replace('`', '', $desc['table']));
178
                switch ($desc['join_type']) {
179
                    case 'CROSS':
180
                        $joinType = 'CROSS JOIN';
181
                        break;
182
                    case 'JOIN':
183
                        $joinType = 'JOIN';
184
                        break;
185
                    case 'LEFT':
186
                        $joinType = 'LEFT JOIN';
187
                        break;
188
                    case 'RIGHT':
189
                        $joinType = 'RIGHT JOIN';
190
                        break;
191
                    case 'INNER':
192
                        $joinType = 'INNER JOIN';
193
                        break;
194
                    case 'OUTER':
195
                        $joinType = 'OUTER JOIN';
196
                        break;
197
                    case 'NATURAL':
198
                        $joinType = 'NATURAL JOIN';
199
                        break;
200
                    case ',':
201
                        $joinType = ',';
202
                        break;
203
                    default:
204
                        throw new \Exception("Unexpected join type: '".$desc['join_type']."'");
205
                }
206
                $expr->setJoinType($joinType);
207
208
                if (isset($desc['alias'])) {
209
                    $expr->setAlias($desc['alias']['name']);
210
                }
211
                $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
212
                if ($subTreeNodes) {
213
                    $expr->setRefClause(self::simplify($subTreeNodes));
214
                }
215
216
                // Debug:
217
                unset($desc['base_expr']);
218
                unset($desc['expr_type']);
219 View Code Duplication
                if (!empty($desc['sub_tree'])) {
0 ignored issues
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...
220
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
221
                }
222
                unset($desc['sub_tree']);
223
                unset($desc['join_type']);
224
                unset($desc['alias']);
225
                unset($desc['table']);
226
                unset($desc['ref_type']);
227
                unset($desc['ref_clause']);
228
                if (!empty($desc)) {
229
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
230
                }
231
232
                return $expr;
233
            case ExpressionType::SUBQUERY:
234
                $expr = new SubQuery();
235
236
                $expr->setSubQuery(self::buildFromSubtree($desc['sub_tree']));
237
238
                if (isset($desc['join_type'])) {
239
                    $expr->setJoinType($desc['join_type']);
240
                }
241
242
                if (isset($desc['alias'])) {
243
                    $expr->setAlias($desc['alias']['name']);
244
                }
245
246
                if (isset($desc['ref_clause'])) {
247
                    $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
248
                    if ($subTreeNodes) {
249
                        $expr->setRefClause(self::simplify($subTreeNodes));
250
                    }
251
                }
252
253
                // Debug:
254
                unset($desc['base_expr']);
255
                unset($desc['expr_type']);
256
                unset($desc['sub_tree']);
257
                unset($desc['join_type']);
258
                unset($desc['alias']);
259
                unset($desc['sub_tree']);
260
                unset($desc['ref_type']);
261
                unset($desc['ref_clause']);
262
                if (!empty($desc)) {
263
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
264
                }
265
266
                return $expr;
267
            case ExpressionType::AGGREGATE_FUNCTION:
268
                $expr = new AggregateFunction();
269
                $expr->setFunctionName($desc['base_expr']);
270
271
                $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
272
273
                if (isset($desc['alias'])) {
274
                    $expr->setAlias($desc['alias']);
275
                }
276
277
                // Debug:
278
                unset($desc['base_expr']);
279
                unset($desc['expr_type']);
280
                unset($desc['sub_tree']);
281
                unset($desc['alias']);
282
                if (!empty($desc)) {
283
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
284
                }
285
286
                return $expr;
287
            case ExpressionType::SIMPLE_FUNCTION:
288
                $expr = new SimpleFunction();
289
                $expr->setBaseExpression($desc['base_expr']);
290
291
                if (isset($desc['sub_tree'])) {
292
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
293
                }
294
295
                if (isset($desc['alias'])) {
296
                    $expr->setAlias($desc['alias']['name']);
297
                }
298
                if (isset($desc['direction'])) {
299
                    $expr->setDirection($desc['direction']);
300
                }
301
302
                // Debug:
303
                unset($desc['base_expr']);
304
                unset($desc['expr_type']);
305
                unset($desc['sub_tree']);
306
                unset($desc['alias']);
307
                unset($desc['direction']);
308
                if (!empty($desc)) {
309
                    throw new \InvalidArgumentException('Unexpected parameters in simple function: '.var_export($desc, true));
310
                }
311
312
                return $expr;
313
            case ExpressionType::RESERVED:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
314
                if (in_array(strtoupper($desc['base_expr']), ['CASE', 'WHEN', 'THEN', 'ELSE', 'END'])) {
315
                    $operator = new Operator();
316
                    $operator->setValue($desc['base_expr']);
317
                    // Debug:
318
                    unset($desc['base_expr']);
319
                    unset($desc['expr_type']);
320 View Code Duplication
                    if (!empty($desc['sub_tree'])) {
0 ignored issues
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...
321
                        throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
322
                    }
323
                    unset($desc['sub_tree']);
324
                    if (!empty($desc)) {
325
                        throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
326
                    }
327
328
                    return $operator;
329
                } else {
330
                    $res = new Reserved();
331
                    $res->setBaseExpression($desc['base_expr']);
332
333
                    if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
334
                        $res->setBrackets(true);
335
                    }
336
337
                    // Debug:
338
                    unset($desc['base_expr']);
339
                    unset($desc['expr_type']);
340
                    unset($desc['sub_tree']);
341
                    unset($desc['alias']);
342
                    unset($desc['direction']);
343
                    if (!empty($desc)) {
344
                        throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
345
                    }
346
347
                    return $res;
348
                }
349
            case ExpressionType::USER_VARIABLE:
350
            case ExpressionType::SESSION_VARIABLE:
351
            case ExpressionType::GLOBAL_VARIABLE:
352
            case ExpressionType::LOCAL_VARIABLE:
353
            case ExpressionType::EXPRESSION:
354
            case ExpressionType::BRACKET_EXPRESSION:
355
            case ExpressionType::TABLE_EXPRESSION:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
356
357
            case ExpressionType::IN_LIST:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
358
359
            case ExpressionType::SIGN:
360
            case ExpressionType::RECORD:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
361
362
            case ExpressionType::MATCH_ARGUMENTS:
363
            case ExpressionType::MATCH_MODE:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
364
365
            case ExpressionType::ALIAS:
366
            case ExpressionType::POSITION:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
367
368
            case ExpressionType::TEMPORARY_TABLE:
369
            case ExpressionType::VIEW:
370
            case ExpressionType::DATABASE:
371
            case ExpressionType::SCHEMA:
372
                $expr = new Expression();
373
                $expr->setBaseExpression($desc['base_expr']);
374
375
                if (isset($desc['sub_tree'])) {
376
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
377
                }
378
379
                if (isset($desc['alias'])) {
380
                    $expr->setAlias($desc['alias']['name']);
381
                }
382
                if (isset($desc['direction'])) {
383
                    $expr->setDirection($desc['direction']);
384
                }
385
386
                if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
387
                    $expr->setBrackets(true);
388
                }
389
390
                // Debug:
391
                unset($desc['base_expr']);
392
                unset($desc['expr_type']);
393
                unset($desc['sub_tree']);
394
                unset($desc['alias']);
395
                unset($desc['direction']);
396
                if (!empty($desc)) {
397
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
398
                }
399
400
                return $expr;
401
            default:
402
                throw new \Exception('Unknown expression type');
403
        }
404
    }
405
406
    private static function buildFromSubtree($subTree)
407
    {
408
        if ($subTree && is_array($subTree)) {
409
            //if (isset($subTree['SELECT'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
85% 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...
410
            // If the subtree is a map instead of a list, we are likely to be on a SUBSELECT statement.
411
            if (!empty($subTree) && !isset($subTree[0])) {
412
                $subTree = StatementFactory::toObject($subTree);
413
            } else {
414
                $subTree = array_map(function ($item) {
415
                    if (is_array($item)) {
416
                        return self::toObject($item);
417
                    } else {
418
                        return $item;
419
                    }
420
                }, $subTree);
421
            }
422
        }
423
424
        return $subTree;
425
    }
426
427
    private static $PRECEDENCE = array(
428
            array('INTERVAL'),
429
            array('BINARY', 'COLLATE'),
430
            array('!'),
431
            array(/*'-'*/ /* (unary minus) ,*/ '~' /*(unary bit inversion)*/),
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
432
            array('^'),
433
            array('*', '/', 'DIV', '%', 'MOD'),
434
            array('-', '+'),
435
            array('<<', '>>'),
436
            array('&'),
437
            array('|'),
438
            array('=' /*(comparison)*/, '<=>', '>=', '>', '<=', '<', '<>', '!=', 'IS', 'LIKE', 'REGEXP', 'IN', 'IS NOT', 'NOT IN'),
439
            array('AND_FROM_BETWEEN'),
440
            array('THEN'),
441
            array('WHEN'),
442
            array('ELSE'),
443
            array('BETWEEN', 'CASE', 'END'),
444
            array('NOT'),
445
            array('&&', 'AND'),
446
            array('XOR'),
447
            array('||', 'OR'), );
448
449
    private static $OPERATOR_TO_CLASS = array(
450
            '=' => 'SQLParser\Node\Equal',
451
            '<' => 'SQLParser\Node\Less',
452
            '>' => 'SQLParser\Node\Greater',
453
            '<=' => 'SQLParser\Node\LessOrEqual',
454
            '=>' => 'SQLParser\Node\GreaterOrEqual',
455
            //'<=>' => '????',
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
456
            '<>' => 'SQLParser\Node\Different',
457
            '!=' => 'SQLParser\Node\Different',
458
            'IS' => 'SQLParser\Node\Is',
459
            'IS NOT' => 'SQLParser\Node\IsNot',
460
            'LIKE' => 'SQLParser\Node\Like',
461
            'REGEXP' => 'SQLParser\Node\Regexp',
462
            'IN' => 'SQLParser\Node\In',
463
            'NOT IN' => 'SQLParser\Node\NotIn',
464
            '+' => 'SQLParser\Node\Plus',
465
            '-' => 'SQLParser\Node\Minus',
466
            '*' => 'SQLParser\Node\Multiply',
467
            '/' => 'SQLParser\Node\Divide',
468
            '%' => 'SQLParser\Node\Modulo',
469
            'MOD' => 'SQLParser\Node\Modulo',
470
            'DIV' => 'SQLParser\Node\Div',
471
            '&' => 'SQLParser\Node\BitwiseAnd',
472
            '|' => 'SQLParser\Node\BitwiseOr',
473
            '^' => 'SQLParser\Node\BitwiseXor',
474
            '<<' => 'SQLParser\Node\ShiftLeft',
475
            '>>' => 'SQLParser\Node\ShiftRight',
476
            '<=>' => 'SQLParser\Node\NullCompatibleEqual',
477
            'AND' => 'SQLParser\Node\AndOp',
478
            '&&' => 'SQLParser\Node\AndOp',
479
            '||' => 'SQLParser\Node\OrOp',
480
            'OR' => 'SQLParser\Node\OrOp',
481
            'XOR' => 'SQLParser\Node\XorOp',
482
            'THEN' => 'SQLParser\Node\Then',
483
            'ELSE' => 'SQLParser\Node\ElseOperation',
484
    );
485
486
    /**
487
     * Takes an array of nodes (including operators) and try to build a tree from it.
488
     *
489
     * @param NodeInterface[]|NodeInterface $nodes
490
     */
491
    public static function simplify($nodes)
492
    {
493
        if (empty($nodes)) {
494
            $nodes = array();
495
        } elseif (!is_array($nodes)) {
496
            $nodes = array($nodes);
497
        }
498
        $minPriority = -1;
499
        $selectedOperators = array();
500
        $lastSelectedOperator = '';
501
        $differentOperatorWithSamePriority = false;
502
503
        // Let's transform "NOT" + "IN" into "NOT IN"
504
        $newNodes = array();
505
        for ($i = 0; $i < count($nodes); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
506
            $node = $nodes[$i];
507
            if ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
508
                    && strtoupper($node->getValue()) == 'IS' && strtoupper($nodes[$i + 1]->getValue()) == 'NOT') {
509
                $notIn = new Operator();
510
                $notIn->setValue('IS NOT');
511
                $newNodes[] = $notIn;
512
                ++$i;
513
            } elseif ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
514
                    && strtoupper($node->getValue()) == 'NOT' && strtoupper($nodes[$i + 1]->getValue()) == 'IN') {
515
                $notIn = new Operator();
516
                $notIn->setValue('NOT IN');
517
                $newNodes[] = $notIn;
518
                ++$i;
519
            } else {
520
                $newNodes[] = $node;
521
            }
522
        }
523
        $nodes = $newNodes;
524
525
        // Let's find the highest level operator.
526
        for ($i = count($nodes) - 1; $i >= 0; --$i) {
527
            $node = $nodes[$i];
528
            if ($node instanceof Operator) {
529
                $priority = self::getOperatorPrecedence($node);
530
531
                if ($priority == $minPriority && $lastSelectedOperator != strtoupper($node->getValue())) {
532
                    $differentOperatorWithSamePriority = true;
533
                } elseif ($priority > $minPriority) {
534
                    $minPriority = $priority;
535
                    $selectedOperators = array($node);
536
                    $lastSelectedOperator = strtoupper($node->getValue());
537
                } else {
538
                    if (strtoupper($node->getValue()) == $lastSelectedOperator && !$differentOperatorWithSamePriority) {
539
                        $selectedOperators[] = $node;
540
                    }
541
                }
542
            }
543
        }
544
        $selectedOperators = array_reverse($selectedOperators);
545
546
        // At this point, the $selectedOperator list contains a list of operators of the same kind that will apply
547
        // at the same time.
548
        if (empty($selectedOperators)) {
549
            // If we have an Expression with no base expression, let's simply discard it.
550
            // Indeed, the tree will add brackets by itself, and no Expression is needed for that.
551
            $newNodes = array();
552
            /*foreach ($nodes as $key=>$operand) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
553
                if ($operand instanceof Expression) {
554
                    $subTree = $operand->getSubTree();
555
                    if (count($subTree) == 1) {
556
                        $nodes[$key] = self::simplify($subTree);
557
                    }
558
                }
559
            }*/
560
            foreach ($nodes as $operand) {
561
                if ($operand instanceof Expression) {
562
                    if (empty($operand->getBaseExpression())) {
563
                        $subTree = $operand->getSubTree();
564
                        if (count($subTree) == 1) {
565
                            $newNodes = array_merge($newNodes, self::simplify($subTree));
0 ignored issues
show
Bug introduced by
It seems like $subTree defined by $operand->getSubTree() on line 563 can also be of type array; however, SQLParser\Node\NodeFactory::simplify() does only seem to accept array<integer,object<SQL...ser\Node\NodeInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
566
                        } else {
567
                            $newNodes[] = $operand;
568
                        }
569
                    } else {
570
                        $newNodes[] = $operand;
571
                    }
572
                } else {
573
                    $newNodes[] = $operand;
574
                }
575
            }
576
577
            return $newNodes;
578
        }
579
580
        // Let's grab the operands of the operator.
581
        $operands = array();
582
        $operand = array();
583
        $tmpOperators = $selectedOperators;
584
        $nextOperator = array_shift($tmpOperators);
585
586
        $isSelectedOperatorFirst = null;
587
588
        foreach ($nodes as $node) {
589
            if ($node === $nextOperator) {
590
                if ($isSelectedOperatorFirst === null) {
591
                    $isSelectedOperatorFirst = true;
592
                }
593
                // Let's apply the "simplify" method on the operand before storing it.
594
                //$operands[] = self::simplify($operand);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
595
                $simple = self::simplify($operand);
596
                if (is_array($simple)) {
597
                    $operands = array_merge($operands, $simple);
598
                } else {
599
                    $operands[] = $simple;
600
                }
601
602
                $operand = array();
603
                $nextOperator = array_shift($tmpOperators);
604
            } else {
605
                if ($isSelectedOperatorFirst === null) {
606
                    $isSelectedOperatorFirst = false;
607
                }
608
                $operand[] = $node;
609
            }
610
        }
611
        //$operands[] = self::simplify($operand);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
612
        //$operands = array_merge($operands, self::simplify($operand));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% 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...
613
        $simple = self::simplify($operand);
614
        if (is_array($simple)) {
615
            $operands = array_merge($operands, $simple);
616
        } else {
617
            $operands[] = $simple;
618
        }
619
620
        // Now, if we have an Expression, let's simply discard it.
621
        // Indeed, the tree will add brackets by itself, and no Expression in needed for that.
622
        /*foreach ($operands as $key=>$operand) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
623
            if ($operand instanceof Expression) {
624
                $subTree = $operand->getSubTree();
625
                if (count($subTree) == 1) {
626
                    $operands[$key] = self::simplify($subTree);
627
                }
628
            }
629
        }*/
630
631
        $operation = strtoupper($selectedOperators[0]->getValue());
632
633
        /* TODO:
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
634
        Remaining operators to code:
635
        array('INTERVAL'),
636
        array('BINARY', 'COLLATE'),
637
        array('!'),
638
        array('NOT'),
639
        */
640
641
        if (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractTwoOperandsOperator')) {
642
            if (count($operands) != 2) {
643
                throw new MagicQueryException('An error occured while parsing SQL statement. Invalid character found next to "'.$operation.'"');
644
            }
645
646
            $leftOperand = array_shift($operands);
647
            $rightOperand = array_shift($operands);
648
649
            $instance = new self::$OPERATOR_TO_CLASS[$operation]();
650
            $instance->setLeftOperand($leftOperand);
651
            $instance->setRightOperand($rightOperand);
652
653
            return $instance;
654
        } elseif (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractManyInstancesOperator')) {
655
            $instance = new self::$OPERATOR_TO_CLASS[$operation]();
656
            $instance->setOperands($operands);
657
658
            return $instance;
659
        } elseif ($operation === 'BETWEEN') {
660
            $leftOperand = array_shift($operands);
661
            $rightOperand = array_shift($operands);
662
            if (!$rightOperand instanceof Operation || $rightOperand->getOperatorSymbol() !== 'AND_FROM_BETWEEN') {
663
                throw new MagicQueryException('Missing AND in BETWEEN filter.');
664
            }
665
666
            $innerOperands = $rightOperand->getOperands();
667
            $minOperand = array_shift($innerOperands);
668
            $maxOperand = array_shift($innerOperands);
669
670
            $instance = new Between();
671
            $instance->setLeftOperand($leftOperand);
672
            $instance->setMinValueOperand($minOperand);
673
            $instance->setMaxValueOperand($maxOperand);
674
675
            return $instance;
676
        } elseif ($operation === 'WHEN') {
677
            $instance = new WhenConditions();
678
679
            if (!$isSelectedOperatorFirst) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isSelectedOperatorFirst of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
680
                $value = array_shift($operands);
681
                $instance->setValue($value);
682
            }
683
            $instance->setOperands($operands);
684
685
            return $instance;
686
        } elseif ($operation === 'CASE') {
687
            $innerOperation = array_shift($operands);
688
689
            if (!empty($operands)) {
690
                throw new MagicQueryException('A CASE statement should contain only a ThenConditions or a ElseOperand object.');
691
            }
692
693
            $instance = new CaseOperation();
694
            $instance->setOperation($innerOperation);
695
696
            return $instance;
697
        } elseif ($operation === 'END') {
698
            // Simply bypass the END operation. We already have a CASE matching node:
699
            $caseOperation = array_shift($operands);
700
701
            return $caseOperation;
702
        } else {
703
            $instance = new Operation();
704
            $instance->setOperatorSymbol($operation);
705
            $instance->setOperands($operands);
706
707
            return $instance;
708
        }
709
    }
710
711
    /**
712
     * Finds the precedence for operator $node (highest number has the least precedence).
713
     *
714
     * @param Operator $node
715
     *
716
     * @throws \Exception
717
     *
718
     * @return unknown
719
     */
720
    private static function getOperatorPrecedence(Operator $node)
721
    {
722
        $value = strtoupper($node->getValue());
723
724
        foreach (self::$PRECEDENCE as $priority => $arr) {
725
            foreach ($arr as $op) {
726
                if ($value == $op) {
727
                    return $priority;
728
                }
729
            }
730
        }
731
        throw new \Exception('Unknown operator precedence for operator '.$value);
732
    }
733
734
    /**
735
     * @param mixed       $node        a node of a recursive array of node
736
     * @param MoufManager $moufManager
737
     *
738
     * @return MoufInstanceDescriptor
739
     */
740
    public static function nodeToInstanceDescriptor($node, MoufManager $moufManager)
741
    {
742
        $instanceDescriptor = $moufManager->createInstance(get_called_class());
0 ignored issues
show
Unused Code introduced by
$instanceDescriptor is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
743
744
        return self::array_map_deep($node, function ($item) use ($moufManager) {
745
            if ($item instanceof NodeInterface) {
746
                return $item->toInstanceDescriptor($moufManager);
747
            } else {
748
                return $item;
749
            }
750
        });
751
    }
752
753
    private static function array_map_deep($array, $callback)
754
    {
755
        $new = array();
756
        if (is_array($array)) {
757
            foreach ($array as $key => $val) {
758
                if (is_array($val)) {
759
                    $new[$key] = self::array_map_deep($val, $callback);
760
                } else {
761
                    $new[$key] = call_user_func($callback, $val);
762
                }
763
            }
764
        } else {
765
            $new = call_user_func($callback, $array);
766
        }
767
768
        return $new;
769
    }
770
771
    /**
772
     * Tansforms the array of nodes (or the node) passed in parameter into a SQL string.
773
     *
774
     * @param mixed       $nodes          Recursive array of node interface
775
     * @param Connection  $dbConnection
776
     * @param array       $parameters
777
     * @param string      $delimiter
778
     * @param bool|string $wrapInBrackets
779
     * @param int|number  $indent
780
     * @param int         $conditionsMode
781
     *
782
     * @return null|string
783
     */
784
    public static function toSql($nodes, Connection $dbConnection = null, array $parameters = array(), $delimiter = ',', $wrapInBrackets = true, $indent = 0, $conditionsMode = SqlRenderInterface::CONDITION_APPLY)
785
    {
786
        if (is_array($nodes)) {
787
            $elems = array();
788
            array_walk_recursive($nodes, function ($item) use (&$elems, $dbConnection, $indent, $delimiter, $parameters, $conditionsMode) {
789
                if ($item instanceof SqlRenderInterface) {
790
                    $itemSql = $item->toSql($parameters, $dbConnection, $indent, $conditionsMode);
791
                    if ($itemSql !== null) {
792
                        $elems[] = str_repeat(' ', $indent).$itemSql;
793
                    }
794
                } else {
795
                    if ($item !== null) {
796
                        $elems[] = str_repeat(' ', $indent).$item;
797
                    }
798
                }
799
            });
800
            $sql = implode($delimiter, $elems);
801
        } else {
802
            $item = $nodes;
803
            if ($item instanceof SqlRenderInterface) {
804
                $itemSql = $item->toSql($parameters, $dbConnection, $indent, $conditionsMode);
805
                if ($itemSql === null || $itemSql === '') {
806
                    return;
807
                }
808
                $sql = str_repeat(' ', $indent).$itemSql;
809
            } else {
810
                if ($item === null || $item === '') {
811
                    return;
812
                }
813
                $sql = str_repeat(' ', $indent).$item;
814
            }
815
        }
816
        if ($wrapInBrackets) {
817
            $sql = '('.$sql.')';
818
        }
819
820
        return $sql;
821
    }
822
823
    /**
824
     * Escapes a DB item (should not be used. Only used if no DBConnection is passed).
825
     *
826
     * @return string
827
     *
828
     * @param string $str
829
     */
830
    public static function escapeDBItem($str, Connection $dbConnection = null)
831
    {
832
        if ($dbConnection) {
833
            return $dbConnection->quoteIdentifier($str);
834
        } else {
835
            return '`'.$str.'`';
836
        }
837
    }
838
}
839