Completed
Pull Request — master (#149)
by Enrico
04:15
created

Expression::factory()   D

Complexity

Conditions 64
Paths 64

Size

Total Lines 174
Code Lines 129

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 126
CRAP Score 64.1196

Importance

Changes 0
Metric Value
cc 64
eloc 129
nc 64
nop 1
dl 0
loc 174
ccs 126
cts 130
cp 0.9692
crap 64.1196
rs 4.1818
c 0
b 0
f 0

How to fix   Long Method    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
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use InvalidArgumentException;
9
use PHPSA\Check;
10
use PHPSA\CompiledExpression;
11
use PHPSA\Compiler\Event\ExpressionBeforeCompile;
12
use PHPSA\Context;
13
use PhpParser\Node;
14
use PHPSA\Definition\ClassDefinition;
15
use PHPSA\Exception\RuntimeException;
16
use PHPSA\Variable;
17
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
18
use Webiny\Component\EventManager\EventManager;
19
20
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 156 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

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

Loading history...
21
{
22
    /**
23
     * @var Context
24
     */
25
    protected $context;
26
27
    /**
28
     * @var EventManager
29
     */
30
    protected $eventManager;
31
32
    /**
33
     * @param Context $context
34
     */
35 769
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 769
        $this->context = $context;
38 769
        $this->eventManager = $eventManager;
39 769
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 751
    protected function factory($expr)
46
    {
47 751
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 751
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 751
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 743
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 743
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 741
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 741
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 720
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 703
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 683
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 663
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 645
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 634
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 622
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 610
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 598
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 584
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 572
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 560
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 532
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 531
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 517
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 483
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105 466
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 455
            case Node\Expr\BinaryOp\Coalesce::class:
108 3
                return new Expression\BinaryOp\Coalesce();
109
                
110
            /**
111
             * @link http://php.net/manual/en/language.operators.increment.php
112
             */
113 452
            case Node\Expr\PostInc::class:
114 8
                return new Expression\Operators\PostInc();
115 444
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 436
            case Node\Expr\PreInc::class:
118 8
                return new Expression\Operators\PreInc();
119 428
            case Node\Expr\PreDec::class:
120 8
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 420
            case Node\Expr\BinaryOp\Div::class:
125 37
                return new Expression\Operators\Arithmetical\Div();
126 383
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 338
            case Node\Expr\BinaryOp\Minus::class:
129 18
                return new Expression\Operators\Arithmetical\Minus();
130 320
            case Node\Expr\BinaryOp\Mul::class:
131 35
                return new Expression\Operators\Arithmetical\Mul();
132 285
            case Node\Expr\BinaryOp\Mod::class:
133 35
                return new Expression\Operators\Arithmetical\Mod();
134 250
            case Node\Expr\BinaryOp\Pow::class:
135 21
                return new Expression\Operators\Arithmetical\Pow();
136
137
            /**
138
             * Bitwise
139
             * @link http://php.net/manual/ru/language.operators.bitwise.php
140
             */
141 229
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 217
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 205
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 193
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 181
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 169
            case Node\Expr\BitwiseNot::class:
152 5
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 164
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 147
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 15
                return new Expression\Operators\Logical\BooleanAnd();
160 132
            case Node\Expr\BooleanNot::class:
161 9
                return new Expression\Operators\Logical\BooleanNot();
162 123
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 108
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 91
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 74
            case Node\Expr\BinaryOp\Greater::class:
173 12
                return new Expression\Operators\Comparison\Greater();
174 62
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 50
            case Node\Expr\BinaryOp\Smaller::class:
177 12
                return new Expression\Operators\Comparison\Smaller();
178 38
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 26
            case Node\Expr\Cast\Array_::class:
185 1
                return new Expression\Casts\ArrayCast();
186 26
            case Node\Expr\Cast\Bool_::class:
187 1
                return new Expression\Casts\BoolCast();
188 26
            case Node\Expr\Cast\Int_::class:
189 1
                return new Expression\Casts\IntCast();
190 26
            case Node\Expr\Cast\Double::class:
191 1
                return new Expression\Casts\DoubleCast();
192 26
            case Node\Expr\Cast\Object_::class:
193 1
                return new Expression\Casts\ObjectCast();
194 26
            case Node\Expr\Cast\String_::class:
195 1
                return new Expression\Casts\StringCast();
196 26
            case Node\Expr\Cast\Unset_::class:
197 1
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 25
            case Node\Expr\Closure::class:
204
                return new Expression\Closure();
205 25
            case Node\Expr\UnaryMinus::class:
206 9
                return new Expression\Operators\UnaryMinus();
207 16
            case Node\Expr\UnaryPlus::class:
208 9
                return new Expression\Operators\UnaryPlus();
209 7
            case Node\Expr\Exit_::class:
210 1
                return new Expression\ExitOp();
211 6
            case Node\Expr\Isset_::class:
212 3
                return new Expression\IssetOp();
213 3
            case Node\Expr\Print_::class:
214 1
                return new Expression\PrintOp();
215 2
        }
216
217 2
        return false;
218
    }
219
220
    /**
221
     * @param object|string $expr
222
     * @return CompiledExpression
223
     */
224 769
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 544 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
225
    {
226 769
        if (is_string($expr)) {
227 13
            return new CompiledExpression(CompiledExpression::STRING, $expr);
228
        }
229
230 769
        if (is_null($expr)) {
231 7
            return new CompiledExpression(CompiledExpression::NULL);
232
        }
233
234 769
        if (!is_object($expr)) {
235
            throw new InvalidArgumentException('$expr must be string/object/null');
236
        }
237
238 769
        $this->eventManager->fire(
239 769
            ExpressionBeforeCompile::EVENT_NAME,
240 769
            new ExpressionBeforeCompile(
241 769
                $expr,
242 769
                $this->context
243 769
            )
244 769
        );
245
246 769
        $className = get_class($expr);
247
        switch ($className) {
248 769
            case Node\Arg::class:
249
                /**
250
                 * @todo Better compile
251
                 */
252 2
                return $this->compile($expr->value);
253 769
            case Node\Expr\PropertyFetch::class:
254
                return $this->passPropertyFetch($expr);
255 769
            case Node\Stmt\Property::class:
256
                return $this->passProperty($expr);
257 769
            case Node\Expr\ClassConstFetch::class:
258
                return $this->passConstFetch($expr);
259 769
            case Node\Expr\Assign::class:
260 13
                return $this->passSymbol($expr);
261 769
            case Node\Expr\AssignRef::class:
262 1
                return $this->passSymbolByRef($expr);
263 769
            case Node\Expr\Variable::class:
264 9
                return $this->passExprVariable($expr);
265
266
            /**
267
             * Expressions
268
             */
269 769
            case Node\Expr\Array_::class:
270 31
                return $this->getArray($expr);
271 768
            case Node\Expr\ConstFetch::class:
272 6
                return $this->constFetch($expr);
273 768
            case Node\Name::class:
274 11
                return $this->getNodeName($expr);
275 768
            case Node\Name\FullyQualified::class:
276
                return $this->getFullyQualifiedNodeName($expr);
277
278
            /**
279
             * Simple Scalar(s)
280
             */
281 768
            case \PHPSA\Node\Scalar\Nil::class:
282 25
                return new CompiledExpression(CompiledExpression::NULL);
283 767
            case Node\Scalar\LNumber::class:
284 475
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
285 759
            case Node\Scalar\DNumber::class:
286 200
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
287 758
            case Node\Scalar\String_::class:
288 38
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
289 753
            case \PHPSA\Node\Scalar\Boolean::class:
290 229
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
291 751
            case \PHPSA\Node\Scalar\Fake::class:
292 71
                return new CompiledExpression($expr->type, $expr->value);
293
        }
294
295 751
        $expressionCompiler = $this->factory($expr);
296 751
        if (!$expressionCompiler) {
297 2
            $this->context->debug("Expression compiler is not implemented for {$className}");
298 2
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
299
        }
300
301 750
        $result = $expressionCompiler->pass($expr, $this->context);
302 750
        if (!$result instanceof CompiledExpression) {
303
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
304
        }
305
306 750
        return $result;
307
    }
308
309
    /**
310
     * @todo Implement
311
     *
312
     * @param Node\Stmt\Property $st
313
     * @return CompiledExpression
314
     */
315
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
316
    {
317
        $docBlock = $st->getDocComment();
318
        if (!$docBlock) {
319
            $this->context->notice(
320
                'missing-docblock',
321
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
322
                $st
323
            );
324
325
            return new CompiledExpression();
326
        }
327
328
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
329
330
        $varTags = $phpdoc->getTagsByName('var');
331
        if ($varTags) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $varTags of type phpDocumentor\Reflection\DocBlock\Tag[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
332
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
333
            $varTag = current($varTags);
334
335
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
336
337
            try {
338
                $type = $typeResolver->resolve($varTag->getType());
339
            } catch (\InvalidArgumentException $e) {
340
                return new CompiledExpression();
341
            }
342
343
            if ($type) {
344
                switch (get_class($type)) {
345
                    case \phpDocumentor\Reflection\Types\Object_::class:
346
                        return new CompiledExpression(
347
                            CompiledExpression::OBJECT
348
                        );
349
                    case \phpDocumentor\Reflection\Types\Integer::class:
350
                        return new CompiledExpression(
351
                            CompiledExpression::INTEGER
352
                        );
353
                    case \phpDocumentor\Reflection\Types\String_::class:
354
                        return new CompiledExpression(
355
                            CompiledExpression::STRING
356
                        );
357
                    case \phpDocumentor\Reflection\Types\Float_::class:
358
                        return new CompiledExpression(
359
                            CompiledExpression::DOUBLE
360
                        );
361
                    case \phpDocumentor\Reflection\Types\Null_::class:
362
                        return new CompiledExpression(
363
                            CompiledExpression::NULL
364
                        );
365
                    case \phpDocumentor\Reflection\Types\Boolean::class:
366
                        return new CompiledExpression(
367
                            CompiledExpression::BOOLEAN
368
                        );
369
                }
370
            }
371
        }
372
373
        return new CompiledExpression();
374
    }
375
376
    /**
377
     * @param Node\Expr\Variable $expr
378
     * @return CompiledExpression
379
     */
380 1
    public function declareVariable(Node\Expr\Variable $expr)
381
    {
382 1
        $variable = $this->context->getSymbol($expr->name);
383 1
        if (!$variable) {
384 1
            $variable = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, 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...
385 1
            $this->context->addVariable($variable);
386 1
        }
387
388 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
389
    }
390
391
    /**
392
     * @param Node\Name\FullyQualified $expr
393
     * @return CompiledExpression
394
     */
395
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
396
    {
397
        $this->context->debug('Unimplemented FullyQualified', $expr);
398
399
        return new CompiledExpression;
400
    }
401
402
    /**
403
     * @param Node\Name $expr
404
     * @return CompiledExpression
405
     */
406 11
    public function getNodeName(Node\Name $expr)
407
    {
408 11
        $nodeString = $expr->toString();
409 11
        if ($nodeString === 'null') {
410 1
            return new CompiledExpression(CompiledExpression::NULL);
411
        }
412
413 10
        if (in_array($nodeString, ['parent'], true)) {
414
            /** @var ClassDefinition $scope */
415
            $scope = $this->context->scope;
416
            assert($scope instanceof ClassDefinition);
417
418
            if ($scope->getExtendsClass()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $scope->getExtendsClass() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
419
                $definition = $scope->getExtendsClassDefinition();
420
                if ($definition) {
421
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
422
                }
423
            } else {
424
                $this->context->notice(
425
                    'no-parent',
426
                    'Cannot access parent:: when current class scope has no parent',
427
                    $expr
428
                );
429
            }
430
        }
431
432 10
        if (in_array($nodeString, ['self', 'static'], true)) {
433
            return CompiledExpression::fromZvalValue($this->context->scope);
434
        }
435
436 10
        if (defined($nodeString)) {
437 1
            return CompiledExpression::fromZvalValue(constant($expr));
438
        }
439
440 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
441
    }
442
443
    /**
444
     * @param Node\Expr\PropertyFetch $expr
445
     * @return CompiledExpression
446
     */
447
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
448
    {
449
        $propertNameCE = $this->compile($expr->name);
450
451
        $scopeExpression = $this->compile($expr->var);
452
        if ($scopeExpression->isObject()) {
453
            $scopeExpressionValue = $scopeExpression->getValue();
454
            if ($scopeExpressionValue instanceof ClassDefinition) {
455
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
456
                if ($propertyName) {
457
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
458
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
459
                        return $this->compile($property);
460
                    } else {
461
                        $this->context->notice(
462
                            'undefined-property',
463
                            sprintf(
464
                                'Property %s does not exist in %s scope',
465
                                $propertyName,
466
                                $scopeExpressionValue->getName()
467
                            ),
468
                            $expr
469
                        );
470
                    }
471
                }
472
            }
473
474
            return new CompiledExpression(CompiledExpression::UNKNOWN);
475
        } elseif (!$scopeExpression->canBeObject()) {
476
            return new CompiledExpression(CompiledExpression::UNKNOWN);
477
        }
478
479
        $this->context->notice(
480
            'property-fetch-on-non-object',
481
            "It's not possible to fetch property on not object",
482
            $expr,
483
            Check::CHECK_BETA
484
        );
485
486
        return new CompiledExpression(CompiledExpression::UNKNOWN);
487
    }
