Completed
Push — master ( b1b3d7...ed93a7 )
by Дмитрий
03:53
created

Expression   F

Complexity

Total Complexity 162

Size/Duplication

Total Lines 783
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 65

Test Coverage

Coverage 43.07%

Importance

Changes 38
Bugs 4 Features 7
Metric Value
c 38
b 4
f 7
dl 0
loc 783
ccs 199
cts 462
cp 0.4307
rs 1.0434
wmc 162
lcom 1
cbo 65

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D factory() 0 114 39
D compile() 0 95 28
C passProperty() 0 63 11
A declareVariable() 0 10 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 35 7
B passCastBoolean() 0 17 6
B passCastInt() 0 17 6
B passCastFloat() 0 17 6
B passCastString() 0 17 6
A passCastUnset() 0 12 2
C passPropertyFetch() 0 41 7
B passConstFetch() 0 22 4
D passSymbol() 0 86 16
B passSymbolByRef() 0 39 5
A passExprVariable() 0 16 2
D getArray() 0 32 9
A constFetch() 0 17 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\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 162 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 373
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 373
        $this->context = $context;
38 373
        $this->eventManager = $eventManager;
39 373
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 362
    protected function factory($expr)
46
    {
47 362
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 362
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 362
            case Node\Expr\FuncCall::class:
54 5
                return new Expression\FunctionCall();
55 358
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 358
            case Node\Expr\New_::class:
61
                return new Expression\Operators\NewOp();
62 358
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * Assign
66
             */
67 358
            case Node\Expr\AssignOp\Pow::class:
68
                return new Expression\AssignOp\Pow();
69 358
            case Node\Expr\AssignOp\Plus::class:
70
                return new Expression\AssignOp\Plus();
71 358
            case Node\Expr\AssignOp\Minus::class:
72
                return new Expression\AssignOp\Minus();
73 358
            case Node\Expr\AssignOp\Mod::class:
74
                return new Expression\AssignOp\Mod();
75 358
            case Node\Expr\AssignOp\BitwiseOr::class:
76
                return new Expression\AssignOp\BitwiseOr();
77 358
            case Node\Expr\AssignOp\BitwiseAnd::class:
78
                return new Expression\AssignOp\BitwiseAnd();
79
            /**
80
             * BinaryOp
81
             */
82 358
            case Node\Expr\BinaryOp\Identical::class:
83 28
                return new Expression\BinaryOp\Identical();
84 330
            case Node\Expr\BinaryOp\Concat::class:
85 1
                return new Expression\Operators\Contact();
86 329
            case Node\Expr\BinaryOp\NotIdentical::class:
87 14
                return new Expression\BinaryOp\NotIdentical();
88 315
            case Node\Expr\BinaryOp\Equal::class:
89 34
                return new Expression\BinaryOp\Equal();
90 281
            case Node\Expr\BinaryOp\NotEqual::class:
91 17
                return new Expression\BinaryOp\NotEqual();
92
            /**
93
             * @link http://php.net/manual/en/language.operators.increment.php
94
             */
95 264
            case Node\Expr\PostInc::class:
96 4
                return new Expression\Operators\PostInc();
97 260
            case Node\Expr\PostDec::class:
98 4
                return new Expression\Operators\PostDec();
99
            /**
100
             * Arithmetical
101
             */
102 256
            case Node\Expr\BinaryOp\Div::class:
103 37
                return new Expression\Operators\Arithmetical\Div();
104 219
            case Node\Expr\BinaryOp\Plus::class:
105 45
                return new Expression\Operators\Arithmetical\Plus();
106 174
            case Node\Expr\BinaryOp\Minus::class:
107 18
                return new Expression\Operators\Arithmetical\Minus();
108 156
            case Node\Expr\BinaryOp\Mul::class:
109 35
                return new Expression\Operators\Arithmetical\Mul();
110 121
            case Node\Expr\BinaryOp\Mod::class:
111 35
                return new Expression\Operators\Arithmetical\Mod();
112
            /**
113
             * Bitwise
114
             * @link http://php.net/manual/ru/language.operators.bitwise.php
115
             */
116 86
            case Node\Expr\BinaryOp\BitwiseOr::class:
117
                return new Expression\Operators\Bitwise\BitwiseOr();
118 86
            case Node\Expr\BinaryOp\BitwiseXor::class:
119
                return new Expression\Operators\Bitwise\BitwiseXor();
120 86
            case Node\Expr\BinaryOp\BitwiseAnd::class:
121
                return new Expression\Operators\Bitwise\BitwiseAnd();
122 86
            case Node\Expr\BinaryOp\ShiftRight::class:
123
                return new Expression\Operators\Bitwise\ShiftRight();
124 86
            case Node\Expr\BinaryOp\ShiftLeft::class:
125
                return new Expression\Operators\Bitwise\ShiftLeft();
126 86
            case Node\Expr\BitwiseNot::class:
127
                return new Expression\Operators\Bitwise\BitwiseNot();
128
            /**
129
             * Logical
130
             */
131 86
            case Node\Expr\BinaryOp\BooleanOr::class:
132 10
                return new Expression\Operators\Logical\BooleanOr();
133 76
            case Node\Expr\BinaryOp\BooleanAnd::class:
134 5
                return new Expression\Operators\Logical\BooleanAnd();
135 71
            case Node\Expr\BooleanNot::class:
136 5
                return new Expression\Operators\Logical\BooleanNot();
137
            /**
138
             * Comparison
139
             */
140 66
            case Node\Expr\BinaryOp\Greater::class:
141 12
                return new Expression\Operators\Comparison\Greater();
142 54
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
143 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
144 42
            case Node\Expr\BinaryOp\Smaller::class:
145 12
                return new Expression\Operators\Comparison\Smaller();
146 30
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
147 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
148
            /**
149
             * Another
150
             */
151 18
            case Node\Expr\UnaryMinus::class:
152 9
                return new Expression\Operators\UnaryMinus();
153 9
            case Node\Expr\UnaryPlus::class:
154 9
                return new Expression\Operators\UnaryPlus();
155
        }
