Completed
Pull Request — master (#193)
by Enrico
06:57 queued 02:48
created

Expression   F

Complexity

Total Complexity 161

Size/Duplication

Total Lines 728
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 90

Test Coverage

Coverage 73.51%

Importance

Changes 0
Metric Value
dl 0
loc 728
ccs 333
cts 453
cp 0.7351
rs 1.0434
c 0
b 0
f 0
wmc 161
lcom 1
cbo 90

15 Methods

Rating   Name   Duplication   Size   Complexity  
B passSymbolByRef() 0 39 5
A passExprVariable() 0 16 2
A __construct() 0 5 1
D compile() 0 84 23
D factory() 0 184 69
C passProperty() 0 54 11
A declareVariable() 0 10 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 36 7
C passPropertyFetch() 0 41 7
B passConstFetch() 0 22 4
C passSymbol() 0 61 13
B compileVariableDeclaration() 0 30 3
D getArray() 0 31 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 phpDocumentor\Reflection\DocBlockFactory;
10
use PHPSA\Check;
11
use PHPSA\CompiledExpression;
12
use PHPSA\Compiler\Event\ExpressionBeforeCompile;
13
use PHPSA\Context;
14
use PhpParser\Node;
15
use PHPSA\Definition\ClassDefinition;
16
use PHPSA\Exception\RuntimeException;
17
use PHPSA\Variable;
18
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
19
use Webiny\Component\EventManager\EventManager;
20
21
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 161 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
22
{
23
    /**
24
     * @var Context
25
     */
26
    protected $context;
27
28
    /**
29
     * @var EventManager
30
     */
31
    protected $eventManager;
32
33
    /**
34
     * @param Context $context
35
     */
36 849
    public function __construct(Context $context, EventManager $eventManager)
37
    {
38 849
        $this->context = $context;
39 849
        $this->eventManager = $eventManager;
40 849
    }
41
42
    /**
43
     * @param $expr
44
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
45
     */
46 819
    protected function factory($expr)
47
    {
48 819
        switch (get_class($expr)) {
49
            /**
50
             * Call(s)
51
             */
52 819
            case Node\Expr\MethodCall::class:
53
                return new Expression\MethodCall();
54 819
            case Node\Expr\FuncCall::class:
55 10
                return new Expression\FunctionCall();
56 811
            case Node\Expr\StaticCall::class:
57 1
                return new Expression\StaticCall();
58
            /**
59
             * Operators
60
             */
61 810
            case Node\Expr\New_::class:
62 2
                return new Expression\Operators\NewOp();
63 808
            case Node\Expr\Instanceof_::class:
64
                return new Expression\Operators\InstanceOfOp();
65
            /**
66
             * AssignOp
67
             */
68 808
            case Node\Expr\AssignOp\Pow::class:
69 21
                return new Expression\AssignOp\Pow();
70 787
            case Node\Expr\AssignOp\Plus::class:
71 17
                return new Expression\AssignOp\Plus();
72 770
            case Node\Expr\AssignOp\Minus::class:
73 20
                return new Expression\AssignOp\Minus();
74 750
            case Node\Expr\AssignOp\Mul::class:
75 20
                return new Expression\AssignOp\Mul();
76 730
            case Node\Expr\AssignOp\Div::class:
77 18
                return new Expression\AssignOp\Div();
78 712
            case Node\Expr\AssignOp\Mod::class:
79 11
                return new Expression\AssignOp\Mod();
80 701
            case Node\Expr\AssignOp\BitwiseOr::class:
81 12
                return new Expression\AssignOp\BitwiseOr();
82 689
            case Node\Expr\AssignOp\BitwiseAnd::class:
83 12
                return new Expression\AssignOp\BitwiseAnd();
84 677
            case Node\Expr\AssignOp\BitwiseXor::class:
85 12
                return new Expression\AssignOp\BitwiseXor();
86 665
            case Node\Expr\AssignOp\Concat::class:
87 14
                return new Expression\AssignOp\Concat();
88 651
            case Node\Expr\AssignOp\ShiftLeft::class:
89 12
                return new Expression\AssignOp\ShiftLeft();
90 639
            case Node\Expr\AssignOp\ShiftRight::class:
91 12
                return new Expression\AssignOp\ShiftRight();
92
93
            /**
94
             * BinaryOp
95
             */
96 627
            case Node\Expr\BinaryOp\Identical::class:
97 28
                return new Expression\BinaryOp\Identical();
98 599
            case Node\Expr\BinaryOp\Concat::class:
99 15
                return new Expression\Operators\Concat();
100 584
            case Node\Expr\BinaryOp\NotIdentical::class:
101 14
                return new Expression\BinaryOp\NotIdentical();
102 570
            case Node\Expr\BinaryOp\Equal::class:
103 42
                return new Expression\BinaryOp\Equal();
104 530
            case Node\Expr\BinaryOp\NotEqual::class:
105 17
                return new Expression\BinaryOp\NotEqual();
106 513
            case Node\Expr\BinaryOp\Spaceship::class:
107 11
                return new Expression\BinaryOp\SpaceShip();
108 502
            case Node\Expr\BinaryOp\Coalesce::class:
109 3
                return new Expression\BinaryOp\Coalesce();
110
                
111
            /**
112
             * @link http://php.net/manual/en/language.operators.increment.php
113
             */
114 499
            case Node\Expr\PostInc::class:
115 8
                return new Expression\Operators\PostInc();
116 491
            case Node\Expr\PostDec::class:
117 8
                return new Expression\Operators\PostDec();
118 483
            case Node\Expr\PreInc::class:
119 8
                return new Expression\Operators\PreInc();
120 475
            case Node\Expr\PreDec::class:
121 8
                return new Expression\Operators\PreDec();
122
            /**
123
             * Arithmetical
124
             */
125 467
            case Node\Expr\BinaryOp\Div::class:
126 37
                return new Expression\Operators\Arithmetical\Div();
127 430
            case Node\Expr\BinaryOp\Plus::class:
128 45
                return new Expression\Operators\Arithmetical\Plus();
129 385
            case Node\Expr\BinaryOp\Minus::class:
130 24
                return new Expression\Operators\Arithmetical\Minus();
131 361
            case Node\Expr\BinaryOp\Mul::class:
132 35
                return new Expression\Operators\Arithmetical\Mul();
133 326
            case Node\Expr\BinaryOp\Mod::class:
134 35
                return new Expression\Operators\Arithmetical\Mod();
135 291
            case Node\Expr\BinaryOp\Pow::class:
136 21
                return new Expression\Operators\Arithmetical\Pow();
137
138
            /**
139
             * Bitwise
140
             * @link http://php.net/manual/ru/language.operators.bitwise.php
141
             */
142 270
            case Node\Expr\BinaryOp\BitwiseOr::class:
143 12
                return new Expression\Operators\Bitwise\BitwiseOr();
144 258
            case Node\Expr\BinaryOp\BitwiseXor::class:
145 12
                return new Expression\Operators\Bitwise\BitwiseXor();
146 246
            case Node\Expr\BinaryOp\BitwiseAnd::class:
147 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
148 234
            case Node\Expr\BinaryOp\ShiftRight::class:
149 12
                return new Expression\Operators\Bitwise\ShiftRight();
150 222
            case Node\Expr\BinaryOp\ShiftLeft::class:
151 12
                return new Expression\Operators\Bitwise\ShiftLeft();
152 210
            case Node\Expr\BitwiseNot::class:
153 5
                return new Expression\Operators\Bitwise\BitwiseNot();
154
            /**
155
             * Logical
156
             */
157 205
            case Node\Expr\BinaryOp\BooleanOr::class:
158 17
                return new Expression\Operators\Logical\BooleanOr();
159 188
            case Node\Expr\BinaryOp\BooleanAnd::class:
160 15
                return new Expression\Operators\Logical\BooleanAnd();
161 173
            case Node\Expr\BooleanNot::class:
162 9
                return new Expression\Operators\Logical\BooleanNot();
163 164
            case Node\Expr\BinaryOp\LogicalAnd::class:
164 15
                return new Expression\Operators\Logical\LogicalAnd();
165 149
            case Node\Expr\BinaryOp\LogicalOr::class:
166 17
                return new Expression\Operators\Logical\LogicalOr();
167 132
            case Node\Expr\BinaryOp\LogicalXor::class:
168 17
                return new Expression\Operators\Logical\LogicalXor();
169
170
            /**
171
             * Comparison
172
             */
173 115
            case Node\Expr\BinaryOp\Greater::class:
174 12
                return new Expression\Operators\Comparison\Greater();
175 103
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
176 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
177 91
            case Node\Expr\BinaryOp\Smaller::class:
178 12
                return new Expression\Operators\Comparison\Smaller();
179 79
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
180 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
181
182
            /**
183
             * Casts
184
             */
185 67
            case Node\Expr\Cast\Array_::class:
186 3
                return new Expression\Casts\ArrayCast();
187 65
            case Node\Expr\Cast\Bool_::class:
188 8
                return new Expression\Casts\BoolCast();
189 58
            case Node\Expr\Cast\Int_::class:
190 8
                return new Expression\Casts\IntCast();
191 51
            case Node\Expr\Cast\Double::class:
192 8
                return new Expression\Casts\DoubleCast();
193 44
            case Node\Expr\Cast\Object_::class:
194 2
                return new Expression\Casts\ObjectCast();
195 43
            case Node\Expr\Cast\String_::class:
196 7
                return new Expression\Casts\StringCast();
197 37
            case Node\Expr\Cast\Unset_::class:
198 2
                return new Expression\Casts\UnsetCast();
199
200
201
            /**
202
             * Other
203
             */
204 35
            case Node\Expr\Closure::class:
205
                return new Expression\Closure();
206 35
            case Node\Expr\UnaryMinus::class:
207 9
                return new Expression\Operators\UnaryMinus();
208 26
            case Node\Expr\UnaryPlus::class:
209 9
                return new Expression\Operators\UnaryPlus();
210 17
            case Node\Expr\Exit_::class:
211 1
                return new Expression\ExitOp();
212 16
            case Node\Expr\Isset_::class:
213 3
                return new Expression\IssetOp();
214 13
            case Node\Expr\Print_::class:
215 2
                return new Expression\PrintOp();
216 12
            case Node\Expr\Empty_::class:
217 5
                return new Expression\EmptyOp();
218 7
            case Node\Expr\Eval_::class:
219 2
                return new Expression\EvalOp();
220 5
            case Node\Expr\ErrorSuppress::class:
221 2
                return new Expression\ErrorSuppress();
222 3
            case Node\Expr\Clone_::class:
223 1
                return new Expression\CloneOp();
224 2
            case Node\Expr\Ternary::class:
225 2
                return new Expression\Ternary();
226
        }
227
228
        return false;
229
    }
230
231
    /**
232
     * @param object|string $expr
233
     * @throws InvalidArgumentException when $expr is not string/object/null
234
     * @throws RuntimeException when compiler class does not return a CompiledExpression
235
     * @return CompiledExpression
236
     */
237 849
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 544 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
238
    {
239 849
        if (is_string($expr)) {
240 16
            return new CompiledExpression(CompiledExpression::STRING, $expr);
241
        }
242
243 849
        if (is_null($expr)) {
244 8
            return new CompiledExpression(CompiledExpression::NULL);
245
        }
246
247 849
        if (!is_object($expr)) {
248
            throw new InvalidArgumentException('$expr must be string/object/null');
249
        }
250
251 849
        $this->eventManager->fire(
252 849
            ExpressionBeforeCompile::EVENT_NAME,
253 849
            new ExpressionBeforeCompile(
254 849
                $expr,
255 849
                $this->context
256 849
            )
257 849
        );
258
259 849
        $className = get_class($expr);
260
        switch ($className) {
261 849
            case Node\Arg::class:
262
                /**
263
                 * @todo Better compile
264
                 */
265 2
                return $this->compile($expr->value);
266 849
            case Node\Expr\PropertyFetch::class:
267
                return $this->passPropertyFetch($expr);
268 849
            case Node\Stmt\Property::class:
269
                return $this->passProperty($expr);
270 849
            case Node\Expr\ClassConstFetch::class:
271
                return $this->passConstFetch($expr);
272 849
            case Node\Expr\Assign::class:
273 29
                return $this->passSymbol($expr);
274 849
            case Node\Expr\AssignRef::class:
275 1
                return $this->passSymbolByRef($expr);
276 849
            case Node\Expr\Variable::class:
277 12
                return $this->passExprVariable($expr);
278
279
            /**
280
             * Expressions
281
             */
282 849
            case Node\Expr\Array_::class:
283 36
                return $this->getArray($expr);
284 848
            case Node\Expr\ConstFetch::class:
285 7
                return $this->constFetch($expr);
286 848
            case Node\Name::class:
287 25
                return $this->getNodeName($expr);
288 848
            case Node\Name\FullyQualified::class:
289
                return $this->getFullyQualifiedNodeName($expr);
290
291
            /**
292
             * Simple Scalar(s)
293
             */
294 848
            case \PHPSA\Node\Scalar\Nil::class:
295 25
                return new CompiledExpression(CompiledExpression::NULL);
296 847
            case Node\Scalar\LNumber::class:
297 512
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
298 827
            case Node\Scalar\DNumber::class:
299 212
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
300 826
            case Node\Scalar\String_::class:
301 50
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
302 821
            case \PHPSA\Node\Scalar\Boolean::class:
303 241
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
304 819
            case \PHPSA\Node\Scalar\Fake::class:
305 79
                return new CompiledExpression($expr->type, $expr->value);
306
        }
307
308 819
        $expressionCompiler = $this->factory($expr);
309 819
        if (!$expressionCompiler) {
310
            $this->context->debug("Expression compiler is not implemented for {$className}");
311
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
312
        }
313
314 819
        $result = $expressionCompiler->pass($expr, $this->context);
315 819
        if (!$result instanceof CompiledExpression) {
316
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
317
        }
318
319 819
        return $result;
320
    }
321
322
    /**
323
     * @todo Implement
324
     *
325
     * @param Node\Stmt\Property $st
326
     * @return CompiledExpression
327
     */
328
    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...
329
    {
330
        $docBlock = $st->getDocComment();
331
        if (!$docBlock) {
332
            return new CompiledExpression();
333
        }
334
335
        $phpdoc = DocBlockFactory::createInstance()->create($docBlock->getText());
336
337
        $varTags = $phpdoc->getTagsByName('var');
338
        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...
339
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
340
            $varTag = current($varTags);
341
342
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
343
344
            try {
345
                $type = $typeResolver->resolve($varTag->getType());
346
            } catch (\InvalidArgumentException $e) {
347
                return new CompiledExpression();
348
            }
349
350
            if ($type) {
351
                switch (get_class($type)) {
352
                    case \phpDocumentor\Reflection\Types\Object_::class:
353
                        return new CompiledExpression(
354
                            CompiledExpression::OBJECT
355
                        );
356
                    case \phpDocumentor\Reflection\Types\Integer::class:
357
                        return new CompiledExpression(
358
                            CompiledExpression::INTEGER
359
                        );
360
                    case \phpDocumentor\Reflection\Types\String_::class:
361
                        return new CompiledExpression(
362
                            CompiledExpression::STRING
363
                        );
364
                    case \phpDocumentor\Reflection\Types\Float_::class:
365
                        return new CompiledExpression(
366
                            CompiledExpression::DOUBLE
367
                        );
368
                    case \phpDocumentor\Reflection\Types\Null_::class:
369
                        return new CompiledExpression(
370
                            CompiledExpression::NULL
371
                        );
372
                    case \phpDocumentor\Reflection\Types\Boolean::class:
373
                        return new CompiledExpression(
374
                            CompiledExpression::BOOLEAN
375
                        );
376
                }
377
            }
378
        }
379
380
        return new CompiledExpression();
381
    }
382
383
    /**
384
     * @param Node\Expr\Variable $expr
385
     * @param mixed $value
386
     * @param int $type
387
     * @return CompiledExpression
388
     */
389 1
    public function declareVariable(Node\Expr\Variable $expr, $value = null, $type = CompiledExpression::UNKNOWN)
390
    {
391 1
        $variable = $this->context->getSymbol($expr->name);
392 1
        if (!$variable) {
393 1
            $variable = new Variable($expr->name, $value, $type, $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...
394 1
            $this->context->addVariable($variable);
395 1
        }
396
397 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
398
    }
399
400
    /**
401
     * @param Node\Name\FullyQualified $expr
402
     * @return CompiledExpression
403
     */
404
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
405
    {
406
        $this->context->debug('Unimplemented FullyQualified', $expr);
407
408
        return new CompiledExpression;
409
    }
410
411
    /**
412
     * @param Node\Name $expr
413
     * @return CompiledExpression
414
     */
415 25
    public function getNodeName(Node\Name $expr)
416
    {
417 25
        $nodeString = $expr->toString();
418 25
        if ($nodeString === 'null') {
419 1
            return new CompiledExpression(CompiledExpression::NULL);
420
        }
421
422 24
        if (in_array($nodeString, ['parent'], true)) {
423
            /** @var ClassDefinition $scope */
424
            $scope = $this->context->scope;
425
            assert($scope instanceof ClassDefinition);
426
427
            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...
428
                $definition = $scope->getExtendsClassDefinition();
429
                if ($definition) {
430
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
431
                }
432
            } else {
433
                $this->context->notice(
434
                    'no-parent',
435
                    'Cannot access parent:: when current class scope has no parent',
436
                    $expr
437
                );
438
            }
439
        }
440
441 24
        if (in_array($nodeString, ['self', 'static'], true)) {
442 1
            return CompiledExpression::fromZvalValue($this->context->scope);
443
        }
444
445 23
        if (defined($nodeString)) {
446 1
            return CompiledExpression::fromZvalValue(constant($expr));
447
        }
448
449 23
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
450
    }
451
452
    /**
453
     * @param Node\Expr\PropertyFetch $expr
454
     * @return CompiledExpression
455
     */
456
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
457
    {
458
        $propertNameCE = $this->compile($expr->name);
459
460
        $scopeExpression = $this->compile($expr->var);
461
        if ($scopeExpression->isObject()) {
462
            $scopeExpressionValue = $scopeExpression->getValue();
463
            if ($scopeExpressionValue instanceof ClassDefinition) {
464
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
465
                if ($propertyName) {
466
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
467
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
468
                        return $this->compile($property);
469
                    } else {
470
                        $this->context->notice(
471
                            'undefined-property',
472
                            sprintf(
473
                                'Property %s does not exist in %s scope',
474
                                $propertyName,
475
                                $scopeExpressionValue->getName()
476
                            ),
477
                            $expr
478
                        );
479
                    }
480
                }
481
            }
482
483
            return new CompiledExpression(CompiledExpression::UNKNOWN);
484
        } elseif ($scopeExpression->canBeObject()) {
485
            return new CompiledExpression(CompiledExpression::UNKNOWN);
486
        }
487
488
        $this->context->notice(
489
            'property-fetch-on-non-object',
490
            "It's not possible to fetch a property on a non-object",
491
            $expr,
492
            Check::CHECK_BETA
493
        );
494
495
        return new CompiledExpression(CompiledExpression::UNKNOWN);
496
    }
