Completed
Push — master ( 40ee83...aecd2c )
by Дмитрий
07:12 queued 02:11
created

Expression   F

Complexity

Total Complexity 131

Size/Duplication

Total Lines 644
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 51

Test Coverage

Coverage 27.06%

Importance

Changes 20
Bugs 0 Features 5
Metric Value
wmc 131
c 20
b 0
f 5
lcom 1
cbo 51
dl 0
loc 644
ccs 102
cts 377
cp 0.2706
rs 1.1896

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A passProperty() 0 4 1
A declareVariable() 0 13 2
A getNodeName() 0 9 2
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
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
C passPropertyFetch() 0 41 7
D factory() 0 94 32
C compile() 0 68 23

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 131 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 316
    public function __construct(Context $context)
27
    {
28 316
        $this->context = $context;
29 316
    }
30
31
    /**
32
     * @param $expr
33
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
34
     */
35 312
    protected function factory($expr)
36
    {
37 312
        switch (get_class($expr)) {
38
            /**
39
             * Call(s)
40
             */
41 312
            case 'PhpParser\Node\Expr\MethodCall':
42
                return new Expression\MethodCall();
43 312
            case 'PhpParser\Node\Expr\FuncCall':
44
                return new Expression\FunctionCall();
45 312
            case 'PhpParser\Node\Expr\StaticCall':
46
                return new Expression\StaticCall();
47
            /**
48
             * Operators
49
             */
50 312
            case 'PhpParser\Node\Expr\New_':
51
                return new Expression\Operators\NewOp();
52 312
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
53 28
                return new Expression\BinaryOp\Identical();
54 284
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
55
                return new Expression\Operators\Contact();
56 284
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
57 14
                return new Expression\BinaryOp\NotIdentical();
58 270
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
59 34
                return new Expression\BinaryOp\Equal();
60 236
            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 219
            case 'PhpParser\Node\Expr\PostInc':
66
                return new Expression\Operators\PostInc();
67 219
            case 'PhpParser\Node\Expr\PostDec':
68
                return new Expression\Operators\PostDec();
69
            /**
70
             * Arithmetical
71
             */
72 219
            case 'PhpParser\Node\Expr\BinaryOp\Div':
73 37
                return new Expression\Operators\Arithmetical\Div();
74 182
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
75 41
                return new Expression\Operators\Arithmetical\Plus();
76 141
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
77 18
                return new Expression\Operators\Arithmetical\Minus();
78 123
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
79 35
                return new Expression\Operators\Arithmetical\Mul();
80 88
            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 53
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
87
                return new Expression\Operators\Bitwise\BitwiseOr();
88 53
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
89
                return new Expression\Operators\Bitwise\BitwiseXor();
90 53
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
91
                return new Expression\Operators\Bitwise\BitwiseAnd();
92 53
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
93
                return new Expression\Operators\Bitwise\ShiftRight();
94 53
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
95
                return new Expression\Operators\Bitwise\ShiftLeft();
96 53
            case 'PhpParser\Node\Expr\BitwiseNot':
97
                return new Expression\Operators\Bitwise\BitwiseNot();
98
            /**
99
             * Logical
100
             */
101 53
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
102
                return new Expression\Operators\Logical\BooleanOr();
103 53
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
104 5
                return new Expression\Operators\Logical\BooleanAnd();
105 48
            case 'PhpParser\Node\Expr\BooleanNot':
106
                return new Expression\Operators\Logical\BooleanNot();
107
            /**
108
             * Comparison
109
             */
110 48
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
111 12
                return new Expression\Operators\Comparison\Greater();
112 36
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
113 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
114 24
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
115 12
                return new Expression\Operators\Comparison\Smaller();
116 12
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
117 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
118
            /**
119
             * Another
120
             */
121
            case 'PhpParser\Node\Expr\UnaryMinus':
122
                return new Expression\Operators\UnaryMinus();
123
            case 'PhpParser\Node\Expr\UnaryPlus':
124
                return new Expression\Operators\UnaryPlus();
125
        }
126
127
        return false;
128
    }
