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

Expression::factory()   D

Complexity

Conditions 69
Paths 69

Size

Total Lines 184
Code Lines 139

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 134
CRAP Score 69.3758

Importance

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