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

Expression::factory()   D

Complexity

Conditions 65
Paths 65

Size

Total Lines 176
Code Lines 131

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 128
CRAP Score 65.1175

Importance

Changes 0
Metric Value
cc 65
eloc 131
nc 65
nop 1
dl 0
loc 176
ccs 128
cts 132
cp 0.9697
crap 65.1175
rs 4.1818
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use InvalidArgumentException;
9
use PHPSA\Check;
10
use PHPSA\CompiledExpression;
11
use PHPSA\Compiler\Event\ExpressionBeforeCompile;
12
use PHPSA\Context;
13
use PhpParser\Node;
14
use PHPSA\Definition\ClassDefinition;
15
use PHPSA\Exception\RuntimeException;
16
use PHPSA\Variable;
17
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
18
use Webiny\Component\EventManager\EventManager;
19
20
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 157 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 774
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 774
        $this->context = $context;
38 774
        $this->eventManager = $eventManager;
39 774
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 756
    protected function factory($expr)
46
    {
47 756
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 756
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 756
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 748
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 748
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 746
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 746
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 725
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 708
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 688
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 668
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 650
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 639
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 627
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 615
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 603
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 589
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 577
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 565
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 537
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 536
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 522
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 488
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105 471
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 460
            case Node\Expr\BinaryOp\Coalesce::class:
108 3
                return new Expression\BinaryOp\Coalesce();
109
                
110
            /**
111
             * @link http://php.net/manual/en/language.operators.increment.php
112
             */
113 457
            case Node\Expr\PostInc::class:
114 8
                return new Expression\Operators\PostInc();
115 449
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 441
            case Node\Expr\PreInc::class:
118 8
                return new Expression\Operators\PreInc();
119 433
            case Node\Expr\PreDec::class:
120 8
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 425
            case Node\Expr\BinaryOp\Div::class:
125 37
                return new Expression\Operators\Arithmetical\Div();
126 388
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 343
            case Node\Expr\BinaryOp\Minus::class:
129 18
                return new Expression\Operators\Arithmetical\Minus();
130 325
            case Node\Expr\BinaryOp\Mul::class:
131 35
                return new Expression\Operators\Arithmetical\Mul();
132 290
            case Node\Expr\BinaryOp\Mod::class:
133 35
                return new Expression\Operators\Arithmetical\Mod();
134 255
            case Node\Expr\BinaryOp\Pow::class:
135 21
                return new Expression\Operators\Arithmetical\Pow();
136
137
            /**
138
             * Bitwise
139
             * @link http://php.net/manual/ru/language.operators.bitwise.php
140
             */
141 234
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 222
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 210
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 198
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 186
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 174
            case Node\Expr\BitwiseNot::class:
152 5
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 169
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 152
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 15
                return new Expression\Operators\Logical\BooleanAnd();
160 137
            case Node\Expr\BooleanNot::class:
161 9
                return new Expression\Operators\Logical\BooleanNot();
162 128
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 113
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 96
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 79
            case Node\Expr\BinaryOp\Greater::class:
173 12
                return new Expression\Operators\Comparison\Greater();
174 67
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 55
            case Node\Expr\BinaryOp\Smaller::class:
177 12
                return new Expression\Operators\Comparison\Smaller();
178 43
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 31
            case Node\Expr\Cast\Array_::class:
185 1
                return new Expression\Casts\ArrayCast();
186 31
            case Node\Expr\Cast\Bool_::class:
187 1
                return new Expression\Casts\BoolCast();
188 31
            case Node\Expr\Cast\Int_::class:
189 1
                return new Expression\Casts\IntCast();
190 31
            case Node\Expr\Cast\Double::class:
191 1
                return new Expression\Casts\DoubleCast();
192 31
            case Node\Expr\Cast\Object_::class:
193 1
                return new Expression\Casts\ObjectCast();
194 31
            case Node\Expr\Cast\String_::class:
195 1
                return new Expression\Casts\StringCast();
196 31
            case Node\Expr\Cast\Unset_::class:
197 1
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 30
            case Node\Expr\Closure::class:
204
                return new Expression\Closure();
205 30
            case Node\Expr\UnaryMinus::class:
206 9
                return new Expression\Operators\UnaryMinus();
207 21
            case Node\Expr\UnaryPlus::class:
208 9
                return new Expression\Operators\UnaryPlus();
209 12
            case Node\Expr\Exit_::class:
210 1
                return new Expression\ExitOp();
211 11
            case Node\Expr\Isset_::class:
212 3
                return new Expression\IssetOp();
213 8
            case Node\Expr\Print_::class:
214 1
                return new Expression\PrintOp();
215 7
            case Node\Expr\Empty_::class:
216 5
                return new Expression\EmptyOp();
217 2
        }
