Completed
Push — 1.1 ( 6ab797...def185 )
by David
03:11
created

NodeFactory::toSql()   C

Complexity

Conditions 11
Paths 8

Size

Total Lines 38
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 38
rs 5.2653
cc 11
eloc 26
nc 8
nop 7

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * 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
34
namespace SQLParser\Node;
35
36
use Mouf\Database\MagicQueryException;
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
                }
89 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...
90
                    $expr = substr($expr, 0, strlen($expr) - 1);
91
                }
92
                $expr = stripslashes($expr);
93
94
                $const->setValue($expr);
95
                // Debug:
96
                unset($desc['base_expr']);
97
                unset($desc['expr_type']);
98
                unset($desc['sub_tree']);
99
                if (!empty($desc)) {
100
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
101
                }
102
103
                return $const;
104
105
            case ExpressionType::OPERATOR:
106
                $operator = new Operator();
107
                $operator->setValue($desc['base_expr']);
108
                // Debug:
109
                unset($desc['base_expr']);
110
                unset($desc['expr_type']);
111 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...
112
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
113
                }
114
                unset($desc['sub_tree']);
115
                if (!empty($desc)) {
116
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
117
                }
118
119
                return $operator;
120
121
            case ExpressionType::COLREF:
122
                if (substr($desc['base_expr'], 0, 1) == ':') {
123
                    $instance = new Parameter();
124
                    $instance->setName(substr($desc['base_expr'], 1));
125
                } else {
126
                    $instance = new ColRef();
127
                    $lastDot = strrpos($desc['base_expr'], '.');
128
                    if ($lastDot === false) {
129
                        $instance->setColumn(str_replace('`', '', $desc['base_expr']));
130
                    } else {
131
                        $instance->setColumn(str_replace('`', '', substr($desc['base_expr'], $lastDot + 1)));
132
                        $instance->setTable(str_replace('`', '', substr($desc['base_expr'], 0, $lastDot)));
133
                    }
134
                    if (!empty($desc['alias'])) {
135
                        $instance->setAlias($desc['alias']['name']);
136
                    }
137
138
                    if (!empty($desc['direction'])) {
139
                        $instance->setDirection($desc['direction']);
140
                    }
141
                }
142
143
                // Debug:
144
                unset($desc['direction']);
145
                unset($desc['base_expr']);
146
                unset($desc['expr_type']);
147 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...
148
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
149
                }
150
                unset($desc['sub_tree']);
151
                unset($desc['alias']);
152
                if (!empty($desc)) {
153
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
154
                }
155
156
                return $instance;
157
            case ExpressionType::TABLE:
158
                $expr = new Table();
159
                $expr->setTable(str_replace('`', '', $desc['table']));
160
                switch ($desc['join_type']) {
161
                    case 'CROSS':
162
                        $joinType = 'CROSS JOIN';
163
                        break;
164
                    case 'JOIN':
165
                        $joinType = 'JOIN';
166
                        break;
167
                    case 'LEFT':
168
                        $joinType = 'LEFT JOIN';
169
                        break;
170
                    case 'RIGHT':
171
                        $joinType = 'RIGHT JOIN';
172
                        break;
173
                    case 'INNER':
174
                        $joinType = 'INNER JOIN';
175
                        break;
176
                    case 'OUTER':
177
                        $joinType = 'OUTER JOIN';
178
                        break;
179
                    case 'NATURAL':
180
                        $joinType = 'NATURAL JOIN';
181
                        break;
182
                    case ',':
183
                        $joinType = ',';
184
                        break;
185
                    default:
186
                        throw new \Exception("Unexpected join type: '".$desc['join_type']."'");
187
                }
188
                $expr->setJoinType($joinType);
189
190
                if (isset($desc['alias'])) {
191
                    $expr->setAlias($desc['alias']['name']);
192
                }
193
                $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
194
                if ($subTreeNodes) {
195
                    $expr->setRefClause(self::simplify($subTreeNodes));
0 ignored issues
show
Documentation introduced by
self::simplify($subTreeNodes) is of type array|object, but the function expects a array<integer,object<SQL...ser\Node\NodeInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
196
                }