497
498
    /**
499
     * @param Node\Expr\ClassConstFetch $expr
500
     * @return CompiledExpression
501
     */
502
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
503
    {
504
        $leftCE = $this->compile($expr->class);
505
        if ($leftCE->isObject()) {
506
            $leftCEValue = $leftCE->getValue();
507
            if ($leftCEValue instanceof ClassDefinition) {
508
                if (!$leftCEValue->hasConst($expr->name, true)) {
509
                    $this->context->notice(
510
                        'undefined-const',
511
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
512
                        $expr
513
                    );
514
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
515
                }
516
517
                return new CompiledExpression();
518
            }
519
        }
520
521
        $this->context->debug('Unknown const fetch', $expr);
522
        return new CompiledExpression();
523
    }
524
525
    /**
526
     * @param Node\Expr\Assign $expr
527
     * @return CompiledExpression
528
     */
529 29
    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...
530
    {
531 29
        $compiledExpression = $this->compile($expr->expr);
532
533 29
        if ($expr->var instanceof Node\Expr\List_) {
534 1
            $isCorrectType = $compiledExpression->isArray();
535
536 1
            foreach ($expr->var->vars as $key => $var) {
537 1
                if (!$var instanceof Node\Expr\Variable) {
538 1
                    continue;
539
                }
540
541 1
                if ($var->name instanceof Node\Expr\Variable) {
542 1
                    $this->compileVariableDeclaration($this->compile($var->name), new CompiledExpression());
543 1
                    continue;
544
                }
545
546 1
                $symbol = $this->context->getSymbol($var->name);
547 1
                if (!$symbol) {
548 1
                    $symbol = new Variable(
549 1
                        $var->name,
0 ignored issues
show
Bug introduced by
It seems like $var->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...
550 1
                        null,
551 1
                        CompiledExpression::UNKNOWN,
552 1
                        $this->context->getCurrentBranch()
553 1
                    );
554 1
                    $this->context->addVariable($symbol);
555 1
                }
556
557 1
                if (!$isCorrectType) {
558
                    $symbol->modify(CompiledExpression::NULL, null);
559
                }
560
561 1
                $symbol->incSets();
562 1
            }
563
564 1
            return new CompiledExpression();
565
        }
566
567 29
        if ($expr->var instanceof Node\Expr\Variable) {
568 29
            $this->compileVariableDeclaration($this->compile($expr->var->name), $compiledExpression);
569
570 29
            return $compiledExpression;
571
        }
572
573 2
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
574 2
            $compiledExpression = $this->compile($expr->var->var);
575 2
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
576 2
                $objectDefinition = $compiledExpression->getValue();
577 2
                if ($objectDefinition instanceof ClassDefinition) {
578 2
                    if (is_string($expr->var->name)) {
579 2
                        if ($objectDefinition->hasProperty($expr->var->name)) {
580
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
581
                        }
582 2
                    }
583 2
                }
584 2
            }