218
219 2
        return false;
220
    }
221
222
    /**
223
     * @param object|string $expr
224
     * @return CompiledExpression
225
     */
226 774
    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...
227
    {
228 774
        if (is_string($expr)) {
229 13
            return new CompiledExpression(CompiledExpression::STRING, $expr);
230
        }
231
232 774
        if (is_null($expr)) {
233 7
            return new CompiledExpression(CompiledExpression::NULL);
234
        }
235
236 774
        if (!is_object($expr)) {
237
            throw new InvalidArgumentException('$expr must be string/object/null');
238
        }
239
240 774
        $this->eventManager->fire(
241 774
            ExpressionBeforeCompile::EVENT_NAME,
242 774
            new ExpressionBeforeCompile(
243 774
                $expr,
244 774
                $this->context
245 774
            )
246 774
        );
247
248 774
        $className = get_class($expr);
249
        switch ($className) {
250 774
            case Node\Arg::class:
251
                /**
252
                 * @todo Better compile
253
                 */
254 2
                return $this->compile($expr->value);
255 774
            case Node\Expr\PropertyFetch::class:
256
                return $this->passPropertyFetch($expr);
257 774
            case Node\Stmt\Property::class:
258
                return $this->passProperty($expr);
259 774
            case Node\Expr\ClassConstFetch::class:
260
                return $this->passConstFetch($expr);
261 774
            case Node\Expr\Assign::class:
262 13
                return $this->passSymbol($expr);
263 774
            case Node\Expr\AssignRef::class:
264 1
                return $this->passSymbolByRef($expr);
265 774
            case Node\Expr\Variable::class:
266 10
                return $this->passExprVariable($expr);
267
268
            /**
269
             * Expressions
270
             */
271 774
            case Node\Expr\Array_::class:
272 31
                return $this->getArray($expr);
273 773
            case Node\Expr\ConstFetch::class:
274 6
                return $this->constFetch($expr);
275 773
            case Node\Name::class:
276 11
                return $this->getNodeName($expr);
277 773
            case Node\Name\FullyQualified::class:
278
                return $this->getFullyQualifiedNodeName($expr);
279
280
            /**
281
             * Simple Scalar(s)
282
             */
283 773
            case \PHPSA\Node\Scalar\Nil::class:
284 25
                return new CompiledExpression(CompiledExpression::NULL);
285 772
            case Node\Scalar\LNumber::class:
286 475
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
287 764
            case Node\Scalar\DNumber::class:
288 200
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
289 763
            case Node\Scalar\String_::class:
290 38
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
291 758
            case \PHPSA\Node\Scalar\Boolean::class:
292 229
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
293 756
            case \PHPSA\Node\Scalar\Fake::class:
294 71
                return new CompiledExpression($expr->type, $expr->value);
295
        }
296
297 756
        $expressionCompiler = $this->factory($expr);
298 756
        if (!$expressionCompiler) {
299 2
            $this->context->debug("Expression compiler is not implemented for {$className}");
300 2
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
301
        }
302
303 755
        $result = $expressionCompiler->pass($expr, $this->context);
304 755
        if (!$result instanceof CompiledExpression) {
305
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
306
        }
307
308 755
        return $result;
309
    }
310
311
    /**
312
     * @todo Implement
313
     *
314
     * @param Node\Stmt\Property $st
315
     * @return CompiledExpression
316
     */
317
    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...