156
157
        return false;
158
    }
159
160
    /**
161
     * @param object|string $expr
162
     * @return CompiledExpression
163
     */
164 373
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 704 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...
165
    {
166 373
        if (is_string($expr)) {
167
            return new CompiledExpression(CompiledExpression::STRING, $expr);
168
        }
169
170 373
        if (is_null($expr)) {
171
            return new CompiledExpression(CompiledExpression::NULL);
172
        }
173
174 373
        if (!is_object($expr)) {
175
            throw new InvalidArgumentException('$expr must be string/object/null');
176
        }
177
178 373
        $this->eventManager->fire(
179 373
            ExpressionBeforeCompile::EVENT_NAME,
180 373
            new ExpressionBeforeCompile(
181 373
                $expr,
182 373
                $this->context
183 373
            )
184 373
        );
185
        
186 373
        $className = get_class($expr);
187
        switch ($className) {
188 373
            case Node\Arg::class:
189
                /**
190
                 * @todo Better compile
191
                 */
192
                return $this->compile($expr->value);
193 373
            case Node\Expr\PropertyFetch::class:
194
                return $this->passPropertyFetch($expr);
195 373
            case Node\Stmt\Property::class:
196
                return $this->passProperty($expr);
197 373
            case Node\Expr\ClassConstFetch::class:
198
                return $this->passConstFetch($expr);
199 373
            case Node\Expr\Assign::class:
200 5
                return $this->passSymbol($expr);
201 373
            case Node\Expr\AssignRef::class:
202 1
                return $this->passSymbolByRef($expr);
203 373
            case Node\Expr\Variable::class:
204 3
                return $this->passExprVariable($expr);
205
            /**
206
             * Cast operators
207
             */
208 373
            case Node\Expr\Cast\Bool_::class:
209
                return $this->passCastBoolean($expr);
210 373
            case Node\Expr\Cast\Int_::class:
211
                return $this->passCastInt($expr);
212 373
            case Node\Expr\Cast\Double::class:
213
                return $this->passCastFloat($expr);
214 373
            case Node\Expr\Cast\String_::class:
215
                return $this->passCastString($expr);
216 373
            case Node\Expr\Cast\Unset_::class:
217
                return $this->passCastUnset($expr);
218
            /**
219
             * Expressions
220
             */
221 373
            case Node\Expr\Array_::class:
222 13
                return $this->getArray($expr);
223 372
            case Node\Expr\ConstFetch::class:
224 3
                return $this->constFetch($expr);
225 372
            case Node\Name::class:
226 5
                return $this->getNodeName($expr);
227 372
            case Node\Name\FullyQualified::class:
228
                return $this->getFullyQualifiedNodeName($expr);
229
            /**
230
             * Simple Scalar(s)
231
             */
232 372
            case \PHPSA\Node\Scalar\Nil::class:
233 8
                return new CompiledExpression(CompiledExpression::NULL);
234 371
            case Node\Scalar\LNumber::class:
235 266
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
236 367
            case Node\Scalar\DNumber::class:
237 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
238 366
            case Node\Scalar\String_::class:
239 10
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
240 364
            case \PHPSA\Node\Scalar\Boolean::class:
241 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
242 362
            case \PHPSA\Node\Scalar\Fake::class:
243 29
                return new CompiledExpression($expr->type, $expr->value);
244
        }
245
246 362
        $expressionCompiler = $this->factory($expr);
247 362
        if (!$expressionCompiler) {
248
            $this->context->debug("Expression compiler is not implemented for {$className}");
249
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
250
        }
251
252 362
        $result = $expressionCompiler->pass($expr, $this->context);
253 362
        if (!$result instanceof CompiledExpression) {
254
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
255
        }
256
257 362
        return $result;
258
    }
