Completed
Pull Request — master (#149)
by Enrico
03:50
created

Expression::factory()   D

Complexity

Conditions 68
Paths 68

Size

Total Lines 182
Code Lines 137

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 132
CRAP Score 68.3806

Importance

Changes 0
Metric Value
cc 68
eloc 137
c 0
b 0
f 0
nc 68
nop 1
dl 0
loc 182
ccs 132
cts 138
cp 0.9565
crap 68.3806
rs 4.1818

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 160 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 777
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 777
        $this->context = $context;
38 777
        $this->eventManager = $eventManager;
39 777
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 759
    protected function factory($expr)
46
    {
47 759
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 759
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 759
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 751
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 751
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 749
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 749
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 728
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 711
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 691
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 671
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 653
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 642
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 630
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 618
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 606
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 592
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 580
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 568
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 540
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 539
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 525
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 491
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105 474
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 463
            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 460
            case Node\Expr\PostInc::class:
114 8
                return new Expression\Operators\PostInc();
115 452
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 444
            case Node\Expr\PreInc::class:
118 8
                return new Expression\Operators\PreInc();
119 436
            case Node\Expr\PreDec::class:
120 8
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 428
            case Node\Expr\BinaryOp\Div::class:
125 37
                return new Expression\Operators\Arithmetical\Div();
126 391
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 346
            case Node\Expr\BinaryOp\Minus::class:
129 18
                return new Expression\Operators\Arithmetical\Minus();
130 328
            case Node\Expr\BinaryOp\Mul::class:
131 35
                return new Expression\Operators\Arithmetical\Mul();
132 293
            case Node\Expr\BinaryOp\Mod::class:
133 35
                return new Expression\Operators\Arithmetical\Mod();
134 258
            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 237
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 225
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 213
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 201
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 189
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 177
            case Node\Expr\BitwiseNot::class:
152 5
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 172
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 155
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 15
                return new Expression\Operators\Logical\BooleanAnd();
160 140
            case Node\Expr\BooleanNot::class:
161 9
                return new Expression\Operators\Logical\BooleanNot();
162 131
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 116
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 99
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 82
            case Node\Expr\BinaryOp\Greater::class:
173 12
                return new Expression\Operators\Comparison\Greater();
174 70
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 58
            case Node\Expr\BinaryOp\Smaller::class:
177 12
                return new Expression\Operators\Comparison\Smaller();
178 46
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 34
            case Node\Expr\Cast\Array_::class:
185 1
                return new Expression\Casts\ArrayCast();
186 34
            case Node\Expr\Cast\Bool_::class:
187 1
                return new Expression\Casts\BoolCast();
188 34
            case Node\Expr\Cast\Int_::class:
189 1
                return new Expression\Casts\IntCast();
190 34
            case Node\Expr\Cast\Double::class:
191 1
                return new Expression\Casts\DoubleCast();
192 34
            case Node\Expr\Cast\Object_::class:
193 1
                return new Expression\Casts\ObjectCast();
194 34
            case Node\Expr\Cast\String_::class:
195 1
                return new Expression\Casts\StringCast();
196 34
            case Node\Expr\Cast\Unset_::class:
197 1
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 33
            case Node\Expr\Closure::class:
204
                return new Expression\Closure();
205 33
            case Node\Expr\UnaryMinus::class:
206 9
                return new Expression\Operators\UnaryMinus();
207 24
            case Node\Expr\UnaryPlus::class:
208 9
                return new Expression\Operators\UnaryPlus();
209 15
            case Node\Expr\Exit_::class:
210 1
                return new Expression\ExitOp();
211 14
            case Node\Expr\Isset_::class:
212 3
                return new Expression\IssetOp();
213 11
            case Node\Expr\Print_::class:
214 2
                return new Expression\PrintOp();
215 10
            case Node\Expr\Empty_::class:
216 5
                return new Expression\EmptyOp();
217 5
            case Node\Expr\Eval_::class:
218 2
                return new Expression\EvalOp();
219 3
            case Node\Expr\ErrorSuppress::class:
220 2
                return new Expression\ErrorSuppress();
221 1
            case Node\Expr\Clone_::class:
222 1
                return new Expression\CloneOp();
223
        }
224
225
        return false;
226
    }
