Completed
Push — master ( 5c8cc7...fa15a1 )
by Дмитрий
11:26
created

Expression   F

Complexity

Total Complexity 150

Size/Duplication

Total Lines 706
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 58

Test Coverage

Coverage 28.64%

Importance

Changes 33
Bugs 1 Features 10
Metric Value
wmc 150
c 33
b 1
f 10
lcom 1
cbo 58
dl 0
loc 706
ccs 116
cts 405
cp 0.2864
rs 1.0434

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
D factory() 0 114 39
A passProperty() 0 4 1
A declareVariable() 0 13 2
D compile() 0 80 26
C getNodeName() 0 36 7
B passCastBoolean() 0 18 6
B passCastInt() 0 18 6
B passCastFloat() 0 18 6
B passCastString() 0 18 6
A passCastUnset() 0 13 2
C passPropertyFetch() 0 39 7
B passConstFetch() 0 22 4
D passSymbol() 0 82 16
B passSymbolByRef() 0 46 6
A passExprVariable() 0 16 2
D getArray() 0 32 9
A constFetch() 0 21 4

How to fix   Complexity   

Complex Class

Complex classes like Expression often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Expression, and based on these observations, apply Extract Interface, too.

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\Context;
12
use PhpParser\Node;
13
use PHPSA\Definition\ClassDefinition;
14
use PHPSA\Exception\RuntimeException;
15
use PHPSA\Variable;
16
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
17
18
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 150 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...
19
{
20
    /**
21
     * @var Context
22
     */
23
    protected $context;
24
25
    /**
26
     * @param Context $context
27
     */
28 363
    public function __construct(Context $context)
29
    {
30 363
        $this->context = $context;
31 363
    }
32
33
    /**
34
     * @param $expr
35
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
36
     */
37 353
    protected function factory($expr)
38
    {
39 353
        switch (get_class($expr)) {
40
            /**
41
             * Call(s)
42
             */
43 353
            case Node\Expr\MethodCall::class:
44
                return new Expression\MethodCall();
45 353
            case Node\Expr\FuncCall::class:
46
                return new Expression\FunctionCall();
47 353
            case Node\Expr\StaticCall::class:
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case Node\Expr\New_::class:
53
                return new Expression\Operators\NewOp();
54 353
            case Node\Expr\Instanceof_::class:
55
                return new Expression\Operators\InstanceOfOp();
56
            /**
57
             * Assign
58
             */
59 353
            case Node\Expr\AssignOp\Pow::class:
60
                return new Expression\AssignOp\Pow();
61
            case Node\Expr\AssignOp\Plus::class:
62
                return new Expression\AssignOp\Plus();
63
            case Node\Expr\AssignOp\Minus::class:
64 353
                return new Expression\AssignOp\Minus();
65 28
            case Node\Expr\AssignOp\Mod::class:
66 325
                return new Expression\AssignOp\Mod();
67
            case Node\Expr\AssignOp\BitwiseOr::class:
68 325
                return new Expression\AssignOp\BitwiseOr();
69 14
            case Node\Expr\AssignOp\BitwiseAnd::class:
70 311
                return new Expression\AssignOp\BitwiseAnd();
71 34
            /**
72 277
             * BinaryOp
73 17
             */
74
            case Node\Expr\BinaryOp\Identical::class:
75
                return new Expression\BinaryOp\Identical();
76
            case Node\Expr\BinaryOp\Concat::class:
77 260
                return new Expression\Operators\Contact();
78 4
            case Node\Expr\BinaryOp\NotIdentical::class:
79 256
                return new Expression\BinaryOp\NotIdentical();
80 4
            case Node\Expr\BinaryOp\Equal::class:
81
                return new Expression\BinaryOp\Equal();
82
            case Node\Expr\BinaryOp\NotEqual::class:
83
                return new Expression\BinaryOp\NotEqual();
84 252
            /**
85 37
             * @link http://php.net/manual/en/language.operators.increment.php
86 215
             */
87 41
            case Node\Expr\PostInc::class:
88 174
                return new Expression\Operators\PostInc();
89 18
            case Node\Expr\PostDec::class:
90 156
                return new Expression\Operators\PostDec();
91 35
            /**
92 121
             * Arithmetical
93 35
             */
94
            case Node\Expr\BinaryOp\Div::class:
95
                return new Expression\Operators\Arithmetical\Div();
96
            case Node\Expr\BinaryOp\Plus::class:
97
                return new Expression\Operators\Arithmetical\Plus();
98 86
            case Node\Expr\BinaryOp\Minus::class:
99
                return new Expression\Operators\Arithmetical\Minus();
100 86
            case Node\Expr\BinaryOp\Mul::class:
101
                return new Expression\Operators\Arithmetical\Mul();
102 86
            case Node\Expr\BinaryOp\Mod::class:
103
                return new Expression\Operators\Arithmetical\Mod();
104 86
            /**
105
             * Bitwise
106 86
             * @link http://php.net/manual/ru/language.operators.bitwise.php
107
             */
108 86
            case Node\Expr\BinaryOp\BitwiseOr::class:
109
                return new Expression\Operators\Bitwise\BitwiseOr();
110
            case Node\Expr\BinaryOp\BitwiseXor::class:
111
                return new Expression\Operators\Bitwise\BitwiseXor();
112
            case Node\Expr\BinaryOp\BitwiseAnd::class:
113 86
                return new Expression\Operators\Bitwise\BitwiseAnd();
114 10
            case Node\Expr\BinaryOp\ShiftRight::class:
115 76
                return new Expression\Operators\Bitwise\ShiftRight();
116 5
            case Node\Expr\BinaryOp\ShiftLeft::class:
117 71
                return new Expression\Operators\Bitwise\ShiftLeft();
118 5
            case Node\Expr\BitwiseNot::class:
119
                return new Expression\Operators\Bitwise\BitwiseNot();
120
            /**
121
             * Logical
122 66
             */
123 12
            case Node\Expr\BinaryOp\BooleanOr::class:
124 54
                return new Expression\Operators\Logical\BooleanOr();
125 12
            case Node\Expr\BinaryOp\BooleanAnd::class:
126 42
                return new Expression\Operators\Logical\BooleanAnd();
127 12
            case Node\Expr\BooleanNot::class:
128 30
                return new Expression\Operators\Logical\BooleanNot();
129 12
            /**
130
             * Comparison
131
             */
132
            case Node\Expr\BinaryOp\Greater::class:
133 18
                return new Expression\Operators\Comparison\Greater();
134 9
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
135 9
                return new Expression\Operators\Comparison\GreaterOrEqual();
136 9
            case Node\Expr\BinaryOp\Smaller::class:
137
                return new Expression\Operators\Comparison\Smaller();
138
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
139
                return new Expression\Operators\Comparison\SmallerOrEqual();
140
            /**
141
             * Another
142
             */
143
            case Node\Expr\UnaryMinus::class:
144
                return new Expression\Operators\UnaryMinus();
145
            case Node\Expr\UnaryPlus::class:
146 363
                return new Expression\Operators\UnaryPlus();
147
        }
148 363
149
        return false;
150
    }
151
152 363
    /**
153
     * @param object|string $expr
154
     * @return CompiledExpression
155
     */
156 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 640 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...
157
    {
158
        if (is_string($expr)) {
159
            return new CompiledExpression(CompiledExpression::STRING, $expr);
160 363
        }
161
162 363
        if (is_null($expr)) {
163
            return new CompiledExpression(CompiledExpression::NULL);
164 363
        }
165
166 363
        if (!is_object($expr)) {
167
            throw new InvalidArgumentException('$expr must be string/object/null');
168 363
        }
169
170 363
        $className = get_class($expr);
171
        switch ($className) {
172 363
            case 'PhpParser\Node\Expr\PropertyFetch':
173
                return $this->passPropertyFetch($expr);
174
            case 'PhpParser\Node\Stmt\Property':
175
                return $this->passProperty($expr);
176
            case 'PhpParser\Node\Expr\ClassConstFetch':
177 363
                return $this->passConstFetch($expr);
178
            case 'PhpParser\Node\Expr\Assign':
179 363
                return $this->passSymbol($expr);
180
            case 'PhpParser\Node\Expr\AssignRef':
181 363
                return $this->passSymbolByRef($expr);
182
            case 'PhpParser\Node\Expr\Variable':
183 363
                return $this->passExprVariable($expr);
184
            /**
185 363
             * Cast operators
186
             */
187
            case 'PhpParser\Node\Expr\Cast\Bool_':
188
                return $this->passCastBoolean($expr);
189
            case 'PhpParser\Node\Expr\Cast\Int_':
190 363
                return $this->passCastInt($expr);
191 11
            case 'PhpParser\Node\Expr\Cast\Double':
192 362
                return $this->passCastFloat($expr);
193
            case 'PhpParser\Node\Expr\Cast\String_':
194 362
                return $this->passCastString($expr);
195
            case 'PhpParser\Node\Expr\Cast\Unset_':
196
                return $this->passCastUnset($expr);
197
            /**
198
             * Expressions
199 362
             */
200 8
            case 'PhpParser\Node\Expr\Array_':
201 361
                return $this->getArray($expr);
202 256
            case 'PhpParser\Node\Expr\ConstFetch':
203 358
                return $this->constFetch($expr);
204 131
            case 'PhpParser\Node\Name':
205 357
                return $this->getNodeName($expr);
206 8
            /**
207 355
             * Simple Scalar(s)
208 60
             */
209 353
            case 'PHPSA\Node\Scalar\Nil':
210 29
                return new CompiledExpression(CompiledExpression::NULL);
211
            case 'PhpParser\Node\Scalar\LNumber':
212
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
213 353
            case 'PhpParser\Node\Scalar\DNumber':
214 353
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
215
            case 'PhpParser\Node\Scalar\String_':
216
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
217
            case 'PHPSA\Node\Scalar\Boolean':
218
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
219 353
            case 'PHPSA\Node\Scalar\Fake':
220 353
                return new CompiledExpression($expr->type, $expr->value);
221
        }
222
223
        $expressionCompiler = $this->factory($expr);
224 353
        if (!$expressionCompiler) {
225
            $this->context->debug("Expression compiler is not implemented for {$className}");
226
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
227
        }
228
229
        $result = $expressionCompiler->pass($expr, $this->context);
230
        if (!$result instanceof CompiledExpression) {
231
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
232
        }
233
234
        return $result;
235
    }
236
237
    /**
238
     * @todo Implement
239
     *
240
     * @param Node\Stmt\Property $st
241
     * @return CompiledExpression
242
     */
243
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Unused Code introduced by
The parameter $st is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
244
    {
245
        return new CompiledExpression();
246
    }
247
248
    /**
249
     * @param Node\Expr\Variable $expr
250
     * @return CompiledExpression
251
     */
252
    public function declareVariable(Node\Expr\Variable $expr)
253
    {
254
        $variable = $this->context->getSymbol($expr->name);
255
        if ($variable) {
256
            $variable->incGets();
257
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
258
        }
259
260
        $symbol = 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...
261
        $this->context->addVariable($symbol);
262
263
        return new CompiledExpression;
264
    }
265
266
    /**
267
     * @param Node\Name $expr
268
     * @return CompiledExpression
269
     */
270
    public function getNodeName(Node\Name $expr)
271
    {
272
        if ($expr->toString() === 'null') {
273
            return new CompiledExpression(CompiledExpression::NULL);
274
        }
275
276
        if (in_array($expr, ['parent'], true)) {
277
            /** @var ClassDefinition $scope */
278
            $scope = $this->context->scope;
279
            assert($scope instanceof ClassDefinition);
280
281
            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...
282
                $definition = $scope->getExtendsClassDefinition();
283
                if ($definition) {
284
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
285
                }
286
            } else {
287
                $this->context->notice(
288
                    'no-parent',
289
                    'Cannot access parent:: when current class scope has no parent',
290
                    $expr
291
                );
292
            }
293
        }
294
295
        if (in_array($expr, ['self', 'static'], true)) {
296
            return CompiledExpression::fromZvalValue($this->context->scope);
297
        }
298
299
        if (defined($expr)) {
300
            return CompiledExpression::fromZvalValue(constant($expr));
301
        }
302
303
        $this->context->debug('[Unknown] How to get Node\Name for ' . $expr);
304
        return new CompiledExpression();
305
    }