259
260
    /**
261
     * @todo Implement
262
     *
263
     * @param Node\Stmt\Property $st
264
     * @return CompiledExpression
265
     */
266
    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...
267
    {
268
        $docBlock = $st->getDocComment();
269
        if (!$docBlock) {
270
            var_dump($st->getAttributes());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($st->getAttributes()); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
271
            die();
272
273
            $this->context->notice(
0 ignored issues
show
Unused Code introduced by
$this->context->notice('...r %s() property', $st); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
274
                'missing-docblock',
275
                'Missing docblock for %s() property',
276
                $st
277
            );
278
279
            return new CompiledExpression();
280
        }
281
282
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
283
284
        $varTags = $phpdoc->getTagsByName('var');
285
        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...
286
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
287
            $varTag = current($varTags);
288
289
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
290
291
            try {
292
                $type = $typeResolver->resolve($varTag->getType());
293
            } catch (\InvalidArgumentException $e) {
294
                return new CompiledExpression();
295
            }
296
297
            if ($type) {
298
                switch (get_class($type)) {
299
                    case \phpDocumentor\Reflection\Types\Object_::class:
300
                        return new CompiledExpression(
301
                            CompiledExpression::OBJECT
302
                        );
303
                    case \phpDocumentor\Reflection\Types\Integer::class:
304
                        return new CompiledExpression(
305
                            CompiledExpression::INTEGER
306
                        );
307
                    case \phpDocumentor\Reflection\Types\String_::class:
308
                        return new CompiledExpression(
309
                            CompiledExpression::STRING
310
                        );
311
                    case \phpDocumentor\Reflection\Types\Float_::class:
312
                        return new CompiledExpression(
313
                            CompiledExpression::DOUBLE
314
                        );
315
                    case \phpDocumentor\Reflection\Types\Null_::class:
316
                        return new CompiledExpression(
317
                            CompiledExpression::NULL
318
                        );
319
                    case \phpDocumentor\Reflection\Types\Boolean::class:
320
                        return new CompiledExpression(
321
                            CompiledExpression::BOOLEAN
322
                        );
323
                }
324
            }
325
        }
326
327
        return new CompiledExpression();
328
    }
329
330
    /**
331
     * @param Node\Expr\Variable $expr
332
     * @return CompiledExpression
333
     */
334
    public function declareVariable(Node\Expr\Variable $expr)
335
    {
336
        $variable = $this->context->getSymbol($expr->name);
337
        if (!$variable) {
338
            $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...
339
            $this->context->addVariable($variable);
340
        }
341
342
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
343
    }
344
345
    /**
346
     * @param Node\Name\FullyQualified $expr
347
     * @return CompiledExpression
348
     */
349
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
350
    {
351
        $this->context->debug('Unimplemented FullyQualified', $expr);
352
        
353
        return new CompiledExpression;
354
    }
355
356
    /**
357
     * @param Node\Name $expr
358
     * @return CompiledExpression
359
     */
360 5
    public function getNodeName(Node\Name $expr)
