Completed
Push — master ( 88f6b9...c58af1 )
by Дмитрий
02:32
created

Expression::getArray()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.1582

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 32
ccs 21
cts 24
cp 0.875
rs 4.909
cc 9
eloc 23
nc 9
nop 1
crap 9.1582
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 143 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 'PhpParser\Node\Expr\MethodCall':
44
                return new Expression\MethodCall();
45 353
            case 'PhpParser\Node\Expr\FuncCall':
46
                return new Expression\FunctionCall();
47 353
            case 'PhpParser\Node\Expr\StaticCall':
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case 'PhpParser\Node\Expr\New_':
53
                return new Expression\Operators\NewOp();
54 353
            case 'PhpParser\Node\Expr\Instanceof_':
55
                return new Expression\Operators\InstanceOfOp();
56
            /**
57
             * Assign
58
             */
59 353
            case 'PhpParser\Node\Expr\AssignOp\Pow':
60
                return new Expression\AssignOp\Pow();
61
            /**
62
             * BinaryOp
63
             */
64 353
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
65 28
                return new Expression\BinaryOp\Identical();
66 325
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
67
                return new Expression\Operators\Contact();
68 325
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
69 14
                return new Expression\BinaryOp\NotIdentical();
70 311
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
71 34
                return new Expression\BinaryOp\Equal();
72 277
            case 'PhpParser\Node\Expr\BinaryOp\NotEqual':
73 17
                return new Expression\BinaryOp\NotEqual();
74
            /**
75
             * @link http://php.net/manual/en/language.operators.increment.php
76
             */
77 260
            case 'PhpParser\Node\Expr\PostInc':
78 4
                return new Expression\Operators\PostInc();
79 256
            case 'PhpParser\Node\Expr\PostDec':
80 4
                return new Expression\Operators\PostDec();
81
            /**
82
             * Arithmetical
83
             */
84 252
            case 'PhpParser\Node\Expr\BinaryOp\Div':
85 37
                return new Expression\Operators\Arithmetical\Div();
86 215
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
87 41
                return new Expression\Operators\Arithmetical\Plus();
88 174
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
89 18
                return new Expression\Operators\Arithmetical\Minus();
90 156
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
91 35
                return new Expression\Operators\Arithmetical\Mul();
92 121
            case 'PhpParser\Node\Expr\BinaryOp\Mod':
93 35
                return new Expression\Operators\Arithmetical\Mod();
94
            /**
95
             * Bitwise
96
             * @link http://php.net/manual/ru/language.operators.bitwise.php
97
             */
98 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
99
                return new Expression\Operators\Bitwise\BitwiseOr();
100 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
101
                return new Expression\Operators\Bitwise\BitwiseXor();
102 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
103
                return new Expression\Operators\Bitwise\BitwiseAnd();
104 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
105
                return new Expression\Operators\Bitwise\ShiftRight();
106 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
107
                return new Expression\Operators\Bitwise\ShiftLeft();
108 86
            case 'PhpParser\Node\Expr\BitwiseNot':
109
                return new Expression\Operators\Bitwise\BitwiseNot();
110
            /**
111
             * Logical
112
             */
113 86
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
114 10
                return new Expression\Operators\Logical\BooleanOr();
115 76
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
116 5
                return new Expression\Operators\Logical\BooleanAnd();
117 71
            case 'PhpParser\Node\Expr\BooleanNot':
118 5
                return new Expression\Operators\Logical\BooleanNot();
119
            /**
120
             * Comparison
121
             */
122 66
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
123 12
                return new Expression\Operators\Comparison\Greater();
124 54
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
125 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
126 42
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
127 12
                return new Expression\Operators\Comparison\Smaller();
128 30
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
129 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
130
            /**
131
             * Another
132
             */
133 18
            case 'PhpParser\Node\Expr\UnaryMinus':
134 9
                return new Expression\Operators\UnaryMinus();
135 9
            case 'PhpParser\Node\Expr\UnaryPlus':
136 9
                return new Expression\Operators\UnaryPlus();
137
        }
138
139
        return false;
140
    }
141
142
    /**
143
     * @param object|string $expr
144
     * @return CompiledExpression
145
     */
146 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 640 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...
147
    {
148 363
        if (is_string($expr)) {
149
            return new CompiledExpression(CompiledExpression::STRING, $expr);
150
        }
151
152 363
        if (is_null($expr)) {
153
            return new CompiledExpression(CompiledExpression::NULL);
154
        }
155
156 363
        if (!is_object($expr)) {
157
            throw new InvalidArgumentException('$expr must be string/object/null');
158
        }
159
160 363
        $className = get_class($expr);
161
        switch ($className) {
162 363
            case 'PhpParser\Node\Expr\PropertyFetch':
163
                return $this->passPropertyFetch($expr);
164 363
            case 'PhpParser\Node\Stmt\Property':
165
                return $this->passProperty($expr);
166 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
167
                return $this->passConstFetch($expr);
168 363
            case 'PhpParser\Node\Expr\Assign':
169
                return $this->passSymbol($expr);
170 363
            case 'PhpParser\Node\Expr\AssignRef':
171
                return $this->passSymbolByRef($expr);
172 363
            case 'PhpParser\Node\Expr\Variable':
173
                return $this->passExprVariable($expr);
174
            /**
175
             * Cast operators
176
             */
177 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
178
                return $this->passCastBoolean($expr);
179 363
            case 'PhpParser\Node\Expr\Cast\Int_':
180
                return $this->passCastInt($expr);
181 363
            case 'PhpParser\Node\Expr\Cast\Double':
182
                return $this->passCastFloat($expr);
183 363
            case 'PhpParser\Node\Expr\Cast\String_':
184
                return $this->passCastString($expr);
185 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
186
                return $this->passCastUnset($expr);
187
            /**
188
             * Expressions
189
             */
190 363
            case 'PhpParser\Node\Expr\Array_':
191 11
                return $this->getArray($expr);
192 362
            case 'PhpParser\Node\Expr\ConstFetch':
193
                return $this->constFetch($expr);
194 362
            case 'PhpParser\Node\Name':
195
                return $this->getNodeName($expr);
196
            /**
197
             * Simple Scalar(s)
198
             */
199 362
            case 'PHPSA\Node\Scalar\Nil':
200 8
                return new CompiledExpression(CompiledExpression::NULL);
201 361
            case 'PhpParser\Node\Scalar\LNumber':
202 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
203 358
            case 'PhpParser\Node\Scalar\DNumber':
204 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
205 357
            case 'PhpParser\Node\Scalar\String_':
206 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
207 355
            case 'PHPSA\Node\Scalar\Boolean':
208 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
209 353
            case 'PHPSA\Node\Scalar\Fake':
210 29
                return new CompiledExpression($expr->type, $expr->value);
211
        }
212
213 353
        $expressionCompiler = $this->factory($expr);
214 353
        if (!$expressionCompiler) {
215
            $this->context->debug("Expression compiler is not implemented for {$className}");
216
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
217
        }
218
219 353
        $result = $expressionCompiler->pass($expr, $this->context);
220 353
        if (!$result instanceof CompiledExpression) {
221
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
222
        }
223
224 353
        return $result;
225
    }
226
227
    /**
228
     * @todo Implement
229
     *
230
     * @param Node\Stmt\Property $st
231
     * @return CompiledExpression
232
     */
233
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Unused Code introduced by
The parameter $st is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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