488
489
    /**
490
     * @param Node\Expr\ClassConstFetch $expr
491
     * @return CompiledExpression
492
     */
493
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
494
    {
495
        $leftCE = $this->compile($expr->class);
496
        if ($leftCE->isObject()) {
497
            $leftCEValue = $leftCE->getValue();
498
            if ($leftCEValue instanceof ClassDefinition) {
499
                if (!$leftCEValue->hasConst($expr->name, true)) {
500
                    $this->context->notice(
501
                        'undefined-const',
502
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
503
                        $expr
504
                    );
505
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
506
                }
507
508
                return new CompiledExpression();
509
            }
510
        }
511
512
        $this->context->debug('Unknown const fetch', $expr);
513
        return new CompiledExpression();
514
    }
515
516
    /**
517
     * @param Node\Expr\Assign $expr
518
     * @return CompiledExpression
519
     */
520 13
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 240 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
521
    {
522 13
        $compiledExpression = $this->compile($expr->expr);
523
524 13
        if ($expr->var instanceof Node\Expr\List_) {
525
            $isCorrectType = false;
526
527
            switch ($compiledExpression->getType()) {
528
                case CompiledExpression::ARR:
529
                    $isCorrectType = true;
530
                    break;
531
            }
532
533
            if ($expr->var->vars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expr->var->vars of type PhpParser\Node\Expr[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
534
                foreach ($expr->var->vars as $key => $var) {
535
                    if ($var instanceof Node\Expr\Variable) {
536
                        $name = $expr->var->name;
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in PhpParser\Node\Expr\List_.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
537
538
                        $symbol = $this->context->getSymbol($name);
539
                        if (!$symbol) {
540
                            $symbol = new Variable(
541
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 536 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, 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...
542
                                null,
543
                                CompiledExpression::UNKNOWN,
544
                                $this->context->getCurrentBranch()
545
                            );
546
                            $this->context->addVariable($symbol);
547
                        }
548
549
                        if (!$isCorrectType) {
550
                            $symbol->modify(CompiledExpression::NULL, null);
551
                        }
552
553
                        $symbol->incSets();
554
                    }
555
                }
556
            }
557
558
            return new CompiledExpression();
559
        }
560
561
562 13
        if ($expr->var instanceof Node\Expr\Variable) {
563 13
            $compiledExpressionName = $this->compile($expr->var->name);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $compiledExpressionName exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
564 13
            switch ($compiledExpressionName->getType()) {
565 13
                case CompiledExpression::STRING:
566 13
                    break;
567
                default:
568
                    $this->context->debug('Unexpected type of Variable name after compile');
569
                    return new CompiledExpression();
570 13
            }
571
572 13
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
573 13
            if ($symbol) {
574 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
575 2
                $this->context->modifyReferencedVariables(
576 2
                    $symbol,
577 2
                    $compiledExpression->getType(),
578 2
                    $compiledExpression->getValue()
579 2
                );
580 2
            } else {
581 13
                $symbol = new Variable(
582 13
                    $compiledExpressionName->getValue(),
583 13
                    $compiledExpression->getValue(),
584 13
                    $compiledExpression->getType(),
585 13
                    $this->context->getCurrentBranch()
586 13
                );
587 13
                $this->context->addVariable($symbol);
588
            }
589
590 13
            $symbol->incSets();
591 13
            return $compiledExpression;
592
        }
593
594 1
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
595 1
            $compiledExpression = $this->compile($expr->var->var);
596 1
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
597 1
                $objectDefinition = $compiledExpression->getValue();
598 1
                if ($objectDefinition instanceof ClassDefinition) {
599 1
                    if (is_string($expr->var->name)) {
600 1
                        if ($objectDefinition->hasProperty($expr->var->name)) {
601
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
602
                        }
603 1
                    }
604 1
                }
605 1
            }
606 1
        }