318
    {
319
        $docBlock = $st->getDocComment();
320
        if (!$docBlock) {
321
            $this->context->notice(
322
                'missing-docblock',
323
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
324
                $st
325
            );
326
327
            return new CompiledExpression();
328
        }
329
330
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
331
332
        $varTags = $phpdoc->getTagsByName('var');
333
        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...
334
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
335
            $varTag = current($varTags);
336
337
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
338
339
            try {
340
                $type = $typeResolver->resolve($varTag->getType());
341
            } catch (\InvalidArgumentException $e) {
342
                return new CompiledExpression();
343
            }
344
345
            if ($type) {
346
                switch (get_class($type)) {
347
                    case \phpDocumentor\Reflection\Types\Object_::class:
348
                        return new CompiledExpression(
349
                            CompiledExpression::OBJECT
350
                        );
351
                    case \phpDocumentor\Reflection\Types\Integer::class:
352
                        return new CompiledExpression(
353
                            CompiledExpression::INTEGER
354
                        );
355
                    case \phpDocumentor\Reflection\Types\String_::class:
356
                        return new CompiledExpression(
357
                            CompiledExpression::STRING
358
                        );
359
                    case \phpDocumentor\Reflection\Types\Float_::class:
360
                        return new CompiledExpression(
361
                            CompiledExpression::DOUBLE
362
                        );
363
                    case \phpDocumentor\Reflection\Types\Null_::class:
364
                        return new CompiledExpression(
365
                            CompiledExpression::NULL
366
                        );
367
                    case \phpDocumentor\Reflection\Types\Boolean::class:
368
                        return new CompiledExpression(
369
                            CompiledExpression::BOOLEAN
370
                        );
371
                }
372
            }
373
        }
374
375
        return new CompiledExpression();
376
    }
377
378
    /**
379
     * @param Node\Expr\Variable $expr
380
     * @return CompiledExpression
381
     */
382 1
    public function declareVariable(Node\Expr\Variable $expr)
383
    {
384 1
        $variable = $this->context->getSymbol($expr->name);
385 1
        if (!$variable) {
386 1
            $variable = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
387 1
            $this->context->addVariable($variable);
388 1
        }
389
390 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
391
    }
392
393
    /**
394
     * @param Node\Name\FullyQualified $expr
395
     * @return CompiledExpression
396
     */
397
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
398
    {
399
        $this->context->debug('Unimplemented FullyQualified', $expr);
400
401
        return new CompiledExpression;
402
    }
403
404
    /**
405
     * @param Node\Name $expr
406
     * @return CompiledExpression
407
     */
408 11
    public function getNodeName(Node\Name $expr)
409
    {
410 11
        $nodeString = $expr->toString();
411 11
        if ($nodeString === 'null') {
412 1
            return new CompiledExpression(CompiledExpression::NULL);
413
        }
414
415 10
        if (in_array($nodeString, ['parent'], true)) {
416
            /** @var ClassDefinition $scope */
417
            $scope = $this->context->scope;
418
            assert($scope instanceof ClassDefinition);
419
420
            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...
421
                $definition = $scope->getExtendsClassDefinition();
422
                if ($definition) {
423
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
424
                }
425
            } else {
426
                $this->context->notice(
427
                    'no-parent',
428
                    'Cannot access parent:: when current class scope has no parent',
429
                    $expr
430
                );
431
            }
432
        }
433
434 10
        if (in_array($nodeString, ['self', 'static'], true)) {
435
            return CompiledExpression::fromZvalValue($this->context->scope);
436
        }
437
438 10
        if (defined($nodeString)) {
439 1
            return CompiledExpression::fromZvalValue(constant($expr));
440
        }
441
442 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
443
    }
444
445
    /**
446
     * @param Node\Expr\PropertyFetch $expr
447
     * @return CompiledExpression
448
     */
449
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
450
    {
451
        $propertNameCE = $this->compile($expr->name);
452
453
        $scopeExpression = $this->compile($expr->var);
454
        if ($scopeExpression->isObject()) {
455
            $scopeExpressionValue = $scopeExpression->getValue();
456
            if ($scopeExpressionValue instanceof ClassDefinition) {
457
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
458
                if ($propertyName) {
459
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
460
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
461
                        return $this->compile($property);
462
                    } else {
463
                        $this->context->notice(
464
                            'undefined-property',
465
                            sprintf(
466
                                'Property %s does not exist in %s scope',
467
                                $propertyName,
468
                                $scopeExpressionValue->getName()
469
                            ),
470
                            $expr
471
                        );
472
                    }
473
                }
474
            }
475
476
            return new CompiledExpression(CompiledExpression::UNKNOWN);
477
        } elseif (!$scopeExpression->canBeObject()) {
478
            return new CompiledExpression(CompiledExpression::UNKNOWN);
479
        }
