Completed
Push — master ( d59936...3437ad )
by Дмитрий
03:43
created

Expression::passSymbol()   C

Complexity

Conditions 15
Paths 12

Size

Total Lines 79
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 79
ccs 0
cts 61
cp 0
rs 5.0981
cc 15
eloc 49
nc 12
nop 1
crap 240

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use PHPSA\CompiledExpression;
9
use PHPSA\Context;
10
use PhpParser\Node;
11
use PHPSA\Definition\ClassDefinition;
12
use PHPSA\Exception\RuntimeException;
13
use PHPSA\Variable;
14
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
15
16
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 132 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...
17
{
18
    /**
19
     * @var Context
20
     */
21
    protected $context;
22
23
    /**
24
     * @param Context $context
25
     */
26 363
    public function __construct(Context $context)
27
    {
28 363
        $this->context = $context;
29 363
    }
30
31
    /**
32
     * @param $expr
33
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
34
     */
35 353
    protected function factory($expr)
36
    {
37 353
        switch (get_class($expr)) {
38
            /**
39
             * Call(s)
40
             */
41 353
            case 'PhpParser\Node\Expr\MethodCall':
42
                return new Expression\MethodCall();
43 353
            case 'PhpParser\Node\Expr\FuncCall':
44
                return new Expression\FunctionCall();
45 353
            case 'PhpParser\Node\Expr\StaticCall':
46
                return new Expression\StaticCall();
47
            /**
48
             * Operators
49
             */
50 353
            case 'PhpParser\Node\Expr\New_':
51
                return new Expression\Operators\NewOp();
52 353
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
53 28
                return new Expression\BinaryOp\Identical();
54 325
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
55
                return new Expression\Operators\Contact();
56 325
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
57 14
                return new Expression\BinaryOp\NotIdentical();
58 311
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
59 34
                return new Expression\BinaryOp\Equal();
60 277
            case 'PhpParser\Node\Expr\BinaryOp\NotEqual':
61 17
                return new Expression\BinaryOp\NotEqual();
62
            /**
63
             * @link http://php.net/manual/en/language.operators.increment.php
64
             */
65 260
            case 'PhpParser\Node\Expr\PostInc':
66 4
                return new Expression\Operators\PostInc();
67 256
            case 'PhpParser\Node\Expr\PostDec':
68 4
                return new Expression\Operators\PostDec();
69
            /**
70
             * Arithmetical
71
             */
72 252
            case 'PhpParser\Node\Expr\BinaryOp\Div':
73 37
                return new Expression\Operators\Arithmetical\Div();
74 215
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
75 41
                return new Expression\Operators\Arithmetical\Plus();
76 174
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
77 18
                return new Expression\Operators\Arithmetical\Minus();
78 156
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
79 35
                return new Expression\Operators\Arithmetical\Mul();
80 121
            case 'PhpParser\Node\Expr\BinaryOp\Mod':
81 35
                return new Expression\Operators\Arithmetical\Mod();
82
            /**
83
             * Bitwise
84
             * @link http://php.net/manual/ru/language.operators.bitwise.php
85
             */
86 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
87
                return new Expression\Operators\Bitwise\BitwiseOr();
88 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
89
                return new Expression\Operators\Bitwise\BitwiseXor();
90 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
91
                return new Expression\Operators\Bitwise\BitwiseAnd();
92 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
93
                return new Expression\Operators\Bitwise\ShiftRight();
94 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
95
                return new Expression\Operators\Bitwise\ShiftLeft();
96 86
            case 'PhpParser\Node\Expr\BitwiseNot':
97
                return new Expression\Operators\Bitwise\BitwiseNot();
98
            /**
99
             * Logical
100
             */
101 86
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
102 10
                return new Expression\Operators\Logical\BooleanOr();
103 76
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
104 5
                return new Expression\Operators\Logical\BooleanAnd();
105 71
            case 'PhpParser\Node\Expr\BooleanNot':
106 5
                return new Expression\Operators\Logical\BooleanNot();
107
            /**
108
             * Comparison
109
             */
110 66
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
111 12
                return new Expression\Operators\Comparison\Greater();
112 54
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
113 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
114 42
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
115 12
                return new Expression\Operators\Comparison\Smaller();
116 30
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
117 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
118
            /**
119
             * Another
120
             */
121 18
            case 'PhpParser\Node\Expr\UnaryMinus':
122 9
                return new Expression\Operators\UnaryMinus();
123 9
            case 'PhpParser\Node\Expr\UnaryPlus':
124 9
                return new Expression\Operators\UnaryPlus();
125
        }
126
127
        return false;
128
    }
129
130
    /**
131
     * @param $expr
132
     * @return CompiledExpression
133
     */
134 363
    public function compile($expr)
135
    {
136 363
        $className = get_class($expr);
137
        switch ($className) {
138 363
            case 'PhpParser\Node\Expr\PropertyFetch':
139
                return $this->passPropertyFetch($expr);
140 363
            case 'PhpParser\Node\Stmt\Property':
141
                return $this->passProperty($expr);
142 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
143
                return $this->passConstFetch($expr);
144 363
            case 'PhpParser\Node\Expr\Assign':
145
                return $this->passSymbol($expr);
146 363
            case 'PhpParser\Node\Expr\AssignRef':
147
                return $this->passSymbolByRef($expr);
148 363
            case 'PhpParser\Node\Expr\Variable':
149
                return $this->passExprVariable($expr);
150
            /**
151
             * Cast operators
152
             */
153 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
154
                return $this->passCastBoolean($expr);
155 363
            case 'PhpParser\Node\Expr\Cast\Int_':
156
                return $this->passCastInt($expr);
157 363
            case 'PhpParser\Node\Expr\Cast\Double':
158
                return $this->passCastFloat($expr);
159 363
            case 'PhpParser\Node\Expr\Cast\String_':
160
                return $this->passCastString($expr);
161 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
162
                return $this->passCastUnset($expr);
163
            /**
164
             * Expressions
165
             */
166 363
            case 'PhpParser\Node\Expr\Array_':
167 11
                return $this->getArray($expr);
168 362
            case 'PhpParser\Node\Expr\ConstFetch':
169
                return $this->constFetch($expr);
170 362
            case 'PhpParser\Node\Name':
171
                return $this->getNodeName($expr);
172
            /**
173
             * Simple Scalar(s)
174
             */
175 362
            case 'PHPSA\Node\Scalar\Nil':
176 8
                return new CompiledExpression(CompiledExpression::NULL);
177 361
            case 'PhpParser\Node\Scalar\LNumber':
178 256
                return new CompiledExpression(CompiledExpression::LNUMBER, $expr->value);
179 358
            case 'PhpParser\Node\Scalar\DNumber':
180 131
                return new CompiledExpression(CompiledExpression::DNUMBER, $expr->value);
181 357
            case 'PhpParser\Node\Scalar\String_':
182 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
183 355
            case 'PHPSA\Node\Scalar\Boolean':
184 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
185 353
            case 'PHPSA\Node\Scalar\Fake':
186 29
                return new CompiledExpression($expr->type, $expr->value);
187
        }
188
189 353
        $expressionCompiler = $this->factory($expr);
190 353
        if (!$expressionCompiler) {
191
            $this->context->debug("Expression compiler is not implemented for {$className}");
192
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
193
        }
194
195 353
        $result = $expressionCompiler->pass($expr, $this->context);
196 353
        if (!$result instanceof CompiledExpression) {
197
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
198
        }
199
200 353
        return $result;
201
    }
202
203
    /**
204
     * @todo Implement
205
     *
206
     * @param Node\Stmt\Property $st
207
     * @return CompiledExpression
208
     */
209
    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...
210
    {
211
        return new CompiledExpression();
212
    }
213
214
    /**
215
     * @param Node\Expr\Variable $expr
216
     * @return CompiledExpression
217
     */
218
    public function declareVariable(Node\Expr\Variable $expr)
219
    {
220
        $variable = $this->context->getSymbol($expr->name);
221
        if ($variable) {
222
            $variable->incGets();
223
            return new CompiledExpression($variable->getType(), $variable->getValue());
224
        }
225
226
        $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...
227
        $this->context->addVariable($symbol);
228
229
        return new CompiledExpression;
230
    }
231
232
    /**
233
     * @param Node\Name $expr
234
     * @return CompiledExpression
235
     */
236
    public function getNodeName(Node\Name $expr)
237
    {
238
        if ($expr->parts[0] === 'null') {
239
            return new CompiledExpression(CompiledExpression::NULL);
240
        }
241
242
        $this->context->debug('Unknown how to get node name');
243
        return new CompiledExpression();
244
    }
245
246
    /**
247
     * (bool) {$expr}
248
     *
249
     * @param Node\Expr\Cast\Bool_ $expr
250
     * @return CompiledExpression
251
     */
252
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
253
    {
254
        $expression = new Expression($this->context);
255
        $compiledExpression = $expression->compile($expr->expr);
256
257
        switch ($compiledExpression->getType()) {
258
            case CompiledExpression::BOOLEAN:
259
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
260
                return $compiledExpression;
261
            case CompiledExpression::DNUMBER:
262
            case CompiledExpression::LNUMBER:
263
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
264
        }
265
266
        return new CompiledExpression();
267
    }
268
269
    /**
270
     * (int) {$expr}
271
     *
272
     * @param Node\Expr\Cast\Int_ $expr
273
     * @return CompiledExpression
274
     */
275
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
276
    {
277
        $expression = new Expression($this->context);
278
        $compiledExpression = $expression->compile($expr->expr);
279
280
        switch ($compiledExpression->getType()) {
281
            case CompiledExpression::LNUMBER:
282
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
283
                return $compiledExpression;
284
            case CompiledExpression::BOOLEAN:
285
            case CompiledExpression::DNUMBER:
286
            case CompiledExpression::STRING:
287
                return new CompiledExpression(CompiledExpression::LNUMBER, (int) $compiledExpression->getValue());
288
        }
289
290
        return new CompiledExpression();
291
    }
292
293
    /**
294
     * (float) {$expr}
295
     *
296
     * @param Node\Expr\Cast\Double $expr
297
     * @return CompiledExpression
298
     */
299
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
300
    {
301
        $expression = new Expression($this->context);
302
        $compiledExpression = $expression->compile($expr->expr);
303
304
        switch ($compiledExpression->getType()) {
305
            case CompiledExpression::DNUMBER:
306
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
307
                return $compiledExpression;
308
            case CompiledExpression::BOOLEAN:
309
            case CompiledExpression::LNUMBER:
310
            case CompiledExpression::STRING:
311
                return new CompiledExpression(CompiledExpression::DNUMBER, (float) $compiledExpression->getValue());
312
        }
313
314
        return new CompiledExpression();
315
    }
316
317
    /**
318
     * (string) {$expr}
319
     *
320
     * @param Node\Expr\Cast\String_ $expr
321
     * @return CompiledExpression
322
     */
323
    protected function passCastString(Node\Expr\Cast\String_ $expr)
324
    {
325
        $expression = new Expression($this->context);
326
        $compiledExpression = $expression->compile($expr->expr);
327
328
        switch ($compiledExpression->getType()) {
329
            case CompiledExpression::STRING:
330
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
331
                return $compiledExpression;
332
            case CompiledExpression::BOOLEAN:
333
            case CompiledExpression::LNUMBER:
334
            case CompiledExpression::DNUMBER:
335
                return new CompiledExpression(CompiledExpression::DNUMBER, (string) $compiledExpression->getValue());
336
        }
337
338
        return new CompiledExpression();
339
    }
340
341
    /**
342
     * (unset) {$expr}
343
     *
344
     * @param Node\Expr\Cast\Unset_ $expr
345
     * @return CompiledExpression
346
     */
347
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
348
    {
349
        $expression = new Expression($this->context);
350
        $compiledExpression = $expression->compile($expr->expr);
351
352
        switch ($compiledExpression->getType()) {
353
            case CompiledExpression::NULL:
354
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
355
                return $compiledExpression;
356
        }
357
358
        return new CompiledExpression(CompiledExpression::NULL, null);
359
    }
360
361
    /**
362
     * @param Node\Expr\PropertyFetch $expr
363
     * @return CompiledExpression
364
     */
365
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
366
    {
367
        $scopeExpression = $this->compile($expr->var);
368
        switch ($scopeExpression->getType()) {
369
            case CompiledExpression::OBJECT:
370
                if ($scopeExpression->getValue() == 'this') {
371
                    if ($this->context->scope === null) {
372
                        throw new RuntimeException('Current $this scope is null');
373
                    }
374
375
                    $propertyName = is_string($expr->name) ? $expr->name : false;
376
                    if ($expr->name instanceof Variable) {
377
                        /**
378
                         * @todo implement fetch from symbol table
379
                         */
380
                        //$methodName = $expr->name->name;
381
                    }
382
383
                    if ($propertyName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $propertyName of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
384
                        if (!$this->context->scope->hasProperty($propertyName)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PHPSA\Definition\ParentDefinition as the method hasProperty() does only exist in the following sub-classes of PHPSA\Definition\ParentDefinition: PHPSA\Definition\ClassDefinition, PHPSA\Definition\RuntimeClassDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
385
                            $this->context->notice(
386
                                'undefined-property',
387
                                sprintf('Property %s does not exist in %s scope', $propertyName, $scopeExpression->getValue()),
388
                                $expr
389
                            );
390
                        }
391
                    }
392
                }
393
                break;
394
            default:
395
                $this->context->debug('You cannot fetch property from not object type');
396
                break;
397
        }
398
399
        $this->context->debug('Unknown property fetch');
400
        return new CompiledExpression();
401
    }
402
403
    /**
404
     * @param Node\Expr\ClassConstFetch $expr
405
     * @return CompiledExpression
406
     */
407
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
408
    {
409
        if ($expr->class instanceof Node\Name) {
410
            $scope = $expr->class->parts[0];
411
412
            if ($scope == 'self' || $scope == 'this') {
413
                if ($this->context->scope === null) {
414
                    throw new RuntimeException("Current {$scope} scope is null");
415
                }
416
417
                if (!$this->context->scope->hasConst($expr->name)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PHPSA\Definition\ParentDefinition as the method hasConst() does only exist in the following sub-classes of PHPSA\Definition\ParentDefinition: PHPSA\Definition\ClassDefinition, PHPSA\Definition\RuntimeClassDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
418
                    $this->context->notice(
419
                        'undefined-const',
420
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $scope),
421
                        $expr
422
                    );
423
                }
424
425
                return new CompiledExpression();
426
            }
427
        }
428
429
        $this->context->debug('Unknown const fetch');
430
        return new CompiledExpression();
431
    }