585 2
        }
586
587 2
        $this->context->debug('Unknown how to pass symbol');
588 2
        return new CompiledExpression();
589
    }
590
591 29
    protected function compileVariableDeclaration(CompiledExpression $variableName, CompiledExpression $value)
592
    {
593 29
        switch ($variableName->getType()) {
594 29
            case CompiledExpression::STRING:
595 29
                break;
596
            default:
597
                $this->context->debug('Unexpected type of Variable name after compile');
598
                return new CompiledExpression();
599 29
        }
600
601 29
        $symbol = $this->context->getSymbol($variableName->getValue());
602 29
        if ($symbol) {
603 3
            $symbol->modify($value->getType(), $value->getValue());
604 3
            $this->context->modifyReferencedVariables(
605 3
                $symbol,
606 3
                $value->getType(),
607 3
                $value->getValue()
608 3
            );
609 3
        } else {
610 29
            $symbol = new Variable(
611 29
                $variableName->getValue(),
612 29
                $value->getValue(),
613 29
                $value->getType(),
614 29
                $this->context->getCurrentBranch()
615 29
            );
616 29
            $this->context->addVariable($symbol);
617
        }
618
619 29
        $symbol->incSets();
620 29
    }
621
622
    /**
623
     * @param Node\Expr\AssignRef $expr
624
     * @return CompiledExpression
625
     */