129
130
    /**
131
     * @param $expr
132
     * @return CompiledExpression
133
     */
134 316
    public function compile($expr)
135
    {
136 316
        $className = get_class($expr);
137
        switch ($className) {
138 316
            case 'PhpParser\Node\Expr\PropertyFetch':
139
                return $this->passPropertyFetch($expr);
140 316
            case 'PhpParser\Node\Stmt\Property':
141
                return $this->passProperty($expr);
142 316
            case 'PhpParser\Node\Expr\ClassConstFetch':
143
                return $this->passConstFetch($expr);
144 316
            case 'PhpParser\Node\Expr\Assign':
145
                return $this->passSymbol($expr);
146 316
            case 'PhpParser\Node\Expr\AssignRef':
147
                return $this->passSymbolByRef($expr);
148 316
            case 'PhpParser\Node\Expr\Variable':
149
                return $this->passExprVariable($expr);
150
            /**
151
             * Cast operators
152
             */
153 316
            case 'PhpParser\Node\Expr\Cast\Bool_':
154
                return $this->passCastBoolean($expr);
155 316
            case 'PhpParser\Node\Expr\Cast\Int_':
156
                return $this->passCastInt($expr);
157 316
            case 'PhpParser\Node\Expr\Cast\Double':
158
                return $this->passCastFloat($expr);
159 316
            case 'PhpParser\Node\Expr\Cast\String_':
160
                return $this->passCastString($expr);
161 316
            case 'PhpParser\Node\Expr\Cast\Unset_':
162
                return $this->passCastUnset($expr);
163
            /**
164
             * Expressions
165
             */
166 316
            case 'PhpParser\Node\Expr\Array_':
167 9
                return $this->getArray($expr);
168 315
            case 'PhpParser\Node\Expr\ConstFetch':
169
                return $this->constFetch($expr);
170 315
            case 'PhpParser\Node\Name':
171
                return $this->getNodeName($expr);
172
            /**
173
             * Simple Scalar(s)
174
             */
175 315
            case 'PHPSA\Node\Scalar\Nil':
176
                return new CompiledExpression(CompiledExpression::NULL);
177 315
            case 'PhpParser\Node\Scalar\LNumber':
178 248
                return new CompiledExpression(CompiledExpression::LNUMBER, $expr->value);
179 313
            case 'PhpParser\Node\Scalar\DNumber':
180 130
                return new CompiledExpression(CompiledExpression::DNUMBER, $expr->value);
181 313
            case 'PhpParser\Node\Scalar\String_':
182 1
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
183 312
            case 'PHPSA\Node\Scalar\Boolean':
184 44
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
185 312
            case 'PHPSA\Node\Scalar\Fake':
186 27
                return new CompiledExpression($expr->type, $expr->value);
187
        }
188
189 312
        $expressionCompiler = $this->factory($expr);
190 312
        if (!$expressionCompiler) {
191
            $this->context->debug("Expression compiler is not implemented for {$className}");
192
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
193
        }
194
195 312
        $result = $expressionCompiler->pass($expr, $this->context);
196 312
        if (!$result instanceof CompiledExpression) {
197
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
198
        }
199
200 312
        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
                $scopeExpressionValue = $scopeExpression->getValue();
371
                if ($scopeExpressionValue instanceof ClassDefinition) {
372
                    $propertyName = is_string($expr->name) ? $expr->name : false;
373
                    if ($expr->name instanceof Variable) {
374
                        /**
375
                         * @todo implement fetch from symbol table
376
                         */
377
                        //$methodName = $expr->name->name;
378
                    }
379
380
                    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...
381
                        if ($scopeExpressionValue->hasProperty($propertyName, true)) {
382
                            $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...
383
                            return new CompiledExpression();
384
                        } else {
385
                            $this->context->notice(
386
                                'undefined-property',
387
                                sprintf(
388
                                    'Property %s does not exist in %s scope',
389
                                    $propertyName,
390
                                    $scopeExpressionValue->getName()
391
                                ),
392
                                $expr
393
                            );
394
                        }
395
                    }
396
                }