197
198
                // Debug:
199
                unset($desc['base_expr']);
200
                unset($desc['expr_type']);
201 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...
202
                    throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
203
                }
204
                unset($desc['sub_tree']);
205
                unset($desc['join_type']);
206
                unset($desc['alias']);
207
                unset($desc['table']);
208
                unset($desc['ref_type']);
209
                unset($desc['ref_clause']);
210
                if (!empty($desc)) {
211
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
212
                }
213
214
                return $expr;
215
            case ExpressionType::SUBQUERY:
216
                $expr = new SubQuery();
217
218
                $expr->setSubQuery(self::buildFromSubtree($desc['sub_tree']));
219
220
                if (isset($desc['join_type'])) {
221
                    $expr->setJoinType($desc['join_type']);
222
                }
223
224
                if (isset($desc['alias'])) {
225
                    $expr->setAlias($desc['alias']['name']);
226
                }
227
228
                if (isset($desc['ref_clause'])) {
229
                    $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
230
                    if ($subTreeNodes) {
231
                        $expr->setRefClause(self::simplify($subTreeNodes));
0 ignored issues
show
Documentation introduced by
self::simplify($subTreeNodes) is of type array|object, but the function expects a array<integer,object<SQL...er\Node\NodeInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
232
                    }
233
                }
234
235
                // Debug:
236
                unset($desc['base_expr']);
237
                unset($desc['expr_type']);
238
                unset($desc['sub_tree']);
239
                unset($desc['join_type']);
240
                unset($desc['alias']);
241
                unset($desc['sub_tree']);
242
                unset($desc['ref_type']);
243
                unset($desc['ref_clause']);
244
                if (!empty($desc)) {
245
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
246
                }
247
248
                return $expr;
249
            case ExpressionType::AGGREGATE_FUNCTION:
250
                $expr = new AggregateFunction();
251
                $expr->setFunctionName($desc['base_expr']);
252
253
                $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
254
255
                if (isset($desc['alias'])) {
256
                    $expr->setAlias($desc['alias']);
257
                }
258
259
                // Debug:
260
                unset($desc['base_expr']);
261
                unset($desc['expr_type']);
262
                unset($desc['sub_tree']);
263
                unset($desc['alias']);
264
                if (!empty($desc)) {
265
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
266
                }
267
268
                return $expr;
269
            case ExpressionType::SIMPLE_FUNCTION:
270
                $expr = new SimpleFunction();
271
                $expr->setBaseExpression($desc['base_expr']);
272
273
                if (isset($desc['sub_tree'])) {
274
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
275
                }
276
277
                if (isset($desc['alias'])) {
278
                    $expr->setAlias($desc['alias']['name']);
279
                }
280
                if (isset($desc['direction'])) {
281
                    $expr->setDirection($desc['direction']);
282
                }
283
284
                // Debug:
285
                unset($desc['base_expr']);
286
                unset($desc['expr_type']);
287
                unset($desc['sub_tree']);
288
                unset($desc['alias']);
289
                unset($desc['direction']);
290
                if (!empty($desc)) {
291
                    throw new \InvalidArgumentException('Unexpected parameters in simple function: '.var_export($desc, true));
292
                }
293
294
                return $expr;
295
296
            case ExpressionType::USER_VARIABLE:
297
            case ExpressionType::SESSION_VARIABLE:
298
            case ExpressionType::GLOBAL_VARIABLE:
299
            case ExpressionType::LOCAL_VARIABLE:
300
301
            case ExpressionType::RESERVED:
302
303
            case ExpressionType::EXPRESSION:
304
            case ExpressionType::BRACKET_EXPRESSION:
305
            case ExpressionType::TABLE_EXPRESSION:
306
307
            case ExpressionType::IN_LIST:
308
309
            case ExpressionType::SIGN:
310
            case ExpressionType::RECORD:
311
312
            case ExpressionType::MATCH_ARGUMENTS:
313
            case ExpressionType::MATCH_MODE:
314
315
            case ExpressionType::ALIAS:
316
            case ExpressionType::POSITION:
317
318
            case ExpressionType::TEMPORARY_TABLE:
319
            case ExpressionType::VIEW:
320
            case ExpressionType::DATABASE:
321
            case ExpressionType::SCHEMA:
322
                $expr = new Expression();
323
                $expr->setBaseExpression($desc['base_expr']);
324
325
                if (isset($desc['sub_tree'])) {
326
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
327
                }
328
329
                if (isset($desc['alias'])) {
330
                    $expr->setAlias($desc['alias']['name']);
331
                }
332
                if (isset($desc['direction'])) {
333
                    $expr->setDirection($desc['direction']);
334
                }
335
336
                if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
337
                    $expr->setBrackets(true);
338
                }
339
340
                // Debug:
341
                unset($desc['base_expr']);
342
                unset($desc['expr_type']);
343
                unset($desc['sub_tree']);
344
                unset($desc['alias']);
345
                unset($desc['direction']);
346
                if (!empty($desc)) {
347
                    throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
348
                }
349
350
                return $expr;
351
            default:
352
                throw new \Exception('Unknown expression type');
353
        }
354
    }
355
356
    private static function buildFromSubtree($subTree)
357
    {
358
        if ($subTree && is_array($subTree)) {
359
            //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...
360
            // If the subtree is a map instead of a list, we are likely to be on a SUBSELECT statement.
361
            if (!empty($subTree) && !isset($subTree[0])) {
362
                $subTree = StatementFactory::toObject($subTree);
363
            } else {
364
                $subTree = array_map(function ($item) {
365
                    if (is_array($item)) {
366
                        return self::toObject($item);
367
                    } else {
368
                        return $item;
369
                    }
370
                }, $subTree);
371
            }
372
        }
373
374
        return $subTree;
375
    }
376
377
    private static $PRECEDENCE = array(
378
            array('INTERVAL'),
379
            array('BINARY', 'COLLATE'),
380
            array('!'),
381
            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...
382
            array('^'),
383
            array('*', '/', 'DIV', '%', 'MOD'),
384
            array('-', '+'),
385
            array('<<', '>>'),
386
            array('&'),
387
            array('|'),
388
            array('=' /*(comparison)*/, '<=>', '>=', '>', '<=', '<', '<>', '!=', 'IS', 'LIKE', 'REGEXP', 'IN', 'IS NOT', 'NOT IN'),
389
            array('AND_FROM_BETWEEN'),
390
            array('BETWEEN', 'CASE', 'WHEN', 'THEN', 'ELSE'),
391
            array('NOT'),
392
            array('&&', 'AND'),
393
            array('XOR'),
394
            array('||', 'OR'), );
395
396
    private static $OPERATOR_TO_CLASS = array(
397
            '=' => 'SQLParser\Node\Equal',
398
            '<' => 'SQLParser\Node\Less',
399
            '>' => 'SQLParser\Node\Greater',
400
            '<=' => 'SQLParser\Node\LessOrEqual',
401
            '=>' => 'SQLParser\Node\GreaterOrEqual',
402
            //'<=>' => '????',
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...
403
            '<>' => 'SQLParser\Node\Different',
404
            '!=' => 'SQLParser\Node\Different',
405
            'IS' => 'SQLParser\Node\Is',
406
            'IS NOT' => 'SQLParser\Node\IsNot',
407
            'LIKE' => 'SQLParser\Node\Like',
408
            'REGEXP' => 'SQLParser\Node\Regexp',
409
            'IN' => 'SQLParser\Node\In',
410
            'NOT IN' => 'SQLParser\Node\NotIn',
411
            '+' => 'SQLParser\Node\Plus',
412
            '-' => 'SQLParser\Node\Minus',
413
            '*' => 'SQLParser\Node\Multiply',
414
            '/' => 'SQLParser\Node\Divide',
415
            '%' => 'SQLParser\Node\Modulo',
416
            'MOD' => 'SQLParser\Node\Modulo',
417
            'DIV' => 'SQLParser\Node\Div',
418
            '&' => 'SQLParser\Node\BitwiseAnd',
419
            '|' => 'SQLParser\Node\BitwiseOr',
420
            '^' => 'SQLParser\Node\BitwiseXor',
421
            '<<' => 'SQLParser\Node\ShiftLeft',
422
            '>>' => 'SQLParser\Node\ShiftRight',
423
            '<=>' => 'SQLParser\Node\NullCompatibleEqual',
424
            'AND' => 'SQLParser\Node\AndOp',
425
            '&&' => 'SQLParser\Node\AndOp',
426
            '||' => 'SQLParser\Node\OrOp',
427
            'OR' => 'SQLParser\Node\OrOp',
428
            'XOR' => 'SQLParser\Node\XorOp',
429
    );