227
228
    /**
229
     * @param object|string $expr
230
     * @return CompiledExpression
231
     */
232 777
    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...
233
    {
234 777
        if (is_string($expr)) {
235 13
            return new CompiledExpression(CompiledExpression::STRING, $expr);
236
        }
237
238 777
        if (is_null($expr)) {
239 7
            return new CompiledExpression(CompiledExpression::NULL);
240
        }
241
242 777
        if (!is_object($expr)) {
243
            throw new InvalidArgumentException('$expr must be string/object/null');
244
        }
245
246 777
        $this->eventManager->fire(
247 777
            ExpressionBeforeCompile::EVENT_NAME,
248 777
            new ExpressionBeforeCompile(
249 777
                $expr,
250 777
                $this->context
251 777
            )
252 777
        );
253
254 777
        $className = get_class($expr);
255
        switch ($className) {
256 777
            case Node\Arg::class:
257
                /**
258
                 * @todo Better compile
259
                 */
260 2
                return $this->compile($expr->value);
261 777
            case Node\Expr\PropertyFetch::class:
262
                return $this->passPropertyFetch($expr);
263 777
            case Node\Stmt\Property::class:
264
                return $this->passProperty($expr);
265 777
            case Node\Expr\ClassConstFetch::class:
266
                return $this->passConstFetch($expr);
267 777
            case Node\Expr\Assign::class:
268 13
                return $this->passSymbol($expr);
269 777
            case Node\Expr\AssignRef::class:
270 1
                return $this->passSymbolByRef($expr);
271 777
            case Node\Expr\Variable::class:
272 10
                return $this->passExprVariable($expr);
273
274
            /**
275
             * Expressions
276
             */
277 777
            case Node\Expr\Array_::class:
278 31
                return $this->getArray($expr);
279 776
            case Node\Expr\ConstFetch::class:
280 6
                return $this->constFetch($expr);
281 776
            case Node\Name::class:
282 11
                return $this->getNodeName($expr);
283 776
            case Node\Name\FullyQualified::class:
284
                return $this->getFullyQualifiedNodeName($expr);
285
286
            /**
287
             * Simple Scalar(s)
288
             */
289 776
            case \PHPSA\Node\Scalar\Nil::class:
290 25
                return new CompiledExpression(CompiledExpression::NULL);
291 775
            case Node\Scalar\LNumber::class:
292 475
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
293 767
            case Node\Scalar\DNumber::class:
294 200
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
295 766
            case Node\Scalar\String_::class:
296 42
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
297 761
            case \PHPSA\Node\Scalar\Boolean::class:
298 229
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
299 759
            case \PHPSA\Node\Scalar\Fake::class:
300 71
                return new CompiledExpression($expr->type, $expr->value);
301
        }
302
303 759
        $expressionCompiler = $this->factory($expr);
304 759
        if (!$expressionCompiler) {
305
            $this->context->debug("Expression compiler is not implemented for {$className}");
306
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
307
        }
308
309 759
        $result = $expressionCompiler->pass($expr, $this->context);
310 759
        if (!$result instanceof CompiledExpression) {
311
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
312
        }
313
314 759
        return $result;
315
    }
316
317
    /**
318
     * @todo Implement
319
     *
320
     * @param Node\Stmt\Property $st
321
     * @return CompiledExpression
322
     */
323
    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...