397
                break;
398
            default:
399
                $this->context->debug('You cannot fetch property from not object type');
400
                break;
401
        }
402
403
        $this->context->debug('Unknown property fetch');
404
        return new CompiledExpression();
405
    }
406
407
    /**
408
     * @param Node\Expr\ClassConstFetch $expr
409
     * @return CompiledExpression
410
     */
411
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
412
    {
413
        if ($expr->class instanceof Node\Name) {
414
            $scope = $expr->class->parts[0];
415
416
            if ($scope == 'self' || $scope == 'this') {
417
                if ($this->context->scope === null) {
418
                    throw new RuntimeException("Current {$scope} scope is null");
419
                }
420
421
                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...
422
                    $this->context->notice(
423
                        'undefined-const',
424
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $scope),
425
                        $expr
426
                    );
427
                }
428
429
                return new CompiledExpression();
430
            }
431
        }
432
433
        $this->context->debug('Unknown const fetch');
434
        return new CompiledExpression();
435
    }
436
437
    /**
438
     * @param Node\Expr\Assign $expr
439
     * @return CompiledExpression
440
     */
441
    protected function passSymbol(Node\Expr\Assign $expr)
442
    {
443
        $expression = new Expression($this->context);
444
        $compiledExpression = $expression->compile($expr->expr);
445
446
        if ($expr->var instanceof Node\Expr\List_) {
447
            $isCorrectType = false;
448
449
            switch ($compiledExpression->getType()) {
450
                case CompiledExpression::ARR:
451
                    $isCorrectType = true;
452
                    break;
453
            }
454
455
            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...
456
                foreach ($expr->var->vars as $key => $var) {
457
                    if ($var instanceof Node\Expr\Variable) {
458
                        $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...
459
460
                        $symbol = $this->context->getSymbol($name);
461
                        if (!$symbol) {
462
                            $symbol = new Variable(
463
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 458 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...
464
                                null,
465
                                CompiledExpression::UNKNOWN,
466
                                $this->context->getCurrentBranch()
467
                            );
468
                            $this->context->addVariable($symbol);
469
                        }
470
471
                        if (!$isCorrectType) {
472
                            $symbol->modify(CompiledExpression::NULL, null);
473
                        }
474
475
                        $symbol->incSets();
476
                    }
477
                }
478
            }
479
480
            return new CompiledExpression();
481
        }
482
483
        if ($expr->var instanceof Node\Expr\Variable) {
484
            $name = $expr->var->name;
485
486
            $symbol = $this->context->getSymbol($name);
487
            if ($symbol) {
488
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
489
            } else {
490
                $symbol = new Variable(
491
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 484 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...
492
                    $compiledExpression->getValue(),
493
                    $compiledExpression->getType(),
494
                    $this->context->getCurrentBranch()
495
                );
496
                $this->context->addVariable($symbol);
497
            }
498
499
            $symbol->incSets();
500
            return $compiledExpression;
501
        }
502
503
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
504
            $compiledExpression = $this->compile($expr->var->var);
505
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
506
                $objectDefinition = $compiledExpression->getValue();
507
                if ($objectDefinition instanceof ClassDefinition) {
508
                    if (is_string($expr->var->name)) {
509
                        if ($objectDefinition->hasProperty($expr->var->name)) {
510
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
511
                        }
512
                    }
513
                }
514
            }
515
        }
516
517
        $this->context->debug('Unknown how to pass symbol');
518
        return new CompiledExpression();
519
    }
520
521
    /**
522
     * @param Node\Expr\AssignRef $expr
523
     * @return CompiledExpression
524
     */
525
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
526
    {
527
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
528
            return new CompiledExpression();
529
        }