306
307
    /**
308
     * (bool) {$expr}
309
     *
310
     * @param Node\Expr\Cast\Bool_ $expr
311
     * @return CompiledExpression
312
     */
313
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
314
    {
315
        $expression = new Expression($this->context);
316
        $compiledExpression = $expression->compile($expr->expr);
317
318
        switch ($compiledExpression->getType()) {
319
            case CompiledExpression::BOOLEAN:
320
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
321
                return $compiledExpression;
322
            case CompiledExpression::DOUBLE:
323
            case CompiledExpression::INTEGER:
324
            case CompiledExpression::NUMBER:
325
            case CompiledExpression::STRING:
326
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
327
        }
328
329
        return new CompiledExpression();
330
    }
331
332
    /**
333
     * (int) {$expr}
334
     *
335
     * @param Node\Expr\Cast\Int_ $expr
336
     * @return CompiledExpression
337
     */
338
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
339
    {
340
        $expression = new Expression($this->context);
341
        $compiledExpression = $expression->compile($expr->expr);
342
343
        switch ($compiledExpression->getType()) {
344
            case CompiledExpression::INTEGER:
345
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
346
                return $compiledExpression;
347
            case CompiledExpression::BOOLEAN:
348
            case CompiledExpression::DOUBLE:
349
            case CompiledExpression::NUMBER:
350
            case CompiledExpression::STRING:
351
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
352
        }
353
354
        return new CompiledExpression();
355
    }