430
431
    /**
432
     * Takes an array of nodes (including operators) and try to build a tree from it.
433
     *
434
     * @param NodeInterface[]|NodeInterface $nodes
435
     */
436
    public static function simplify($nodes)
437
    {
438
        if (empty($nodes)) {
439
            $nodes = array();
440
        } elseif (!is_array($nodes)) {
441
            $nodes = array($nodes);
442
        }
443
        $minPriority = -1;
444
        $selectedOperators = array();
445
        $lastSelectedOperator = '';
446
        $differentOperatorWithSamePriority = false;
447
448
        // Let's transform "NOT" + "IN" into "NOT IN"
449
        $newNodes = array();
450
        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...
451
            $node = $nodes[$i];
452
            if ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
453
                    && strtoupper($node->getValue()) == 'IS' && strtoupper($nodes[$i + 1]->getValue()) == 'NOT') {
454
                $notIn = new Operator();
455
                $notIn->setValue('IS NOT');
456
                $newNodes[] = $notIn;
457
                ++$i;
458
            } elseif ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
459
                    && strtoupper($node->getValue()) == 'NOT' && strtoupper($nodes[$i + 1]->getValue()) == 'IN') {
460
                $notIn = new Operator();
461
                $notIn->setValue('NOT IN');
462
                $newNodes[] = $notIn;
463
                ++$i;
464
            } else {
465
                $newNodes[] = $node;
466
            }
467
        }
468
        $nodes = $newNodes;
469
470
        // Let's find the highest level operator.
471
        for ($i = count($nodes) - 1; $i >= 0; --$i) {
472
            $node = $nodes[$i];
473
            if ($node instanceof Operator) {
474
                $priority = self::getOperatorPrecedence($node);
475
476
                if ($priority == $minPriority && $lastSelectedOperator != strtoupper($node->getValue())) {
477
                    $differentOperatorWithSamePriority = true;
478
                } elseif ($priority > $minPriority) {
479
                    $minPriority = $priority;
480
                    $selectedOperators = array($node);
481
                    $lastSelectedOperator = strtoupper($node->getValue());
482
                } else {
483
                    if (strtoupper($node->getValue()) == $lastSelectedOperator && !$differentOperatorWithSamePriority) {
484
                        $selectedOperators[] = $node;
485
                    }
486
                }
487
            }
488
        }
489
        $selectedOperators = array_reverse($selectedOperators);
490
491
        // At this point, the $selectedOperator list contains a list of operators of the same kind that will apply
492
        // at the same time.
493
        if (empty($selectedOperators)) {
494
            // If we have an Expression with no base expression, let's simply discard it.
495
            // Indeed, the tree will add brackets by itself, and no Expression is needed for that.
496
            $newNodes = array();
497
            /*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...
498
                if ($operand instanceof Expression) {
499
                    $subTree = $operand->getSubTree();
500
                    if (count($subTree) == 1) {
501
                        $nodes[$key] = self::simplify($subTree);
502
                    }
503
                }
504
            }*/
505
            foreach ($nodes as $operand) {
506
                if ($operand instanceof Expression) {
507
                    if (empty($operand->getBaseExpression())) {
508
                        $subTree = $operand->getSubTree();
509
                        if (count($subTree) == 1) {
510
                            $newNodes = array_merge($newNodes, self::simplify($subTree));
0 ignored issues
show
Documentation introduced by
$subTree is of type array|object, but the function expects a array<integer,object<SQL...ser\Node\NodeInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
511
                        } else {
512
                            $newNodes[] = $operand;
513
                        }
514
                    } else {
515
                        $newNodes[] = $operand;
516
                    }
517
                } else {
518
                    $newNodes[] = $operand;
519
                }
520
            }
521
522
            return $newNodes;
523
        }