361
    {
362 5
        if ($expr->toString() === 'null') {
363
            return new CompiledExpression(CompiledExpression::NULL);
364
        }
365
366 5
        if (in_array($expr, ['parent'], true)) {
367
            /** @var ClassDefinition $scope */
368
            $scope = $this->context->scope;
369
            assert($scope instanceof ClassDefinition);
370
371
            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...
372
                $definition = $scope->getExtendsClassDefinition();
373
                if ($definition) {
374
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
375
                }
376
            } else {
377
                $this->context->notice(
378
                    'no-parent',
379
                    'Cannot access parent:: when current class scope has no parent',
380
                    $expr
381
                );
382
            }
383
        }
384
385 5
        if (in_array($expr, ['self', 'static'], true)) {
386
            return CompiledExpression::fromZvalValue($this->context->scope);
387
        }
388
389 5
        if (defined($expr)) {
390
            return CompiledExpression::fromZvalValue(constant($expr));
391
        }
392
393 5
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
394
    }
395
396
    /**
397
     * (bool) {$expr}
398
     *
399
     * @param Node\Expr\Cast\Bool_ $expr
400
     * @return CompiledExpression
401
     */
402
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
403
    {
404
        $compiledExpression = $this->compile($expr->expr);
405
406
        switch ($compiledExpression->getType()) {
407
            case CompiledExpression::BOOLEAN:
408
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
409
                return $compiledExpression;
410
            case CompiledExpression::DOUBLE:
411
            case CompiledExpression::INTEGER:
412
            case CompiledExpression::NUMBER:
413
            case CompiledExpression::STRING:
414
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
415
        }
416
417
        return new CompiledExpression();
418
    }
419
420
    /**
421
     * (int) {$expr}
422
     *
423
     * @param Node\Expr\Cast\Int_ $expr
424
     * @return CompiledExpression
425
     */
426
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
427
    {
428
        $compiledExpression = $this->compile($expr->expr);
429
430
        switch ($compiledExpression->getType()) {
431
            case CompiledExpression::INTEGER:
432
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
433
                return $compiledExpression;
434
            case CompiledExpression::BOOLEAN:
435
            case CompiledExpression::DOUBLE:
436
            case CompiledExpression::NUMBER:
437
            case CompiledExpression::STRING:
438
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
439
        }
440
441
        return new CompiledExpression();
442
    }
443
444
    /**
445
     * (float) {$expr}
446
     *
447
     * @param Node\Expr\Cast\Double $expr
448
     * @return CompiledExpression
449
     */
450
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
451
    {
452
        $compiledExpression = $this->compile($expr->expr);
453
454
        switch ($compiledExpression->getType()) {
455
            case CompiledExpression::DOUBLE:
456
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
457
                return $compiledExpression;
458
            case CompiledExpression::BOOLEAN:
459
            case CompiledExpression::INTEGER:
460
            case CompiledExpression::NUMBER:
461
            case CompiledExpression::STRING:
462
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
463
        }
464
465
        return new CompiledExpression();
466
    }
467
468
    /**
469
     * (string) {$expr}
470
     *
471
     * @param Node\Expr\Cast\String_ $expr
472
     * @return CompiledExpression
473
     */
474
    protected function passCastString(Node\Expr\Cast\String_ $expr)
475
    {
476
        $compiledExpression = $this->compile($expr->expr);
477
478
        switch ($compiledExpression->getType()) {
479
            case CompiledExpression::STRING:
480
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
481
                return $compiledExpression;
482
            case CompiledExpression::BOOLEAN:
483
            case CompiledExpression::INTEGER:
484
            case CompiledExpression::NUMBER:
485
            case CompiledExpression::DOUBLE:
486
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
487
        }
488
489
        return new CompiledExpression();
490
    }
491
492
    /**
493
     * (unset) {$expr}
494
     *
495
     * @param Node\Expr\Cast\Unset_ $expr
496
     * @return CompiledExpression
497
     */
498
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
499
    {
500
        $compiledExpression = $this->compile($expr->expr);
501
502
        switch ($compiledExpression->getType()) {
503
            case CompiledExpression::NULL:
504
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
505
                return $compiledExpression;
506
        }
507
508
        return new CompiledExpression(CompiledExpression::NULL, null);
509
    }
510
511
    /**
512
     * @param Node\Expr\PropertyFetch $expr
513
     * @return CompiledExpression
514
     */
