Completed
Push — master ( fba3d8...90421b )
by Дмитрий
04:23 queued 23s
created

Expression   F

Complexity

Total Complexity 143

Size/Duplication

Total Lines 692
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 72

Test Coverage

Coverage 55.76%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 692
ccs 237
cts 425
cp 0.5576
rs 1.0643
wmc 143
lcom 1
cbo 72

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D factory() 0 143 50
D compile() 0 84 23
C passProperty() 0 60 11
A declareVariable() 0 10 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 36 7
C passPropertyFetch() 0 41 7
B passConstFetch() 0 22 4
D passSymbol() 0 94 17
B passSymbolByRef() 0 39 5
A passExprVariable() 0 16 2
D getArray() 0 32 9
A constFetch() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like Expression often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Expression, and based on these observations, apply Extract Interface, too.

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