432
433
    /**
434
     * @param Node\Expr\Assign $expr
435
     * @return CompiledExpression
436
     */
437
    protected function passSymbol(Node\Expr\Assign $expr)
438
    {
439
        $expression = new Expression($this->context);
440
        $compiledExpression = $expression->compile($expr->expr);
441
442
        if ($expr->var instanceof Node\Expr\List_) {
443
            $isCorrectType = false;
444
445
            switch ($compiledExpression->getType()) {
446
                case CompiledExpression::ARR:
447
                    $isCorrectType = true;
448
                    break;
449
            }
450
451
            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...
452
                foreach ($expr->var->vars as $key => $var) {
453
                    if ($var instanceof Node\Expr\Variable) {
454
                        $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...
455
456
                        $symbol = $this->context->getSymbol($name);
457
                        if (!$symbol) {
458
                            $symbol = new Variable(
459
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 454 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...
460
                                null,
461
                                CompiledExpression::UNKNOWN,
462
                                $this->context->getCurrentBranch()
463
                            );
464
                            $this->context->addVariable($symbol);
465
                        }
466
467
                        if (!$isCorrectType) {
468
                            $symbol->modify(CompiledExpression::NULL, null);
469
                        }
470
471
                        $symbol->incSets();
472
                    }
473
                }
474
            }
475
476
            return new CompiledExpression();
477
        }