515
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
516
    {
517
        $propertNameCE = $this->compile($expr->name);
518
519
        $scopeExpression = $this->compile($expr->var);
520
        if ($scopeExpression->isObject()) {
521
            $scopeExpressionValue = $scopeExpression->getValue();
522
            if ($scopeExpressionValue instanceof ClassDefinition) {
523
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
524
                if ($propertyName) {
525
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
526
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
527
                        return $this->compile($property);
528
                    } else {
529
                        $this->context->notice(
530
                            'undefined-property',
531
                            sprintf(
532
                                'Property %s does not exist in %s scope',
533
                                $propertyName,
534
                                $scopeExpressionValue->getName()
535
                            ),
536
                            $expr
537
                        );
538
                    }
539
                }
540
            }
541
542
            return new CompiledExpression(CompiledExpression::UNKNOWN);
543
        } elseif (!$scopeExpression->canBeObject()) {
544
            return new CompiledExpression(CompiledExpression::UNKNOWN);
545
        }
546
547
        $this->context->notice(
548
            'property-fetch-on-non-object',
549
            "It's not possible to fetch property on not object",
550
            $expr,
551
            Check::CHECK_BETA
552
        );
553
554
        return new CompiledExpression(CompiledExpression::UNKNOWN);
555
    }
556
557
    /**
558
     * @param Node\Expr\ClassConstFetch $expr
559
     * @return CompiledExpression
560
     */
561
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
562
    {
563
        $leftCE = $this->compile($expr->class);
564
        if ($leftCE->isObject()) {
565
            $leftCEValue = $leftCE->getValue();
566
            if ($leftCEValue instanceof ClassDefinition) {
567
                if (!$leftCEValue->hasConst($expr->name)) {
568
                    $this->context->notice(
569
                        'undefined-const',
570
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
571
                        $expr
572
                    );
573
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
574
                }
575
576
                return new CompiledExpression();
577
            }
578
        }
579
580
        $this->context->debug('Unknown const fetch', $expr);
581
        return new CompiledExpression();
582
    }
583
584
    /**
585
     * @param Node\Expr\Assign $expr
586
     * @return CompiledExpression
587
     */
588 5
    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...
589
    {
590 5
        $compiledExpression = $this->compile($expr->expr);
591
592 5
        if ($expr->var instanceof Node\Expr\List_) {
593
            $isCorrectType = false;
594
595
            switch ($compiledExpression->getType()) {
596
                case CompiledExpression::ARR:
597
                    $isCorrectType = true;
598
                    break;
599
            }
600
601
            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...
602
                foreach ($expr->var->vars as $key => $var) {
603
                    if ($var instanceof Node\Expr\Variable) {
604
                        if (!isset($expr->var->name)) {
605
                            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...
606
                        }
607
                        $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...
608
609
                        $symbol = $this->context->getSymbol($name);
610
                        if (!$symbol) {
611
                            $symbol = new Variable(
612
                                $name,
613
                                null,
614
                                CompiledExpression::UNKNOWN,
615
                                $this->context->getCurrentBranch()
616
                            );
617
                            $this->context->addVariable($symbol);
618
                        }
619
620
                        if (!$isCorrectType) {
621
                            $symbol->modify(CompiledExpression::NULL, null);
622
                        }
623
624
                        $symbol->incSets();
625
                    }
626
                }
627
            }
628
629
            return new CompiledExpression();
630
        }
631
632 5
        if ($expr->var instanceof Node\Expr\Variable) {
633 5
            $name = $expr->var->name;
634
635 5
            $symbol = $this->context->getSymbol($name);
636 5
            if ($symbol) {
637 1
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
638 1
                $this->context->modifyReferencedVariables(
639 1
                    $symbol,
640 1
                    $compiledExpression->getType(),
641 1
                    $compiledExpression->getValue()
642 1
                );
643 1
            } else {
644 5
                $symbol = new Variable(
645 5
                    $name,
646 5
                    $compiledExpression->getValue(),
647 5
                    $compiledExpression->getType(),
648 5
                    $this->context->getCurrentBranch()
649 5
                );
650 5
                $this->context->addVariable($symbol);
651
            }
652
653 5
            $symbol->incSets();
654 5
            return $compiledExpression;
655
        }
656
657
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
658
            $compiledExpression = $this->compile($expr->var->var);
659
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
660
                $objectDefinition = $compiledExpression->getValue();