524
525
        // Let's grab the operands of the operator.
526
        $operands = array();
527
        $operand = array();
528
        $tmpOperators = $selectedOperators;
529
        $nextOperator = array_shift($tmpOperators);
530
531
        foreach ($nodes as $node) {
532
            if ($node === $nextOperator) {
533
                // Let's apply the "simplify" method on the operand before storing it.
534
                //$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...
535
                $simple = self::simplify($operand);
536
                if (is_array($simple)) {
537
                    $operands = array_merge($operands, $simple);
538
                } else {
539
                    $operands[] = $simple;
540
                }
541
542
                $operand = array();
543
                $nextOperator = array_shift($tmpOperators);
544
            } else {
545
                $operand[] = $node;
546
            }
547
        }
548
        //$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...
549
        //$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...
550
        $simple = self::simplify($operand);
551
        if (is_array($simple)) {
552
            $operands = array_merge($operands, $simple);
553
        } else {
554
            $operands[] = $simple;
555
        }
556
557
        // Now, if we have an Expression, let's simply discard it.
558
        // Indeed, the tree will add brackets by itself, and no Expression in needed for that.
559
        /*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...
560
            if ($operand instanceof Expression) {
561
                $subTree = $operand->getSubTree();
562
                if (count($subTree) == 1) {
563
                    $operands[$key] = self::simplify($subTree);
564
                }
565
            }
566
        }*/
567
568
        $operation = strtoupper($selectedOperators[0]->getValue());
569
570
        /* TODO:
0 ignored issues
show
Unused Code Comprehensibility introduced by
68% 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...
571
        Remaining operators to code:
572
        array('INTERVAL'),
573
        array('BINARY', 'COLLATE'),
574
        array('!'),
575
        array('BETWEEN', 'CASE', 'WHEN', 'THEN', 'ELSE'),
576
        array('NOT'),
577
        */
578
579
        if (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractTwoOperandsOperator')) {
580
            $leftOperand = array_shift($operands);
581
            while (!empty($operands)) {
582
                $rightOperand = array_shift($operands);
583
584
                $instance = new self::$OPERATOR_TO_CLASS[$operation]();
585
                $instance->setLeftOperand($leftOperand);
586
                $instance->setRightOperand($rightOperand);
587
                $leftOperand = $instance;
588
            }
589
590
            return $instance;
0 ignored issues
show
Bug introduced by
The variable $instance does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
591
        } elseif (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractManyInstancesOperator')) {
592
            $instance = new self::$OPERATOR_TO_CLASS[$operation]();
593
            $instance->setOperands($operands);
594
595
            return $instance;
596
        } elseif ($operation === 'BETWEEN') {
597
            $leftOperand = array_shift($operands);
598
            $rightOperand = array_shift($operands);
599
            if (!$rightOperand instanceof Operation || $rightOperand->getOperatorSymbol() !== 'AND_FROM_BETWEEN') {
600
                throw new MagicQueryException('Missing AND in BETWEEN filter.');
601
            }
602
603
            $innerOperands = $rightOperand->getOperands();
604
            $minOperand = array_shift($innerOperands);
605
            $maxOperand = array_shift($innerOperands);
606
607
            $instance = new Between();
608
            $instance->setLeftOperand($leftOperand);
609
            $instance->setMinValueOperand($minOperand);
610
            $instance->setMaxValueOperand($maxOperand);
611
612
            return $instance;
613
        } else {
614
            $instance = new Operation();
615
            $instance->setOperatorSymbol($operation);
616
            $instance->setOperands($operands);
617
618
            return $instance;
619
        }
620
    }
621
622
    /**
623
     * Finds the precedence for operator $node (highest number has the least precedence).
624
     *
625
     * @param Operator $node
626
     *
627
     * @throws \Exception
628
     *
629
     * @return unknown
630
     */