478
479
        if ($expr->var instanceof Node\Expr\Variable) {
480
            $name = $expr->var->name;
481
482
            $symbol = $this->context->getSymbol($name);
483
            if ($symbol) {
484
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
485
            } else {
486
                $symbol = new Variable(
487
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 480 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...
488
                    $compiledExpression->getValue(),
489
                    $compiledExpression->getType(),
490
                    $this->context->getCurrentBranch()
491
                );
492
                $this->context->addVariable($symbol);
493
            }
494
495
            $symbol->incSets();
496
            return $compiledExpression;
497
        }
498
499
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
500
            $compiledExpression = $this->compile($expr->var->var);
501
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
502
                $objectDefinition = $compiledExpression->getValue();
503
                if ($objectDefinition instanceof ClassDefinition) {
504
                    if (is_string($expr->var->name)) {
505
                        if ($objectDefinition->hasProperty($expr->var->name)) {
506
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
507
                        }
508
                    }
509
                }
510
            }
511
        }
512
513
        $this->context->debug('Unknown how to pass symbol');
514
        return new CompiledExpression();
515
    }
516
517
    /**
518
     * @param Node\Expr\AssignRef $expr
519
     * @return CompiledExpression
520
     */
521
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
522
    {
523
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
524
            return new CompiledExpression();
525
        }