324
    {
325
        $docBlock = $st->getDocComment();
326
        if (!$docBlock) {
327
            $this->context->notice(
328
                'missing-docblock',
329
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
330
                $st
331
            );
332
333
            return new CompiledExpression();
334
        }
335
336
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
337
338
        $varTags = $phpdoc->getTagsByName('var');
339
        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...
340
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
341
            $varTag = current($varTags);
342
343
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
344
345
            try {
346
                $type = $typeResolver->resolve($varTag->getType());
347
            } catch (\InvalidArgumentException $e) {
348
                return new CompiledExpression();
349
            }
350
351
            if ($type) {
352
                switch (get_class($type)) {
353
                    case \phpDocumentor\Reflection\Types\Object_::class:
354
                        return new CompiledExpression(
355
                            CompiledExpression::OBJECT
356
                        );
357
                    case \phpDocumentor\Reflection\Types\Integer::class:
358
                        return new CompiledExpression(
359
                            CompiledExpression::INTEGER
360
                        );
361
                    case \phpDocumentor\Reflection\Types\String_::class:
362
                        return new CompiledExpression(
363
                            CompiledExpression::STRING
364
                        );
365
                    case \phpDocumentor\Reflection\Types\Float_::class:
366
                        return new CompiledExpression(
367
                            CompiledExpression::DOUBLE
368
                        );
369
                    case \phpDocumentor\Reflection\Types\Null_::class:
370
                        return new CompiledExpression(
371
                            CompiledExpression::NULL
372
                        );
373
                    case \phpDocumentor\Reflection\Types\Boolean::class:
374
                        return new CompiledExpression(
375
                            CompiledExpression::BOOLEAN
376
                        );
377
                }
378
            }
379
        }
380
381
        return new CompiledExpression();
382
    }
383
384
    /**
385
     * @param Node\Expr\Variable $expr
386
     * @return CompiledExpression
387
     */
388 1
    public function declareVariable(Node\Expr\Variable $expr)
389
    {
390 1
        $variable = $this->context->getSymbol($expr->name);
391 1
        if (!$variable) {
392 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...
393 1
            $this->context->addVariable($variable);
394 1
        }
395
396 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
397
    }
398
399
    /**
400
     * @param Node\Name\FullyQualified $expr
401
     * @return CompiledExpression
402
     */
403
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
404
    {
405
        $this->context->debug('Unimplemented FullyQualified', $expr);
406
407
        return new CompiledExpression;
408
    }
409
410
    /**
411
     * @param Node\Name $expr
412
     * @return CompiledExpression
413
     */
414 11
    public function getNodeName(Node\Name $expr)
415
    {
416 11
        $nodeString = $expr->toString();
417 11
        if ($nodeString === 'null') {
418 1
            return new CompiledExpression(CompiledExpression::NULL);
419
        }
420
421 10
        if (in_array($nodeString, ['parent'], true)) {
422
            /** @var ClassDefinition $scope */
423
            $scope = $this->context->scope;
424
            assert($scope instanceof ClassDefinition);
425
426
            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...
427
                $definition = $scope->getExtendsClassDefinition();
428
                if ($definition) {
429
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
430
                }
431
            } else {
432
                $this->context->notice(
433
                    'no-parent',
434
                    'Cannot access parent:: when current class scope has no parent',
435
                    $expr
436
                );
437
            }
438
        }
439
440 10
        if (in_array($nodeString, ['self', 'static'], true)) {
441
            return CompiledExpression::fromZvalValue($this->context->scope);
442
        }
443
444 10
        if (defined($nodeString)) {
445 1
            return CompiledExpression::fromZvalValue(constant($expr));
446
        }
447
448 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
449
    }
450
451
    /**
452
     * @param Node\Expr\PropertyFetch $expr
453
     * @return CompiledExpression
454
     */
455
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
456
    {
457
        $propertNameCE = $this->compile($expr->name);
458
459
        $scopeExpression = $this->compile($expr->var);
460
        if ($scopeExpression->isObject()) {
461
            $scopeExpressionValue = $scopeExpression->getValue();
462
            if ($scopeExpressionValue instanceof ClassDefinition) {
463
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
464
                if ($propertyName) {
465
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
466
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
467
                        return $this->compile($property);
468
                    } else {
469
                        $this->context->notice(
470
                            'undefined-property',
471
                            sprintf(
472
                                'Property %s does not exist in %s scope',
473
                                $propertyName,
474
                                $scopeExpressionValue->getName()
475
                            ),
476
                            $expr
477
                        );
478
                    }
479
                }
480
            }
481
482
            return new CompiledExpression(CompiledExpression::UNKNOWN);
483
        } elseif (!$scopeExpression->canBeObject()) {
484
            return new CompiledExpression(CompiledExpression::UNKNOWN);
485
        }
