Completed
Pull Request — master (#120)
by Enrico
02:58
created

Expression   F

Complexity

Total Complexity 167

Size/Duplication

Total Lines 798
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 70

Test Coverage

Coverage 47.19%

Importance

Changes 0
Metric Value
dl 0
loc 798
ccs 227
cts 481
cp 0.4719
rs 1.0434
c 0
b 0
f 0
wmc 167
lcom 1
cbo 70

19 Methods

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