480
481
        $this->context->notice(
482
            'property-fetch-on-non-object',
483
            "It's not possible to fetch property on not object",
484
            $expr,
485
            Check::CHECK_BETA
486
        );
487
488
        return new CompiledExpression(CompiledExpression::UNKNOWN);
489
    }
490
491
    /**
492
     * @param Node\Expr\ClassConstFetch $expr
493
     * @return CompiledExpression
494
     */
495
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
496
    {
497
        $leftCE = $this->compile($expr->class);
498
        if ($leftCE->isObject()) {
499
            $leftCEValue = $leftCE->getValue();
500
            if ($leftCEValue instanceof ClassDefinition) {
501
                if (!$leftCEValue->hasConst($expr->name, true)) {
502
                    $this->context->notice(
503
                        'undefined-const',
504
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
505
                        $expr
506
                    );
507
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
508
                }
509
510
                return new CompiledExpression();
511
            }
512
        }
513
514
        $this->context->debug('Unknown const fetch', $expr);
515
        return new CompiledExpression();
516
    }
517
518
    /**
519
     * @param Node\Expr\Assign $expr
520
     * @return CompiledExpression
521
     */
522 13
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 240 execution paths which exceeds the configured maximum of 200.

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

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

Loading history...
523
    {
524 13
        $compiledExpression = $this->compile($expr->expr);
525
526 13
        if ($expr->var instanceof Node\Expr\List_) {
527
            $isCorrectType = false;
528
529
            switch ($compiledExpression->getType()) {
530
                case CompiledExpression::ARR:
531
                    $isCorrectType = true;
532
                    break;
533
            }
534
535
            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...
536
                foreach ($expr->var->vars as $key => $var) {
537
                    if ($var instanceof Node\Expr\Variable) {
538
                        $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...
539
540
                        $symbol = $this->context->getSymbol($name);
541
                        if (!$symbol) {
542
                            $symbol = new Variable(
543
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 538 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...
544
                                null,
545
                                CompiledExpression::UNKNOWN,
546
                                $this->context->getCurrentBranch()
547
                            );
548
                            $this->context->addVariable($symbol);
549
                        }
550
551
                        if (!$isCorrectType) {
552
                            $symbol->modify(CompiledExpression::NULL, null);
553
                        }
554
555
                        $symbol->incSets();
556
                    }
557
                }
558
            }
559
560
            return new CompiledExpression();
561
        }
562
563
564 13
        if ($expr->var instanceof Node\Expr\Variable) {
565 13
            $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...
566 13
            switch ($compiledExpressionName->getType()) {
567 13
                case CompiledExpression::STRING:
568 13
                    break;
569
                default:
570
                    $this->context->debug('Unexpected type of Variable name after compile');
571
                    return new CompiledExpression();
572 13
            }
573
574 13
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
575 13
            if ($symbol) {
576 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
577 2
                $this->context->modifyReferencedVariables(
578 2
                    $symbol,
579 2
                    $compiledExpression->getType(),
580 2
                    $compiledExpression->getValue()
581 2
                );
582 2
            } else {
583 13
                $symbol = new Variable(
584 13
                    $compiledExpressionName->getValue(),
585 13
                    $compiledExpression->getValue(),
586 13
                    $compiledExpression->getType(),
587 13
                    $this->context->getCurrentBranch()
588 13
                );
589 13
                $this->context->addVariable($symbol);
590
            }
591
592 13
            $symbol->incSets();
593 13
            return $compiledExpression;
594
        }
595
596 1
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
597 1
            $compiledExpression = $this->compile($expr->var->var);
598 1
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
599 1
                $objectDefinition = $compiledExpression->getValue();