607
608 1
        $this->context->debug('Unknown how to pass symbol');
609 1
        return new CompiledExpression();
610
    }
611
612
    /**
613
     * @param Node\Expr\AssignRef $expr
614
     * @return CompiledExpression
615
     */
616 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
617
    {
618 1
        if ($expr->var instanceof Node\Expr\Variable) {
619 1
            $name = $expr->var->name;
620
621 1
            $compiledExpression = $this->compile($expr->expr);
622
623 1
            $symbol = $this->context->getSymbol($name);
624 1
            if ($symbol) {
625
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
626
            } else {
627 1
                $symbol = new Variable(
628 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 619 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, 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...
629 1
                    $compiledExpression->getValue(),
630 1
                    $compiledExpression->getType(),
631 1
                    $this->context->getCurrentBranch()
632 1
                );
633 1
                $this->context->addVariable($symbol);
634
            }
635
636 1
            if ($expr->expr instanceof Node\Expr\Variable) {
637 1
                $rightVarName = $expr->expr->name;
638
639 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
640 1
                if ($rightSymbol) {
641 1
                    $rightSymbol->incUse();
642 1
                    $symbol->setReferencedTo($rightSymbol);
643 1
                } else {
644
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
645
                }
646 1
            }
647
648 1
            $symbol->incSets();
649 1
            return $compiledExpression;
650
        }
651
652
        $this->context->debug('Unknown how to pass symbol by ref');
653
        return new CompiledExpression();
654
    }