661
                if ($objectDefinition instanceof ClassDefinition) {
662
                    if (is_string($expr->var->name)) {
663
                        if ($objectDefinition->hasProperty($expr->var->name)) {
664
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
665
                        }
666
                    }
667
                }
668
            }
669
        }
670
671
        $this->context->debug('Unknown how to pass symbol');
672
        return new CompiledExpression();
673
    }
674
675
    /**
676
     * @param Node\Expr\AssignRef $expr
677
     * @return CompiledExpression
678
     */
679 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
680
    {
681 1
        if ($expr->var instanceof Node\Expr\Variable) {
682 1
            $name = $expr->var->name;
683
684 1
            $compiledExpression = $this->compile($expr->expr);
685
686 1
            $symbol = $this->context->getSymbol($name);
687 1
            if ($symbol) {
688
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
689
            } else {
690 1
                $symbol = new Variable(
691 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 682 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...
692 1
                    $compiledExpression->getValue(),
693 1
                    $compiledExpression->getType(),
694 1
                    $this->context->getCurrentBranch()
695 1
                );
696 1
                $this->context->addVariable($symbol);
697
            }
698
699 1
            if ($expr->expr instanceof Node\Expr\Variable) {
700 1
                $rightVarName = $expr->expr->name;
701
702 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
703 1
                if ($rightSymbol) {
704 1
                    $rightSymbol->incUse();
705 1
                    $symbol->setReferencedTo($rightSymbol);
706 1
                } else {
707
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
708
                }
709 1
            }
710
711 1
            $symbol->incSets();
712 1
            return $compiledExpression;
713
        }
714
715
        $this->context->debug('Unknown how to pass symbol by ref');
716
        return new CompiledExpression();
717
    }
718
719
    /**
720
     * @param Node\Expr\Variable $expr
721
     * @return CompiledExpression
722
     */
723 3
    protected function passExprVariable(Node\Expr\Variable $expr)
724
    {
725 3
        $variable = $this->context->getSymbol($expr->name);
726 3
        if ($variable) {
727 3
            $variable->incGets();
728 3
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
729
        }
730
731
        $this->context->notice(
732
            'undefined-variable',
733
            sprintf('You trying to use undefined variable $%s', $expr->name),
734
            $expr
735
        );
736
737
        return new CompiledExpression();
738
    }
739
740
    /**
741
     * Compile Array_ expression to CompiledExpression
742
     *
743
     * @param Node\Expr\Array_ $expr
744
     * @return CompiledExpression
745
     */
746 13
    protected function getArray(Node\Expr\Array_ $expr)
747
    {
748 13
        if ($expr->items === array()) {
749 8
            return new CompiledExpression(CompiledExpression::ARR, array());
750
        }
751
752 5
        $resultArray = array();
753
754 5
        foreach ($expr->items as $item) {
755 5
            $compiledValueResult = $this->compile($item->value);
756 5
            if ($item->key) {
757 1
                $compiledKeyResult = $this->compile($item->key);
758 1
                switch ($compiledKeyResult->getType()) {
759 1
                    case CompiledExpression::INTEGER:
760 1
                    case CompiledExpression::DOUBLE:
761 1
                    case CompiledExpression::BOOLEAN:
762 1
                    case CompiledExpression::NULL:
763 1
                    case CompiledExpression::STRING:
764 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
765 1
                        break;
766
                    default:
767
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
768
                        return new CompiledExpression(CompiledExpression::ARR);
769
                        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...
770 1
                }
771 1
            } else {
772 4
                $resultArray[] = $compiledValueResult->getValue();
773
            }
774 5
        }
775
776 5
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
777
    }
778
779
    /**
780
     * Convert const fetch expr to CompiledExpression
781
     *
782
     * @param Node\Expr\ConstFetch $expr
783
     * @return CompiledExpression
784
     */
785 3
    protected function constFetch(Node\Expr\ConstFetch $expr)
786
    {
787 3
        if ($expr->name instanceof Node\Name) {
788 3
            if ($expr->name->parts[0] === 'true') {
789 3
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
790
            }
791
792
            if ($expr->name->parts[0] === 'false') {
793
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
794
            }
795
        }
796
797
        /**
798
         * @todo Implement check
799
         */
800
        return $this->compile($expr->name);
801
    }
802
}
803