356
357
    /**
358
     * (float) {$expr}
359
     *
360
     * @param Node\Expr\Cast\Double $expr
361
     * @return CompiledExpression
362
     */
363
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
364
    {
365
        $expression = new Expression($this->context);
366
        $compiledExpression = $expression->compile($expr->expr);
367
368
        switch ($compiledExpression->getType()) {
369
            case CompiledExpression::DOUBLE:
370
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
371
                return $compiledExpression;
372
            case CompiledExpression::BOOLEAN:
373
            case CompiledExpression::INTEGER:
374
            case CompiledExpression::NUMBER:
375
            case CompiledExpression::STRING:
376
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
377
        }
378
379
        return new CompiledExpression();
380
    }
381
382
    /**
383
     * (string) {$expr}
384
     *
385
     * @param Node\Expr\Cast\String_ $expr
386
     * @return CompiledExpression
387
     */
388
    protected function passCastString(Node\Expr\Cast\String_ $expr)
389
    {
390
        $expression = new Expression($this->context);
391
        $compiledExpression = $expression->compile($expr->expr);
392
393
        switch ($compiledExpression->getType()) {
394
            case CompiledExpression::STRING:
395
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
396
                return $compiledExpression;
397
            case CompiledExpression::BOOLEAN:
398
            case CompiledExpression::INTEGER:
399
            case CompiledExpression::NUMBER:
400
            case CompiledExpression::DOUBLE:
401
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
402
        }
403
404
        return new CompiledExpression();
405
    }