626 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
627
    {
628 1
        if ($expr->var instanceof Node\Expr\Variable) {
629 1
            $name = $expr->var->name;
630
631 1
            $compiledExpression = $this->compile($expr->expr);
632
633 1
            $symbol = $this->context->getSymbol($name);
634 1
            if ($symbol) {
635
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
636
            } else {
637 1
                $symbol = new Variable(
638 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 629 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
639 1
                    $compiledExpression->getValue(),
640 1
                    $compiledExpression->getType(),
641 1
                    $this->context->getCurrentBranch()
642 1
                );
643 1
                $this->context->addVariable($symbol);
644
            }
645
646 1
            if ($expr->expr instanceof Node\Expr\Variable) {
647 1
                $rightVarName = $expr->expr->name;
648
649 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
650 1
                if ($rightSymbol) {
651 1
                    $rightSymbol->incUse();
652 1
                    $symbol->setReferencedTo($rightSymbol);
653 1
                } else {
654
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
655
                }
656 1
            }
657
658 1
            $symbol->incSets();
659 1
            return $compiledExpression;
660
        }
661
662
        $this->context->debug('Unknown how to pass symbol by ref');
663
        return new CompiledExpression();
664
    }
665
666
    /**
667
     * @param Node\Expr\Variable $expr
668
     * @return CompiledExpression
669
     */