530
531
        if ($expr->var instanceof Node\Expr\Variable) {
532
            $name = $expr->var->name;
533
534
            $expression = new Expression($this->context);
535
            $compiledExpression = $expression->compile($expr->expr);
536
537
            $symbol = $this->context->getSymbol($name);
538
            if ($symbol) {
539
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
540
541
                if ($expr->expr instanceof Node\Expr\Variable) {
542
                    $rightVarName = $expr->expr->name;
543
544
                    $rightSymbol = $this->context->getSymbol($rightVarName);
545
                    if ($rightSymbol) {
546
                        $rightSymbol->incUse();
547
                        $symbol->setReferencedTo($rightSymbol);
548
                    } else {
549
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
550
                    }
551
                }
552
553
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
554
            } else {
555
                $symbol = new Variable(
556
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 532 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...
557
                    $compiledExpression->getValue(),
558
                    $compiledExpression->getType(),
559
                    $this->context->getCurrentBranch()
560
                );
561
                $this->context->addVariable($symbol);
562
            }
563
564
            $symbol->incSets();
565
            return $compiledExpression;
566
        }
567
568
        $this->context->debug('Unknown how to pass symbol by ref');
569
        return new CompiledExpression();
570
    }
571
572
    /**
573
     * @param Node\Expr\Variable $expr
574
     * @return CompiledExpression
575
     */
576
    protected function passExprVariable(Node\Expr\Variable $expr)
577
    {
578
        $variable = $this->context->getSymbol($expr->name);
579
        if ($variable) {
580
            $variable->incGets();
581
            return new CompiledExpression($variable->getType(), $variable->getValue());
582
        }
583
584
        $this->context->notice(
585
            'undefined-variable',
586
            sprintf('You trying to use undefined variable $%s', $expr->name),
587
            $expr
588
        );
589
590
        return new CompiledExpression();
591
    }
592
593
    /**
594
     * Compile Array_ expression to CompiledExpression
595
     *
596
     * @param Node\Expr\Array_ $expr
597
     * @return CompiledExpression
598
     */
599 9
    protected function getArray(Node\Expr\Array_ $expr)
600
    {
601 9
        if ($expr->items === array()) {
602 6
            return new CompiledExpression(CompiledExpression::ARR, array());
603
        }
604
605 3
        $resultArray = array();
606
607 3
        foreach ($expr->items as $item) {
608 3
            $compiledValueResult = $this->compile($item->value);
609 3
            if ($item->key) {
610 1
                $compiledKeyResult = $this->compile($item->key);
611 1
                switch ($compiledKeyResult->getType()) {
612 1
                    case CompiledExpression::INTEGER:
613 1
                    case CompiledExpression::DOUBLE:
614 1
                    case CompiledExpression::BOOLEAN:
615 1
                    case CompiledExpression::NULL:
616 1
                    case CompiledExpression::STRING:
617 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
618 1
                        break;
619
                    default:
620
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
621
                        return new CompiledExpression(CompiledExpression::ARR);
622
                        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...
623 1
                }
624 1
            } else {
625 2
                $resultArray[] = $compiledValueResult->getValue();
626
            }
627 3
        }
628
629 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
630
    }
631
632
    /**
633
     * Convert const fetch expr to CompiledExpression
634
     *
635
     * @param Node\Expr\ConstFetch $expr
636
     * @return CompiledExpression
637
     */
638
    protected function constFetch(Node\Expr\ConstFetch $expr)
639
    {
640
        if ($expr->name instanceof Node\Name) {
641
            if ($expr->name->parts[0] === 'true') {
642
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
643
            }
644
645
            if ($expr->name->parts[0] === 'false') {
646
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
647
            }
648
        }
649
650
        /**
651
         * @todo Implement check
652
         */
653
654
        $expression = new Expression($this->context);
655
        $compiledExpr = $expression->compile($expr->name);
656
657
        return $compiledExpr;
658
    }
659
}
660