406
407
    /**
408
     * (unset) {$expr}
409
     *
410
     * @param Node\Expr\Cast\Unset_ $expr
411
     * @return CompiledExpression
412
     */
413
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
414
    {
415
        $expression = new Expression($this->context);
416
        $compiledExpression = $expression->compile($expr->expr);
417
418
        switch ($compiledExpression->getType()) {
419
            case CompiledExpression::NULL:
420
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
421
                return $compiledExpression;
422
        }
423
424
        return new CompiledExpression(CompiledExpression::NULL, null);
425
    }
426
427
    /**
428
     * @param Node\Expr\PropertyFetch $expr
429
     * @return CompiledExpression
430
     */
431
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
432
    {
433
        $propertNameCE = $this->compile($expr->name);
434
435
        $scopeExpression = $this->compile($expr->var);
436
        if ($scopeExpression->isObject()) {
437
            $scopeExpressionValue = $scopeExpression->getValue();
438
            if ($scopeExpressionValue instanceof ClassDefinition) {
439
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
440
                if ($propertyName) {
441
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
442
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
443
                        return $this->compile($property);
444
                    } else {
445
                        $this->context->notice(
446
                            'undefined-property',
447
                            sprintf(
448
                                'Property %s does not exist in %s scope',
449
                                $propertyName,
450
                                $scopeExpressionValue->getName()
451
                            ),
452
                            $expr
453
                        );
454
                    }
455
                }
456
            }
457
        } elseif (!$scopeExpression->canBeObject()) {
458
            return new CompiledExpression(CompiledExpression::UNKNOWN);
459
        }