486
487
        $this->context->notice(
488
            'property-fetch-on-non-object',
489
            "It's not possible to fetch property on not object",
490
            $expr,
491
            Check::CHECK_BETA
492
        );
493
494
        return new CompiledExpression(CompiledExpression::UNKNOWN);
495
    }
496
497
    /**
498
     * @param Node\Expr\ClassConstFetch $expr
499
     * @return CompiledExpression
500
     */
501
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
502
    {
503
        $leftCE = $this->compile($expr->class);
504
        if ($leftCE->isObject()) {
505
            $leftCEValue = $leftCE->getValue();
506
            if ($leftCEValue instanceof ClassDefinition) {
507
                if (!$leftCEValue->hasConst($expr->name, true)) {
508
                    $this->context->notice(
509
                        'undefined-const',
510
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
511
                        $expr
512
                    );
513
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
514
                }
515
516
                return new CompiledExpression();
517
            }
518
        }
519
520
        $this->context->debug('Unknown const fetch', $expr);
521
        return new CompiledExpression();
522
    }
523
524
    /**
525
     * @param Node\Expr\Assign $expr
526
     * @return CompiledExpression
527
     */
528 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...
529
    {
530 13
        $compiledExpression = $this->compile($expr->expr);
531
532 13
        if ($expr->var instanceof Node\Expr\List_) {
533
            $isCorrectType = false;
534
535
            switch ($compiledExpression->getType()) {
536
                case CompiledExpression::ARR:
537
                    $isCorrectType = true;
538
                    break;
539
            }
540
541
            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...
542
                foreach ($expr->var->vars as $key => $var) {
543
                    if ($var instanceof Node\Expr\Variable) {
544
                        $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...
545
546
                        $symbol = $this->context->getSymbol($name);
547
                        if (!$symbol) {
548
                            $symbol = new Variable(
549
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 544 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...
550
                                null,
551
                                CompiledExpression::UNKNOWN,
552
                                $this->context->getCurrentBranch()
553
                            );
554
                            $this->context->addVariable($symbol);
555
                        }
556
557
                        if (!$isCorrectType) {
558
                            $symbol->modify(CompiledExpression::NULL, null);
559
                        }
560
561
                        $symbol->incSets();
562
                    }
563
                }
564
            }
565
566
            return new CompiledExpression();
567
        }
568
569
570 13
        if ($expr->var instanceof Node\Expr\Variable) {
571 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...
572 13
            switch ($compiledExpressionName->getType()) {
573 13
                case CompiledExpression::STRING:
574 13
                    break;
575
                default:
576
                    $this->context->debug('Unexpected type of Variable name after compile');
577
                    return new CompiledExpression();
578 13
            }
579
580 13
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
581 13
            if ($symbol) {
582 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
583 2
                $this->context->modifyReferencedVariables(
584 2
                    $symbol,
585 2
                    $compiledExpression->getType(),
586 2
                    $compiledExpression->getValue()
587 2
                );
588 2
            } else {
589 13
                $symbol = new Variable(
590 13
                    $compiledExpressionName->getValue(),
591 13
                    $compiledExpression->getValue(),
592 13
                    $compiledExpression->getType(),
593 13
                    $this->context->getCurrentBranch()
594 13
                );
595 13
                $this->context->addVariable($symbol);
596
            }
597
598 13
            $symbol->incSets();
599 13
            return $compiledExpression;
600
        }
601
602 1
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
603 1
            $compiledExpression = $this->compile($expr->var->var);
604 1
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
605 1
                $objectDefinition = $compiledExpression->getValue();
606 1
                if ($objectDefinition instanceof ClassDefinition) {
607 1
                    if (is_string($expr->var->name)) {
608 1
                        if ($objectDefinition->hasProperty($expr->var->name)) {
609
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
610
                        }
611 1
                    }
612 1
                }