526
527
        if ($expr->var instanceof Node\Expr\Variable) {
528
            $name = $expr->var->name;
529
530
            $expression = new Expression($this->context);
531
            $compiledExpression = $expression->compile($expr->expr);
532
533
            $symbol = $this->context->getSymbol($name);
534
            if ($symbol) {
535
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
536
537
                if ($expr->expr instanceof Node\Expr\Variable) {
538
                    $rightVarName = $expr->expr->name;
539
540
                    $rightSymbol = $this->context->getSymbol($rightVarName);
541
                    if ($rightSymbol) {
542
                        $rightSymbol->incUse();
543
                        $symbol->setReferencedTo($rightSymbol);
544
                    } else {
545
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
546
                    }
547
                }
548
549
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
550
            } else {
551
                $symbol = new Variable(
552
                    $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...
553
                    $compiledExpression->getValue(),
554
                    $compiledExpression->getType(),
555
                    $this->context->getCurrentBranch()
556
                );
557
                $this->context->addVariable($symbol);
558
            }
559
560
            $symbol->incSets();
561
            return $compiledExpression;
562
        }
563
564
        $this->context->debug('Unknown how to pass symbol by ref');
565
        return new CompiledExpression();
566
    }
567
568
    /**
569
     * @param Node\Expr\Variable $expr
570
     * @return CompiledExpression
571
     */