460
461
        $this->context->notice(
462
            'property-fetch-on-non-object',
463
            "It's not possible to fetch property on not object",
464
            $expr,
465
            Check::CHECK_BETA
466
        );
467
468
        return new CompiledExpression(CompiledExpression::UNKNOWN);
469
    }
470
471
    /**
472
     * @param Node\Expr\ClassConstFetch $expr
473
     * @return CompiledExpression
474
     */
475
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
476
    {
477
        $leftCE = $this->compile($expr->class);
478
        if ($leftCE->isObject()) {
479
            $leftCEValue = $leftCE->getValue();
480
            if ($leftCEValue instanceof ClassDefinition) {
481
                if (!$leftCEValue->hasConst($expr->name)) {
482
                    $this->context->notice(
483
                        'undefined-const',
484
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
485
                        $expr
486
                    );
487
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
488
                }
489
490
                return new CompiledExpression();
491
            }
492
        }
493
494
        $this->context->debug('Unknown const fetch');
495
        return new CompiledExpression();
496
    }
497
498
    /**
499
     * @param Node\Expr\Assign $expr
500
     * @return CompiledExpression
501
     */
502
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 216 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...
503
    {
504
        $expression = new Expression($this->context);
505
        $compiledExpression = $expression->compile($expr->expr);
506
507
        if ($expr->var instanceof Node\Expr\List_) {
508
            $isCorrectType = false;
509
510
            switch ($compiledExpression->getType()) {
511
                case CompiledExpression::ARR:
512
                    $isCorrectType = true;
513
                    break;
514
            }
515
516
            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...
517
                foreach ($expr->var->vars as $key => $var) {
518
                    if ($var instanceof Node\Expr\Variable) {
519
                        if (!isset($expr->var->name)) {
520
                            var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
521
                        }
522
                        $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...
523
524
                        $symbol = $this->context->getSymbol($name);
525
                        if (!$symbol) {
526
                            $symbol = new Variable(
527
                                $name,
528
                                null,
529
                                CompiledExpression::UNKNOWN,
530
                                $this->context->getCurrentBranch()
531
                            );
532
                            $this->context->addVariable($symbol);
533
                        }
534
535
                        if (!$isCorrectType) {
536
                            $symbol->modify(CompiledExpression::NULL, null);
537
                        }
538
539
                        $symbol->incSets();
540
                    }
541
                }
542
            }
543
544
            return new CompiledExpression();
545
        }
546
547
        if ($expr->var instanceof Node\Expr\Variable) {
548
            $name = $expr->var->name;
549
550
            $symbol = $this->context->getSymbol($name);
551
            if ($symbol) {
552
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
553
            } else {
554
                $symbol = new Variable(
555
                    $name,
556
                    $compiledExpression->getValue(),
557
                    $compiledExpression->getType(),
558
                    $this->context->getCurrentBranch()
559
                );
560
                $this->context->addVariable($symbol);
561
            }
562
563
            $symbol->incSets();
564
            return $compiledExpression;
565
        }
566
567
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
568
            $compiledExpression = $this->compile($expr->var->var);
569
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
570
                $objectDefinition = $compiledExpression->getValue();
571
                if ($objectDefinition instanceof ClassDefinition) {
572
                    if (is_string($expr->var->name)) {
573
                        if ($objectDefinition->hasProperty($expr->var->name)) {
574
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
575
                        }
576
                    }
577
                }
578
            }
579
        }
580
581
        $this->context->debug('Unknown how to pass symbol');