655
656
    /**
657
     * @param Node\Expr\Variable $expr
658
     * @return CompiledExpression
659
     */
660 9
    protected function passExprVariable(Node\Expr\Variable $expr)
661
    {
662 9
        $variable = $this->context->getSymbol($expr->name);
663 9
        if ($variable) {
664 9
            $variable->incGets();
665 9
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
666
        }
667
668
        $this->context->notice(
669
            'undefined-variable',
670
            sprintf('You trying to use undefined variable $%s', $expr->name),
671
            $expr
672
        );
673
674
        return new CompiledExpression();
675
    }
676
677
    /**
678
     * Compile Array_ expression to CompiledExpression
679
     *
680
     * @param Node\Expr\Array_ $expr
681
     * @return CompiledExpression
682
     */
683 31
    protected function getArray(Node\Expr\Array_ $expr)
684
    {
685 31
        if ($expr->items === []) {
686 22
            return new CompiledExpression(CompiledExpression::ARR, []);
687
        }
688
689 10
        $resultArray = [];
690
691 10
        foreach ($expr->items as $item) {
692 10
            $compiledValueResult = $this->compile($item->value);
693 10
            if ($item->key) {
694 3
                $compiledKeyResult = $this->compile($item->key);
695 3
                switch ($compiledKeyResult->getType()) {
696 3
                    case CompiledExpression::INTEGER:
697 3
                    case CompiledExpression::DOUBLE:
698 3
                    case CompiledExpression::BOOLEAN:
699 3
                    case CompiledExpression::NULL:
700 3
                    case CompiledExpression::STRING:
701 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
702 3
                        break;
703 1
                    default:
704 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
705 1
                        return new CompiledExpression(CompiledExpression::ARR);
706
                        break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
707 3
                }
708 3
            } else {
709 7
                $resultArray[] = $compiledValueResult->getValue();
710
            }
711 10
        }
712
713 10
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
714
    }
715
716
    /**
717
     * Convert const fetch expr to CompiledExpression
718
     *
719
     * @param Node\Expr\ConstFetch $expr
720
     * @return CompiledExpression
721
     */
722 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
723
    {
724 6
        if ($expr->name instanceof Node\Name) {
725 6
            if ($expr->name->parts[0] === 'true') {
726 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
727
            }
728
729 2
            if ($expr->name->parts[0] === 'false') {
730
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
731
            }
732 2
        }
733
734
        /**
735
         * @todo Implement check
736
         */
737 2
        return $this->compile($expr->name);
738
    }
739
}
740