Completed
Pull Request — master (#115)
by Enrico
03:39
created

Expression::declareVariable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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