613 1
            }
614 1
        }
615
616 1
        $this->context->debug('Unknown how to pass symbol');
617 1
        return new CompiledExpression();
618
    }
619
620
    /**
621
     * @param Node\Expr\AssignRef $expr
622
     * @return CompiledExpression
623
     */
624 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
625
    {
626 1
        if ($expr->var instanceof Node\Expr\Variable) {
627 1
            $name = $expr->var->name;
628
629 1
            $compiledExpression = $this->compile($expr->expr);
630
631 1
            $symbol = $this->context->getSymbol($name);
632 1
            if ($symbol) {
633
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
634
            } else {
635 1
                $symbol = new Variable(
636 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 627 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...
637 1
                    $compiledExpression->getValue(),
638 1
                    $compiledExpression->getType(),
639 1
                    $this->context->getCurrentBranch()
640 1
                );
641 1
                $this->context->addVariable($symbol);
642
            }
643
644 1
            if ($expr->expr instanceof Node\Expr\Variable) {
645 1
                $rightVarName = $expr->expr->name;
646
647 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
648 1
                if ($rightSymbol) {
649 1
                    $rightSymbol->incUse();
650 1
                    $symbol->setReferencedTo($rightSymbol);
651 1
                } else {
652
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
653
                }
654 1
            }
655
656 1
            $symbol->incSets();
657 1
            return $compiledExpression;
658
        }
659
660
        $this->context->debug('Unknown how to pass symbol by ref');
661
        return new CompiledExpression();
662
    }
663
664
    /**
665
     * @param Node\Expr\Variable $expr
666
     * @return CompiledExpression
667
     */
668 10
    protected function passExprVariable(Node\Expr\Variable $expr)
669
    {
670 10
        $variable = $this->context->getSymbol($expr->name);
671 10
        if ($variable) {
672 10
            $variable->incGets();
673 10
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
674
        }
675
676 1
        $this->context->notice(
677 1
            'undefined-variable',
678 1
            sprintf('You trying to use undefined variable $%s', $expr->name),
679
            $expr
680 1
        );
681
682 1
        return new CompiledExpression();
683
    }
684
685
    /**
686
     * Compile Array_ expression to CompiledExpression
687
     *
688
     * @param Node\Expr\Array_ $expr
689
     * @return CompiledExpression
690
     */
691 31
    protected function getArray(Node\Expr\Array_ $expr)
692
    {
693 31
        if ($expr->items === []) {
694 22
            return new CompiledExpression(CompiledExpression::ARR, []);
695
        }
696
697 10
        $resultArray = [];
698
699 10
        foreach ($expr->items as $item) {
700 10
            $compiledValueResult = $this->compile($item->value);
701 10
            if ($item->key) {
702 3
                $compiledKeyResult = $this->compile($item->key);
703 3
                switch ($compiledKeyResult->getType()) {
704 3
                    case CompiledExpression::INTEGER:
705 3
                    case CompiledExpression::DOUBLE:
706 3
                    case CompiledExpression::BOOLEAN:
707 3
                    case CompiledExpression::NULL:
708 3
                    case CompiledExpression::STRING:
709 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
710 3
                        break;
711 1
                    default:
712 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
713 1
                        return new CompiledExpression(CompiledExpression::ARR);
714
                        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...
715 3
                }
716 3
            } else {
717 7
                $resultArray[] = $compiledValueResult->getValue();
718
            }
719 10
        }
720
721 10
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
722
    }
723
724
    /**
725
     * Convert const fetch expr to CompiledExpression
726
     *
727
     * @param Node\Expr\ConstFetch $expr
728
     * @return CompiledExpression
729
     */
730 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
731
    {
732 6
        if ($expr->name instanceof Node\Name) {
733 6
            if ($expr->name->parts[0] === 'true') {
734 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
735
            }
736
737 2
            if ($expr->name->parts[0] === 'false') {
738
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
739
            }
740 2
        }
741
742
        /**
743
         * @todo Implement check
744
         */
745 2
        return $this->compile($expr->name);
746
    }
747
}
748