631
    private static function getOperatorPrecedence(Operator $node)
632
    {
633
        $value = strtoupper($node->getValue());
634
635
        foreach (self::$PRECEDENCE as $priority => $arr) {
636
            foreach ($arr as $op) {
637
                if ($value == $op) {
638
                    return $priority;
639
                }
640
            }
641
        }
642
        throw new \Exception('Unknown operator precedence for operator '.$value);
643
    }
644
645
    /**
646
     * @param mixed       $node        a node of a recursive array of node
647
     * @param MoufManager $moufManager
648
     *
649
     * @return MoufInstanceDescriptor
650
     */
651
    public static function nodeToInstanceDescriptor($node, MoufManager $moufManager)
652
    {
653
        $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...
654
655
        return self::array_map_deep($node, function ($item) use ($moufManager) {
656
            if ($item instanceof NodeInterface) {
657
                return $item->toInstanceDescriptor($moufManager);
658
            } else {
659
                return $item;
660
            }
661
        });
662
    }
663
664
    private static function array_map_deep($array, $callback)
665
    {
666
        $new = array();
667
        if (is_array($array)) {
668
            foreach ($array as $key => $val) {
669
                if (is_array($val)) {
670
                    $new[$key] = self::array_map_deep($val, $callback);
671
                } else {
672
                    $new[$key] = call_user_func($callback, $val);
673
                }
674
            }
675
        } else {
676
            $new = call_user_func($callback, $array);
677
        }
678
679
        return $new;
680
    }
681
682
    /**
683
     * Tansforms the array of nodes (or the node) passed in parameter into a SQL string.
684
     *
685
     * @param mixed       $nodes          Recursive array of node interface
686
     * @param Connection  $dbConnection
687
     * @param array       $parameters
688
     * @param string      $delimiter
689
     * @param bool|string $wrapInBrackets
690
     * @param int|number  $indent
691
     * @param int         $conditionsMode
692
     *
693
     * @return null|string
694
     */
695
    public static function toSql($nodes, Connection $dbConnection = null, array $parameters = array(), $delimiter = ',', $wrapInBrackets = true, $indent = 0, $conditionsMode = SqlRenderInterface::CONDITION_APPLY)
696
    {
697
        if (is_array($nodes)) {
698
            $elems = array();
699
            array_walk_recursive($nodes, function ($item) use (&$elems, $dbConnection, $indent, $delimiter, $parameters, $conditionsMode) {
700
                if ($item instanceof SqlRenderInterface) {
701
                    $itemSql = $item->toSql($parameters, $dbConnection, $indent, $conditionsMode);
702
                    if ($itemSql !== null) {
703
                        $elems[] = str_repeat(' ', $indent).$itemSql;
704
                    }
705
                } else {
706
                    if ($item !== null) {
707
                        $elems[] = str_repeat(' ', $indent).$item;
708
                    }
709
                }
710
            });
711
            $sql = implode($delimiter, $elems);
712
        } else {
713
            $item = $nodes;
714
            if ($item instanceof SqlRenderInterface) {
715
                $itemSql = $item->toSql($parameters, $dbConnection, $indent, $conditionsMode);
716
                if ($itemSql === null || $itemSql === '') {
717
                    return;
718
                }
719
                $sql = str_repeat(' ', $indent).$itemSql;
720
            } else {
721
                if ($item === null || $item === '') {
722
                    return;
723
                }
724
                $sql = str_repeat(' ', $indent).$item;
725
            }
726
        }
727
        if ($wrapInBrackets) {
728
            $sql = '('.$sql.')';
729
        }
730
731
        return $sql;
732
    }
733
734
    /**
735
     * Escapes a DB item (should not be used. Only used if no DBConnection is passed).
736
     *
737
     * @return string
738
     *
739
     * @param string $str
740
     */
741
    public static function escapeDBItem($str, Connection $dbConnection = null)
742
    {
743
        if ($dbConnection) {
744
            return $dbConnection->quoteIdentifier($str);
745
        } else {
746
            return '`'.$str.'`';
747
        }
748
    }
749
}
750