670 12
    protected function passExprVariable(Node\Expr\Variable $expr)
671
    {
672 12
        $variable = $this->context->getSymbol($expr->name);
673 12
        if ($variable) {
674 12
            $variable->incGets();
675 12
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
676
        }
677
678 1
        $this->context->notice(
679 1
            'undefined-variable',
680 1
            sprintf('You are trying to use an undefined variable $%s', $expr->name),
681
            $expr
682 1
        );
683
684 1
        return new CompiledExpression();
685
    }
686
687
    /**
688
     * Compile Array_ expression to CompiledExpression
689
     *
690
     * @param Node\Expr\Array_ $expr
691
     * @return CompiledExpression
692
     */
693 36
    protected function getArray(Node\Expr\Array_ $expr)
694
    {
695 36
        if ($expr->items === []) {
696 27
            return new CompiledExpression(CompiledExpression::ARR, []);
697
        }
698
699 11
        $resultArray = [];
700
701 11
        foreach ($expr->items as $item) {
702 11
            $compiledValueResult = $this->compile($item->value);
703 11
            if ($item->key) {
704 3
                $compiledKeyResult = $this->compile($item->key);
705 3
                switch ($compiledKeyResult->getType()) {
706 3
                    case CompiledExpression::INTEGER:
707 3
                    case CompiledExpression::DOUBLE:
708 3
                    case CompiledExpression::BOOLEAN:
709 3
                    case CompiledExpression::NULL:
710 3
                    case CompiledExpression::STRING:
711 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
712 3
                        break;
713 1
                    default:
714 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
715 1
                        return new CompiledExpression(CompiledExpression::ARR);
716 3
                }
717 3
            } else {
718 8
                $resultArray[] = $compiledValueResult->getValue();
719
            }
720 11
        }
721
722 11
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
723
    }
724
725
    /**
726
     * Convert const fetch expr to CompiledExpression
727
     *
728
     * @param Node\Expr\ConstFetch $expr
729
     * @return CompiledExpression
730
     */
731 7
    protected function constFetch(Node\Expr\ConstFetch $expr)
732
    {
733 7
        if ($expr->name instanceof Node\Name) {
734 7
            if ($expr->name->parts[0] === 'true') {
735 6
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
736
            }
737
738 3
            if ($expr->name->parts[0] === 'false') {
739 1
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
740
            }
741 2
        }
742
743
        /**
744
         * @todo Implement check
745
         */
746 2
        return $this->compile($expr->name);
747
    }
748
}
749