600 1
                if ($objectDefinition instanceof ClassDefinition) {
601 1
                    if (is_string($expr->var->name)) {
602 1
                        if ($objectDefinition->hasProperty($expr->var->name)) {
603
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
604
                        }
605 1
                    }
606 1
                }
607 1
            }
608 1
        }
609
610 1
        $this->context->debug('Unknown how to pass symbol');
611 1
        return new CompiledExpression();
612
    }
613
614
    /**
615
     * @param Node\Expr\AssignRef $expr
616
     * @return CompiledExpression
617
     */
618 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
619
    {
620 1
        if ($expr->var instanceof Node\Expr\Variable) {
621 1
            $name = $expr->var->name;
622
623 1
            $compiledExpression = $this->compile($expr->expr);
624
625 1
            $symbol = $this->context->getSymbol($name);
626 1
            if ($symbol) {
627
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
628
            } else {
629 1
                $symbol = new Variable(
630 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 621 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...
631 1
                    $compiledExpression->getValue(),
632 1
                    $compiledExpression->getType(),
633 1
                    $this->context->getCurrentBranch()
634 1
                );
635 1
                $this->context->addVariable($symbol);
636
            }
637
638 1
            if ($expr->expr instanceof Node\Expr\Variable) {
639 1
                $rightVarName = $expr->expr->name;
640
641 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
642 1
                if ($rightSymbol) {
643 1
                    $rightSymbol->incUse();
644 1
                    $symbol->setReferencedTo($rightSymbol);
645 1
                } else {
646
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
647
                }
648 1
            }
649
650 1
            $symbol->incSets();
651 1
            return $compiledExpression;
652
        }
653
654
        $this->context->debug('Unknown how to pass symbol by ref');
655
        return new CompiledExpression();
656
    }
657
658
    /**
659
     * @param Node\Expr\Variable $expr
660
     * @return CompiledExpression
661
     */
662 10
    protected function passExprVariable(Node\Expr\Variable $expr)
663
    {
664 10
        $variable = $this->context->getSymbol($expr->name);
665 10
        if ($variable) {
666 10
            $variable->incGets();
667 10
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
668
        }
669
670 1
        $this->context->notice(
671 1
            'undefined-variable',
672 1
            sprintf('You trying to use undefined variable $%s', $expr->name),
673
            $expr
674 1
        );
675
676 1
        return new CompiledExpression();
677
    }
678
679
    /**
680
     * Compile Array_ expression to CompiledExpression
681
     *
682
     * @param Node\Expr\Array_ $expr
683
     * @return CompiledExpression
684
     */
685 31
    protected function getArray(Node\Expr\Array_ $expr)
686
    {
687 31
        if ($expr->items === []) {
688 22
            return new CompiledExpression(CompiledExpression::ARR, []);
689
        }
690
691 10
        $resultArray = [];
692
693 10
        foreach ($expr->items as $item) {
694 10
            $compiledValueResult = $this->compile($item->value);
695 10
            if ($item->key) {
696 3
                $compiledKeyResult = $this->compile($item->key);
697 3
                switch ($compiledKeyResult->getType()) {
698 3
                    case CompiledExpression::INTEGER:
699 3
                    case CompiledExpression::DOUBLE:
700 3
                    case CompiledExpression::BOOLEAN:
701 3
                    case CompiledExpression::NULL:
702 3
                    case CompiledExpression::STRING:
703 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
704 3
                        break;
705 1
                    default:
706 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
707 1
                        return new CompiledExpression(CompiledExpression::ARR);
708
                        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...
709 3
                }
710 3
            } else {
711 7
                $resultArray[] = $compiledValueResult->getValue();
712
            }
713 10
        }
714
715 10
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
716
    }
717
718
    /**
719
     * Convert const fetch expr to CompiledExpression
720
     *
721
     * @param Node\Expr\ConstFetch $expr
722
     * @return CompiledExpression
723
     */
724 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
725
    {
726 6
        if ($expr->name instanceof Node\Name) {
727 6
            if ($expr->name->parts[0] === 'true') {
728 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
729
            }
730
731 2
            if ($expr->name->parts[0] === 'false') {
732
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
733
            }
734 2
        }
735
736
        /**
737
         * @todo Implement check
738
         */
739 2
        return $this->compile($expr->name);
740
    }
741
}
742