582
        return new CompiledExpression();
583
    }
584
585
    /**
586
     * @param Node\Expr\AssignRef $expr
587
     * @return CompiledExpression
588
     */
589
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
590
    {
591
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
592
            return new CompiledExpression();
593
        }
594
595
        if ($expr->var instanceof Node\Expr\Variable) {
596
            $name = $expr->var->name;
597
598
            $expression = new Expression($this->context);
599
            $compiledExpression = $expression->compile($expr->expr);
600
601
            $symbol = $this->context->getSymbol($name);
602
            if ($symbol) {
603
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
604
605
                if ($expr->expr instanceof Node\Expr\Variable) {
606
                    $rightVarName = $expr->expr->name;
607
608
                    $rightSymbol = $this->context->getSymbol($rightVarName);
609
                    if ($rightSymbol) {
610
                        $rightSymbol->incUse();
611
                        $symbol->setReferencedTo($rightSymbol);
612
                    } else {
613
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
614
                    }
615
                }
616
617
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
618
            } else {
619
                $symbol = new Variable(
620
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 596 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...
621
                    $compiledExpression->getValue(),
622
                    $compiledExpression->getType(),
623
                    $this->context->getCurrentBranch()
624
                );
625
                $this->context->addVariable($symbol);
626
            }
627
628
            $symbol->incSets();
629
            return $compiledExpression;
630
        }
631
632
        $this->context->debug('Unknown how to pass symbol by ref');
633
        return new CompiledExpression();
634
    }
635
636
    /**
637
     * @param Node\Expr\Variable $expr
638
     * @return CompiledExpression
639
     */
640
    protected function passExprVariable(Node\Expr\Variable $expr)
641
    {
642
        $variable = $this->context->getSymbol($expr->name);
643
        if ($variable) {
644
            $variable->incGets();
645
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
646 11
        }
647
648 11
        $this->context->notice(
649 8
            'undefined-variable',
650
            sprintf('You trying to use undefined variable $%s', $expr->name),
651
            $expr
652 3
        );
653
654 3
        return new CompiledExpression();
655 3
    }
656 3
657 1
    /**
658 1
     * Compile Array_ expression to CompiledExpression
659 1
     *
660 1
     * @param Node\Expr\Array_ $expr
661 1
     * @return CompiledExpression
662 1
     */
663 1
    protected function getArray(Node\Expr\Array_ $expr)
664 1
    {
665 1
        if ($expr->items === array()) {
666
            return new CompiledExpression(CompiledExpression::ARR, array());
667
        }
668
669
        $resultArray = array();
670 1
671 1
        foreach ($expr->items as $item) {
672 2
            $compiledValueResult = $this->compile($item->value);
673
            if ($item->key) {
674 3
                $compiledKeyResult = $this->compile($item->key);
675
                switch ($compiledKeyResult->getType()) {
676 3
                    case CompiledExpression::INTEGER:
677
                    case CompiledExpression::DOUBLE:
678
                    case CompiledExpression::BOOLEAN:
679
                    case CompiledExpression::NULL:
680
                    case CompiledExpression::STRING:
681
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
682
                        break;
683
                    default:
684
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
685
                        return new CompiledExpression(CompiledExpression::ARR);
686
                        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...
687
                }
688
            } else {
689
                $resultArray[] = $compiledValueResult->getValue();
690
            }
691
        }
692
693
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
694
    }
695
696
    /**
697
     * Convert const fetch expr to CompiledExpression
698
     *
699
     * @param Node\Expr\ConstFetch $expr
700
     * @return CompiledExpression
701
     */
702
    protected function constFetch(Node\Expr\ConstFetch $expr)
703
    {
704
        if ($expr->name instanceof Node\Name) {
705
            if ($expr->name->parts[0] === 'true') {
706
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
707
            }
708
709
            if ($expr->name->parts[0] === 'false') {
710
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
711
            }
712
        }
713
714
        /**
715
         * @todo Implement check
716
         */
717
718
        $expression = new Expression($this->context);
719
        $compiledExpr = $expression->compile($expr->name);
720
721
        return $compiledExpr;
722
    }
723
}
724