572
    protected function passExprVariable(Node\Expr\Variable $expr)
573
    {
574
        $variable = $this->context->getSymbol($expr->name);
575
        if ($variable) {
576
            $variable->incGets();
577
            return new CompiledExpression($variable->getType(), $variable->getValue());
578
        }
579
580
        $this->context->notice(
581
            'undefined-variable',
582
            sprintf('You trying to use undefined variable $%s', $expr->name),
583
            $expr
584
        );
585
586
        return new CompiledExpression();
587
    }
588
589
    /**
590
     * Compile Array_ expression to CompiledExpression
591
     *
592
     * @param Node\Expr\Array_ $expr
593
     * @return CompiledExpression
594
     */
595 11
    protected function getArray(Node\Expr\Array_ $expr)
596
    {
597 11
        if ($expr->items === array()) {
598 8
            return new CompiledExpression(CompiledExpression::ARR, array());
599
        }
600
601 3
        $resultArray = array();
602
603 3
        foreach ($expr->items as $item) {
604 3
            $compiledValueResult = $this->compile($item->value);
605 3
            if ($item->key) {
606 1
                $compiledKeyResult = $this->compile($item->key);
607 1
                switch ($compiledKeyResult->getType()) {
608 1
                    case CompiledExpression::INTEGER:
609 1
                    case CompiledExpression::DOUBLE:
610 1
                    case CompiledExpression::BOOLEAN:
611 1
                    case CompiledExpression::NULL:
612 1
                    case CompiledExpression::STRING:
613 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
614 1
                        break;
615
                    default:
616
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
617
                        return new CompiledExpression(CompiledExpression::ARR);
618
                        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...
619 1
                }
620 1
            } else {
621 2
                $resultArray[] = $compiledValueResult->getValue();
622
            }
623 3
        }
624
625 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
626
    }
627
628
    /**
629
     * Convert const fetch expr to CompiledExpression
630
     *
631
     * @param Node\Expr\ConstFetch $expr
632
     * @return CompiledExpression
633
     */
634
    protected function constFetch(Node\Expr\ConstFetch $expr)
635
    {
636
        if ($expr->name instanceof Node\Name) {
637
            if ($expr->name->parts[0] === 'true') {
638
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
639
            }
640
641
            if ($expr->name->parts[0] === 'false') {
642
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
643
            }
644
        }
645
646
        /**
647
         * @todo Implement check
648
         */
649
650
        $expression = new Expression($this->context);
651
        $compiledExpr = $expression->compile($expr->name);
652
653
        return $compiledExpr;
654
    }
655
}
656