Completed
Push — master ( efa69f...da576a )
by Дмитрий
02:46
created

Expression::passProperty()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 2
b 0
f 0
nc 5
nop 1
dl 0
loc 24
ccs 0
cts 17
cp 0
crap 30
rs 8.5125
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use InvalidArgumentException;
9
use PHPSA\Check;
10
use PHPSA\CompiledExpression;
11
use PHPSA\Context;
12
use PhpParser\Node;
13
use PHPSA\Definition\ClassDefinition;
14
use PHPSA\Exception\RuntimeException;
15
use PHPSA\Variable;
16
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
17
18
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 156 which exceeds the configured maximum of 50.

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

Some resources for further reading:

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

Loading history...
19
{
20
    /**
21
     * @var Context
22
     */
23
    protected $context;
24
25
    /**
26
     * @param Context $context
27
     */
28 363
    public function __construct(Context $context)
29
    {
30 363
        $this->context = $context;
31 363
    }
32
33
    /**
34
     * @param $expr
35
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
36
     */
37 353
    protected function factory($expr)
38
    {
39 353
        switch (get_class($expr)) {
40
            /**
41
             * Call(s)
42
             */
43 353
            case Node\Expr\MethodCall::class:
44
                return new Expression\MethodCall();
45 353
            case Node\Expr\FuncCall::class:
46
                return new Expression\FunctionCall();
47 353
            case Node\Expr\StaticCall::class:
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case Node\Expr\New_::class:
53
                return new Expression\Operators\NewOp();
54 353
            case Node\Expr\Instanceof_::class:
55
                return new Expression\Operators\InstanceOfOp();
56
            /**
57
             * Assign
58
             */
59 353
            case Node\Expr\AssignOp\Pow::class:
60
                return new Expression\AssignOp\Pow();
61 353
            case Node\Expr\AssignOp\Plus::class:
62
                return new Expression\AssignOp\Plus();
63 353
            case Node\Expr\AssignOp\Minus::class:
64
                return new Expression\AssignOp\Minus();
65 353
            case Node\Expr\AssignOp\Mod::class:
66
                return new Expression\AssignOp\Mod();
67 353
            case Node\Expr\AssignOp\BitwiseOr::class:
68
                return new Expression\AssignOp\BitwiseOr();
69 353
            case Node\Expr\AssignOp\BitwiseAnd::class:
70
                return new Expression\AssignOp\BitwiseAnd();
71
            /**
72
             * BinaryOp
73
             */
74 353
            case Node\Expr\BinaryOp\Identical::class:
75 28
                return new Expression\BinaryOp\Identical();
76 325
            case Node\Expr\BinaryOp\Concat::class:
77
                return new Expression\Operators\Contact();
78 325
            case Node\Expr\BinaryOp\NotIdentical::class:
79 14
                return new Expression\BinaryOp\NotIdentical();
80 311
            case Node\Expr\BinaryOp\Equal::class:
81 34
                return new Expression\BinaryOp\Equal();
82 277
            case Node\Expr\BinaryOp\NotEqual::class:
83 17
                return new Expression\BinaryOp\NotEqual();
84
            /**
85
             * @link http://php.net/manual/en/language.operators.increment.php
86
             */
87 260
            case Node\Expr\PostInc::class:
88 4
                return new Expression\Operators\PostInc();
89 256
            case Node\Expr\PostDec::class:
90 4
                return new Expression\Operators\PostDec();
91
            /**
92
             * Arithmetical
93
             */
94 252
            case Node\Expr\BinaryOp\Div::class:
95 37
                return new Expression\Operators\Arithmetical\Div();
96 215
            case Node\Expr\BinaryOp\Plus::class:
97 41
                return new Expression\Operators\Arithmetical\Plus();
98 174
            case Node\Expr\BinaryOp\Minus::class:
99 18
                return new Expression\Operators\Arithmetical\Minus();
100 156
            case Node\Expr\BinaryOp\Mul::class:
101 35
                return new Expression\Operators\Arithmetical\Mul();
102 121
            case Node\Expr\BinaryOp\Mod::class:
103 35
                return new Expression\Operators\Arithmetical\Mod();
104
            /**
105
             * Bitwise
106
             * @link http://php.net/manual/ru/language.operators.bitwise.php
107
             */
108 86
            case Node\Expr\BinaryOp\BitwiseOr::class:
109
                return new Expression\Operators\Bitwise\BitwiseOr();
110 86
            case Node\Expr\BinaryOp\BitwiseXor::class:
111
                return new Expression\Operators\Bitwise\BitwiseXor();
112 86
            case Node\Expr\BinaryOp\BitwiseAnd::class:
113
                return new Expression\Operators\Bitwise\BitwiseAnd();
114 86
            case Node\Expr\BinaryOp\ShiftRight::class:
115
                return new Expression\Operators\Bitwise\ShiftRight();
116 86
            case Node\Expr\BinaryOp\ShiftLeft::class:
117
                return new Expression\Operators\Bitwise\ShiftLeft();
118 86
            case Node\Expr\BitwiseNot::class:
119
                return new Expression\Operators\Bitwise\BitwiseNot();
120
            /**
121
             * Logical
122
             */
123 86
            case Node\Expr\BinaryOp\BooleanOr::class:
124 10
                return new Expression\Operators\Logical\BooleanOr();
125 76
            case Node\Expr\BinaryOp\BooleanAnd::class:
126 5
                return new Expression\Operators\Logical\BooleanAnd();
127 71
            case Node\Expr\BooleanNot::class:
128 5
                return new Expression\Operators\Logical\BooleanNot();
129
            /**
130
             * Comparison
131
             */
132 66
            case Node\Expr\BinaryOp\Greater::class:
133 12
                return new Expression\Operators\Comparison\Greater();
134 54
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
135 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
136 42
            case Node\Expr\BinaryOp\Smaller::class:
137 12
                return new Expression\Operators\Comparison\Smaller();
138 30
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
139 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
140
            /**
141
             * Another
142
             */
143 18
            case Node\Expr\UnaryMinus::class:
144 9
                return new Expression\Operators\UnaryMinus();
145 9
            case Node\Expr\UnaryPlus::class:
146 9
                return new Expression\Operators\UnaryPlus();
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * @param object|string $expr
154
     * @return CompiledExpression
155
     */
156 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 672 execution paths which exceeds the configured maximum of 200.

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

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

Loading history...
157
    {
158 363
        if (is_string($expr)) {
159
            return new CompiledExpression(CompiledExpression::STRING, $expr);
160
        }
161
162 363
        if (is_null($expr)) {
163
            return new CompiledExpression(CompiledExpression::NULL);
164
        }
165
166 363
        if (!is_object($expr)) {
167
            throw new InvalidArgumentException('$expr must be string/object/null');
168
        }
169
170 363
        $className = get_class($expr);
171
        switch ($className) {
172 363
            case 'PhpParser\Node\Expr\PropertyFetch':
173
                return $this->passPropertyFetch($expr);
174 363
            case 'PhpParser\Node\Stmt\Property':
175
                return $this->passProperty($expr);
176 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
177
                return $this->passConstFetch($expr);
178 363
            case 'PhpParser\Node\Expr\Assign':
179
                return $this->passSymbol($expr);
180 363
            case 'PhpParser\Node\Expr\AssignRef':
181
                return $this->passSymbolByRef($expr);
182 363
            case 'PhpParser\Node\Expr\Variable':
183
                return $this->passExprVariable($expr);
184
            /**
185
             * Cast operators
186
             */
187 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
188
                return $this->passCastBoolean($expr);
189 363
            case 'PhpParser\Node\Expr\Cast\Int_':
190
                return $this->passCastInt($expr);
191 363
            case 'PhpParser\Node\Expr\Cast\Double':
192
                return $this->passCastFloat($expr);
193 363
            case 'PhpParser\Node\Expr\Cast\String_':
194
                return $this->passCastString($expr);
195 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
196
                return $this->passCastUnset($expr);
197
            /**
198
             * Expressions
199
             */
200 363
            case 'PhpParser\Node\Expr\Array_':
201 11
                return $this->getArray($expr);
202 362
            case 'PhpParser\Node\Expr\ConstFetch':
203
                return $this->constFetch($expr);
204 362
            case 'PhpParser\Node\Name':
205
                return $this->getNodeName($expr);
206 362
            case 'PhpParser\Node\Name\FullyQualified':
207
                return $this->getFullyQualifiedNodeName($expr);
208
            /**
209
             * Simple Scalar(s)
210
             */
211 362
            case 'PHPSA\Node\Scalar\Nil':
212 8
                return new CompiledExpression(CompiledExpression::NULL);
213 361
            case 'PhpParser\Node\Scalar\LNumber':
214 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
215 358
            case 'PhpParser\Node\Scalar\DNumber':
216 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
217 357
            case 'PhpParser\Node\Scalar\String_':
218 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
219 355
            case 'PHPSA\Node\Scalar\Boolean':
220 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
221 353
            case 'PHPSA\Node\Scalar\Fake':
222 29
                return new CompiledExpression($expr->type, $expr->value);
223
        }
224
225 353
        $expressionCompiler = $this->factory($expr);
226 353
        if (!$expressionCompiler) {
227
            $this->context->debug("Expression compiler is not implemented for {$className}");
228
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
229
        }
230
231 353
        $result = $expressionCompiler->pass($expr, $this->context);
232 353
        if (!$result instanceof CompiledExpression) {
233
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
234
        }
235
236 353
        return $result;
237
    }
238
239
    /**
240
     * @todo Implement
241
     *
242
     * @param Node\Stmt\Property $st
243
     * @return CompiledExpression
244
     */
245
    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...
246
    {
247
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($st->getDocComment()->getText());
248
249
        $varTags = $phpdoc->getTagsByName('var');
250
        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...
251
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
252
            $varTag = current($varTags);
253
254
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
255
            $type = $typeResolver->resolve($varTag->getType());
256
257
            if ($type) {
258
                switch (get_class($type)) {
259
                    case \phpDocumentor\Reflection\Types\Object_::class:
260
                        return new CompiledExpression(CompiledExpression::OBJECT);
261
                    case \phpDocumentor\Reflection\Types\Integer::class:
262
                        return new CompiledExpression(CompiledExpression::INTEGER);
263
                }
264
            }
265
        }
266
267
        return new CompiledExpression();
268
    }
269
270
    /**
271
     * @param Node\Expr\Variable $expr
272
     * @return CompiledExpression
273
     */
274
    public function declareVariable(Node\Expr\Variable $expr)
275
    {
276
        $variable = $this->context->getSymbol($expr->name);
277
        if ($variable) {
278
            $variable->incGets();
279
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
280
        }
281
282
        $symbol = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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