Completed
Push — master ( e3ac6b...c3d7b3 )
by Дмитрий
02:53
created

Expression   F

Complexity

Total Complexity 137

Size/Duplication

Total Lines 658
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 54

Test Coverage

Coverage 28.79%

Importance

Changes 22
Bugs 0 Features 7
Metric Value
wmc 137
c 22
b 0
f 7
lcom 1
cbo 54
dl 0
loc 658
ccs 112
cts 389
cp 0.2879
rs 1.153

18 Methods

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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