Completed
Push — master ( da576a...aba30f )
by Дмитрий
03:13
created

Expression   F

Complexity

Total Complexity 162

Size/Duplication

Total Lines 772
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 61

Test Coverage

Coverage 26.41%

Importance

Changes 35
Bugs 1 Features 8
Metric Value
c 35
b 1
f 8
dl 0
loc 772
ccs 122
cts 462
cp 0.2641
rs 1.0434
wmc 162
lcom 1
cbo 61

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
D compile() 0 82 27
D factory() 0 114 39
C passProperty() 0 58 11
A declareVariable() 0 13 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 35 7
B passCastBoolean() 0 18 6
B passCastInt() 0 18 6
B passCastFloat() 0 18 6
B passCastString() 0 18 6
A passCastUnset() 0 13 2
C passPropertyFetch() 0 39 7
B passConstFetch() 0 22 4
D passSymbol() 0 82 16
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 InvalidArgumentException;
9
use PHPSA\Check;
10
use PHPSA\CompiledExpression;
11
use PHPSA\Context;
12
use PhpParser\Node;
13
use PHPSA\Definition\ClassDefinition;
14
use PHPSA\Exception\RuntimeException;
15
use PHPSA\Variable;
16
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
17
18
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 162 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...
19
{
20
    /**
21
     * @var Context
22
     */
23
    protected $context;
24
25
    /**
26
     * @param Context $context
27
     */
28 363
    public function __construct(Context $context)
29
    {
30 363
        $this->context = $context;
31 363
    }
32
33
    /**
34
     * @param $expr
35
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
36
     */
37 353
    protected function factory($expr)
38
    {
39 353
        switch (get_class($expr)) {
40
            /**
41
             * Call(s)
42
             */
43 353
            case Node\Expr\MethodCall::class:
44
                return new Expression\MethodCall();
45 353
            case Node\Expr\FuncCall::class:
46
                return new Expression\FunctionCall();
47 353
            case Node\Expr\StaticCall::class:
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case Node\Expr\New_::class:
53
                return new Expression\Operators\NewOp();
54 353
            case Node\Expr\Instanceof_::class:
55
                return new Expression\Operators\InstanceOfOp();
56
            /**
57
             * Assign
58
             */
59 353
            case Node\Expr\AssignOp\Pow::class:
60
                return new Expression\AssignOp\Pow();
61 353
            case Node\Expr\AssignOp\Plus::class:
62
                return new Expression\AssignOp\Plus();
63 353
            case Node\Expr\AssignOp\Minus::class:
64
                return new Expression\AssignOp\Minus();
65 353
            case Node\Expr\AssignOp\Mod::class:
66
                return new Expression\AssignOp\Mod();
67 353
            case Node\Expr\AssignOp\BitwiseOr::class:
68
                return new Expression\AssignOp\BitwiseOr();
69 353
            case Node\Expr\AssignOp\BitwiseAnd::class:
70
                return new Expression\AssignOp\BitwiseAnd();
71
            /**
72
             * BinaryOp
73
             */
74 353
            case Node\Expr\BinaryOp\Identical::class:
75 28
                return new Expression\BinaryOp\Identical();
76 325
            case Node\Expr\BinaryOp\Concat::class:
77
                return new Expression\Operators\Contact();
78 325
            case Node\Expr\BinaryOp\NotIdentical::class:
79 14
                return new Expression\BinaryOp\NotIdentical();
80 311
            case Node\Expr\BinaryOp\Equal::class:
81 34
                return new Expression\BinaryOp\Equal();
82 277
            case Node\Expr\BinaryOp\NotEqual::class:
83 17
                return new Expression\BinaryOp\NotEqual();
84
            /**
85
             * @link http://php.net/manual/en/language.operators.increment.php
86
             */
87 260
            case Node\Expr\PostInc::class:
88 4
                return new Expression\Operators\PostInc();
89 256
            case Node\Expr\PostDec::class:
90 4
                return new Expression\Operators\PostDec();
91
            /**
92
             * Arithmetical
93
             */
94 252
            case Node\Expr\BinaryOp\Div::class:
95 37
                return new Expression\Operators\Arithmetical\Div();
96 215
            case Node\Expr\BinaryOp\Plus::class:
97 41
                return new Expression\Operators\Arithmetical\Plus();
98 174
            case Node\Expr\BinaryOp\Minus::class:
99 18
                return new Expression\Operators\Arithmetical\Minus();
100 156
            case Node\Expr\BinaryOp\Mul::class:
101 35
                return new Expression\Operators\Arithmetical\Mul();
102 121
            case Node\Expr\BinaryOp\Mod::class:
103 35
                return new Expression\Operators\Arithmetical\Mod();
104
            /**
105
             * Bitwise
106
             * @link http://php.net/manual/ru/language.operators.bitwise.php
107
             */
108 86
            case Node\Expr\BinaryOp\BitwiseOr::class:
109
                return new Expression\Operators\Bitwise\BitwiseOr();
110 86
            case Node\Expr\BinaryOp\BitwiseXor::class:
111
                return new Expression\Operators\Bitwise\BitwiseXor();
112 86
            case Node\Expr\BinaryOp\BitwiseAnd::class:
113
                return new Expression\Operators\Bitwise\BitwiseAnd();
114 86
            case Node\Expr\BinaryOp\ShiftRight::class:
115
                return new Expression\Operators\Bitwise\ShiftRight();
116 86
            case Node\Expr\BinaryOp\ShiftLeft::class:
117
                return new Expression\Operators\Bitwise\ShiftLeft();
118 86
            case Node\Expr\BitwiseNot::class:
119
                return new Expression\Operators\Bitwise\BitwiseNot();
120
            /**
121
             * Logical
122
             */
123 86
            case Node\Expr\BinaryOp\BooleanOr::class:
124 10
                return new Expression\Operators\Logical\BooleanOr();
125 76
            case Node\Expr\BinaryOp\BooleanAnd::class:
126 5
                return new Expression\Operators\Logical\BooleanAnd();
127 71
            case Node\Expr\BooleanNot::class:
128 5
                return new Expression\Operators\Logical\BooleanNot();
129
            /**
130
             * Comparison
131
             */
132 66
            case Node\Expr\BinaryOp\Greater::class:
133 12
                return new Expression\Operators\Comparison\Greater();
134 54
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
135 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
136 42
            case Node\Expr\BinaryOp\Smaller::class:
137 12
                return new Expression\Operators\Comparison\Smaller();
138 30
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
139 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
140
            /**
141
             * Another
142
             */
143 18
            case Node\Expr\UnaryMinus::class:
144 9
                return new Expression\Operators\UnaryMinus();
145 9
            case Node\Expr\UnaryPlus::class:
146 9
                return new Expression\Operators\UnaryPlus();
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * @param object|string $expr
154
     * @return CompiledExpression
155
     */
156 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 672 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
157
    {
158 363
        if (is_string($expr)) {
159
            return new CompiledExpression(CompiledExpression::STRING, $expr);
160
        }
161
162 363
        if (is_null($expr)) {
163
            return new CompiledExpression(CompiledExpression::NULL);
164
        }
165
166 363
        if (!is_object($expr)) {
167
            throw new InvalidArgumentException('$expr must be string/object/null');
168
        }
169
170 363
        $className = get_class($expr);
171
        switch ($className) {
172 363
            case 'PhpParser\Node\Expr\PropertyFetch':
173
                return $this->passPropertyFetch($expr);
174 363
            case 'PhpParser\Node\Stmt\Property':
175
                return $this->passProperty($expr);
176 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
177
                return $this->passConstFetch($expr);
178 363
            case 'PhpParser\Node\Expr\Assign':
179
                return $this->passSymbol($expr);
180 363
            case 'PhpParser\Node\Expr\AssignRef':
181
                return $this->passSymbolByRef($expr);
182 363
            case 'PhpParser\Node\Expr\Variable':
183
                return $this->passExprVariable($expr);
184
            /**
185
             * Cast operators
186
             */
187 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
188
                return $this->passCastBoolean($expr);
189 363
            case 'PhpParser\Node\Expr\Cast\Int_':
190
                return $this->passCastInt($expr);
191 363
            case 'PhpParser\Node\Expr\Cast\Double':
192
                return $this->passCastFloat($expr);
193 363
            case 'PhpParser\Node\Expr\Cast\String_':
194
                return $this->passCastString($expr);
195 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
196
                return $this->passCastUnset($expr);
197
            /**
198
             * Expressions
199
             */
200 363
            case 'PhpParser\Node\Expr\Array_':
201 11
                return $this->getArray($expr);
202 362
            case 'PhpParser\Node\Expr\ConstFetch':
203
                return $this->constFetch($expr);
204 362
            case 'PhpParser\Node\Name':
205
                return $this->getNodeName($expr);
206 362
            case 'PhpParser\Node\Name\FullyQualified':
207
                return $this->getFullyQualifiedNodeName($expr);
208
            /**
209
             * Simple Scalar(s)
210
             */
211 362
            case 'PHPSA\Node\Scalar\Nil':
212 8
                return new CompiledExpression(CompiledExpression::NULL);
213 361
            case 'PhpParser\Node\Scalar\LNumber':
214 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
215 358
            case 'PhpParser\Node\Scalar\DNumber':
216 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
217 357
            case 'PhpParser\Node\Scalar\String_':
218 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
219 355
            case 'PHPSA\Node\Scalar\Boolean':
220 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
221 353
            case 'PHPSA\Node\Scalar\Fake':
222 29
                return new CompiledExpression($expr->type, $expr->value);
223
        }
224
225 353
        $expressionCompiler = $this->factory($expr);
226 353
        if (!$expressionCompiler) {
227
            $this->context->debug("Expression compiler is not implemented for {$className}");
228
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
229
        }
230
231 353
        $result = $expressionCompiler->pass($expr, $this->context);
232 353
        if (!$result instanceof CompiledExpression) {
233
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
234
        }
235
236 353
        return $result;
237
    }
238
239
    /**
240
     * @todo Implement
241
     *
242
     * @param Node\Stmt\Property $st
243
     * @return CompiledExpression
244
     */
245
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
246
    {
247
        $docBlock = $st->getDocComment();
248
        if (!$docBlock) {
249
            $this->context->notice(
250
                'missing-docblock',
251
                'Missing docblock for %s() property',
252
                $st
253
            );
254
        }
255
256
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
257
258
        $varTags = $phpdoc->getTagsByName('var');
259
        if ($varTags) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $varTags of type phpDocumentor\Reflection\DocBlock\Tag[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
260
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
261
            $varTag = current($varTags);
262
263
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
264
265
            try {
266
                $type = $typeResolver->resolve($varTag->getType());
267
            } catch (\InvalidArgumentException $e) {
268
                return new CompiledExpression();
269
            }
270
271
            if ($type) {
272
                switch (get_class($type)) {
273
                    case \phpDocumentor\Reflection\Types\Object_::class:
274
                        return new CompiledExpression(
275
                            CompiledExpression::OBJECT
276
                        );
277
                    case \phpDocumentor\Reflection\Types\Integer::class:
278
                        return new CompiledExpression(
279
                            CompiledExpression::INTEGER
280
                        );
281
                    case \phpDocumentor\Reflection\Types\String_::class:
282
                        return new CompiledExpression(
283
                            CompiledExpression::STRING
284
                        );
285
                    case \phpDocumentor\Reflection\Types\Float_::class:
286
                        return new CompiledExpression(
287
                            CompiledExpression::DOUBLE
288
                        );
289
                    case \phpDocumentor\Reflection\Types\Null_::class:
290
                        return new CompiledExpression(
291
                            CompiledExpression::NULL
292
                        );
293
                    case \phpDocumentor\Reflection\Types\Boolean::class:
294
                        return new CompiledExpression(
295
                            CompiledExpression::BOOLEAN
296
                        );
297
                }
298
            }
299
        }
300
301
        return new CompiledExpression();
302
    }
303
304
    /**
305
     * @param Node\Expr\Variable $expr
306
     * @return CompiledExpression
307
     */
308
    public function declareVariable(Node\Expr\Variable $expr)
309
    {
310
        $variable = $this->context->getSymbol($expr->name);
311
        if ($variable) {
312
            $variable->incGets();
313
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
314
        }
315
316
        $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...
317
        $this->context->addVariable($symbol);
318
319
        return new CompiledExpression;
320
    }
321
322
    /**
323
     * @param Node\Name\FullyQualified $expr
324
     * @return CompiledExpression
325
     */
326
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
327
    {
328
        $this->context->debug('Unimplemented FullyQualified', $expr);
329
        
330
        return new CompiledExpression;
331
    }
332
333
    /**
334
     * @param Node\Name $expr
335
     * @return CompiledExpression
336
     */
337
    public function getNodeName(Node\Name $expr)
338
    {
339
        if ($expr->toString() === 'null') {
340
            return new CompiledExpression(CompiledExpression::NULL);
341
        }
342
343
        if (in_array($expr, ['parent'], true)) {
344
            /** @var ClassDefinition $scope */
345
            $scope = $this->context->scope;
346
            assert($scope instanceof ClassDefinition);
347
348
            if ($scope->getExtendsClass()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $scope->getExtendsClass() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
349
                $definition = $scope->getExtendsClassDefinition();
350
                if ($definition) {
351
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
352
                }
353
            } else {
354
                $this->context->notice(
355
                    'no-parent',
356
                    'Cannot access parent:: when current class scope has no parent',
357
                    $expr
358
                );
359
            }
360
        }
361
362
        if (in_array($expr, ['self', 'static'], true)) {
363
            return CompiledExpression::fromZvalValue($this->context->scope);
364
        }
365
366
        if (defined($expr)) {
367
            return CompiledExpression::fromZvalValue(constant($expr));
368
        }
369
370
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
371
    }
372
373
    /**
374
     * (bool) {$expr}
375
     *
376
     * @param Node\Expr\Cast\Bool_ $expr
377
     * @return CompiledExpression
378
     */
379
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
380
    {
381
        $expression = new Expression($this->context);
382
        $compiledExpression = $expression->compile($expr->expr);
383
384
        switch ($compiledExpression->getType()) {
385
            case CompiledExpression::BOOLEAN:
386
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
387
                return $compiledExpression;
388
            case CompiledExpression::DOUBLE:
389
            case CompiledExpression::INTEGER:
390
            case CompiledExpression::NUMBER:
391
            case CompiledExpression::STRING:
392
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
393
        }
394
395
        return new CompiledExpression();
396
    }
397
398
    /**
399
     * (int) {$expr}
400
     *
401
     * @param Node\Expr\Cast\Int_ $expr
402
     * @return CompiledExpression
403
     */
404
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
405
    {
406
        $expression = new Expression($this->context);
407
        $compiledExpression = $expression->compile($expr->expr);
408
409
        switch ($compiledExpression->getType()) {
410
            case CompiledExpression::INTEGER:
411
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
412
                return $compiledExpression;
413
            case CompiledExpression::BOOLEAN:
414
            case CompiledExpression::DOUBLE:
415
            case CompiledExpression::NUMBER:
416
            case CompiledExpression::STRING:
417
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
418
        }
419
420
        return new CompiledExpression();
421
    }
422
423
    /**
424
     * (float) {$expr}
425
     *
426
     * @param Node\Expr\Cast\Double $expr
427
     * @return CompiledExpression
428
     */
429
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
430
    {
431
        $expression = new Expression($this->context);
432
        $compiledExpression = $expression->compile($expr->expr);
433
434
        switch ($compiledExpression->getType()) {
435
            case CompiledExpression::DOUBLE:
436
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
437
                return $compiledExpression;
438
            case CompiledExpression::BOOLEAN:
439
            case CompiledExpression::INTEGER:
440
            case CompiledExpression::NUMBER:
441
            case CompiledExpression::STRING:
442
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
443
        }
444
445
        return new CompiledExpression();
446
    }
447
448
    /**
449
     * (string) {$expr}
450
     *
451
     * @param Node\Expr\Cast\String_ $expr
452
     * @return CompiledExpression
453
     */
454
    protected function passCastString(Node\Expr\Cast\String_ $expr)
455
    {
456
        $expression = new Expression($this->context);
457
        $compiledExpression = $expression->compile($expr->expr);
458
459
        switch ($compiledExpression->getType()) {
460
            case CompiledExpression::STRING:
461
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
462
                return $compiledExpression;
463
            case CompiledExpression::BOOLEAN:
464
            case CompiledExpression::INTEGER:
465
            case CompiledExpression::NUMBER:
466
            case CompiledExpression::DOUBLE:
467
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
468
        }
469
470
        return new CompiledExpression();
471
    }
472
473
    /**
474
     * (unset) {$expr}
475
     *
476
     * @param Node\Expr\Cast\Unset_ $expr
477
     * @return CompiledExpression
478
     */
479
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
480
    {
481
        $expression = new Expression($this->context);
482
        $compiledExpression = $expression->compile($expr->expr);
483
484
        switch ($compiledExpression->getType()) {
485
            case CompiledExpression::NULL:
486
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
487
                return $compiledExpression;
488
        }
489
490
        return new CompiledExpression(CompiledExpression::NULL, null);
491
    }
492
493
    /**
494
     * @param Node\Expr\PropertyFetch $expr
495
     * @return CompiledExpression
496
     */
497
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
498
    {
499
        $propertNameCE = $this->compile($expr->name);
500
501
        $scopeExpression = $this->compile($expr->var);
502
        if ($scopeExpression->isObject()) {
503
            $scopeExpressionValue = $scopeExpression->getValue();
504
            if ($scopeExpressionValue instanceof ClassDefinition) {
505
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
506
                if ($propertyName) {
507
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
508
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
509
                        return $this->compile($property);
510
                    } else {
511
                        $this->context->notice(
512
                            'undefined-property',
513
                            sprintf(
514
                                'Property %s does not exist in %s scope',
515
                                $propertyName,
516
                                $scopeExpressionValue->getName()
517
                            ),
518
                            $expr
519
                        );
520
                    }
521
                }
522
            }
523
        } elseif (!$scopeExpression->canBeObject()) {
524
            return new CompiledExpression(CompiledExpression::UNKNOWN);
525
        }
526
527
        $this->context->notice(
528
            'property-fetch-on-non-object',
529
            "It's not possible to fetch property on not object",
530
            $expr,
531
            Check::CHECK_BETA
532
        );
533
534
        return new CompiledExpression(CompiledExpression::UNKNOWN);
535
    }
536
537
    /**
538
     * @param Node\Expr\ClassConstFetch $expr
539
     * @return CompiledExpression
540
     */
541
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
542
    {
543
        $leftCE = $this->compile($expr->class);
544
        if ($leftCE->isObject()) {
545
            $leftCEValue = $leftCE->getValue();
546
            if ($leftCEValue instanceof ClassDefinition) {
547
                if (!$leftCEValue->hasConst($expr->name)) {
548
                    $this->context->notice(
549
                        'undefined-const',
550
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
551
                        $expr
552
                    );
553
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
554
                }
555
556
                return new CompiledExpression();
557
            }
558
        }
559
560
        $this->context->debug('Unknown const fetch', $expr);
561
        return new CompiledExpression();
562
    }
563
564
    /**
565
     * @param Node\Expr\Assign $expr
566
     * @return CompiledExpression
567
     */
568
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 216 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
569
    {
570
        $expression = new Expression($this->context);
571
        $compiledExpression = $expression->compile($expr->expr);
572
573
        if ($expr->var instanceof Node\Expr\List_) {
574
            $isCorrectType = false;
575
576
            switch ($compiledExpression->getType()) {
577
                case CompiledExpression::ARR:
578
                    $isCorrectType = true;
579
                    break;
580
            }
581
582
            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...
583
                foreach ($expr->var->vars as $key => $var) {
584
                    if ($var instanceof Node\Expr\Variable) {
585
                        if (!isset($expr->var->name)) {
586
                            var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
587
                        }
588
                        $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...
589
590
                        $symbol = $this->context->getSymbol($name);
591
                        if (!$symbol) {
592
                            $symbol = new Variable(
593
                                $name,
594
                                null,
595
                                CompiledExpression::UNKNOWN,
596
                                $this->context->getCurrentBranch()
597
                            );
598
                            $this->context->addVariable($symbol);
599
                        }
600
601
                        if (!$isCorrectType) {
602
                            $symbol->modify(CompiledExpression::NULL, null);
603
                        }
604
605
                        $symbol->incSets();
606
                    }
607
                }
608
            }
609
610
            return new CompiledExpression();
611
        }
612
613
        if ($expr->var instanceof Node\Expr\Variable) {
614
            $name = $expr->var->name;
615
616
            $symbol = $this->context->getSymbol($name);
617
            if ($symbol) {
618
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
619
            } else {
620
                $symbol = new Variable(
621
                    $name,
622
                    $compiledExpression->getValue(),
623
                    $compiledExpression->getType(),
624
                    $this->context->getCurrentBranch()
625
                );
626
                $this->context->addVariable($symbol);
627
            }
628
629
            $symbol->incSets();
630
            return $compiledExpression;
631
        }
632
633
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
634
            $compiledExpression = $this->compile($expr->var->var);
635
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
636
                $objectDefinition = $compiledExpression->getValue();
637
                if ($objectDefinition instanceof ClassDefinition) {
638
                    if (is_string($expr->var->name)) {
639
                        if ($objectDefinition->hasProperty($expr->var->name)) {
640
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
641
                        }
642
                    }
643
                }
644
            }
645
        }
646
647
        $this->context->debug('Unknown how to pass symbol');
648
        return new CompiledExpression();
649
    }
650
651
    /**
652
     * @param Node\Expr\AssignRef $expr
653
     * @return CompiledExpression
654
     */
655
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
656
    {
657
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
658
            return new CompiledExpression();
659
        }
660
661
        if ($expr->var instanceof Node\Expr\Variable) {
662
            $name = $expr->var->name;
663
664
            $expression = new Expression($this->context);
665
            $compiledExpression = $expression->compile($expr->expr);
666
667
            $symbol = $this->context->getSymbol($name);
668
            if ($symbol) {
669
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
670
671
                if ($expr->expr instanceof Node\Expr\Variable) {
672
                    $rightVarName = $expr->expr->name;
673
674
                    $rightSymbol = $this->context->getSymbol($rightVarName);
675
                    if ($rightSymbol) {
676
                        $rightSymbol->incUse();
677
                        $symbol->setReferencedTo($rightSymbol);
678
                    } else {
679
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
680
                    }
681
                }
682
683
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
684
            } else {
685
                $symbol = new Variable(
686
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 662 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...
687
                    $compiledExpression->getValue(),
688
                    $compiledExpression->getType(),
689
                    $this->context->getCurrentBranch()
690
                );
691
                $this->context->addVariable($symbol);
692
            }
693
694
            $symbol->incSets();
695
            return $compiledExpression;
696
        }
697
698
        $this->context->debug('Unknown how to pass symbol by ref');
699
        return new CompiledExpression();
700
    }
701
702
    /**
703
     * @param Node\Expr\Variable $expr
704
     * @return CompiledExpression
705
     */
706
    protected function passExprVariable(Node\Expr\Variable $expr)
707
    {
708
        $variable = $this->context->getSymbol($expr->name);
709
        if ($variable) {
710
            $variable->incGets();
711
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
712
        }
713
714
        $this->context->notice(
715
            'undefined-variable',
716
            sprintf('You trying to use undefined variable $%s', $expr->name),
717
            $expr
718
        );
719
720
        return new CompiledExpression();
721
    }
722
723
    /**
724
     * Compile Array_ expression to CompiledExpression
725
     *
726
     * @param Node\Expr\Array_ $expr
727
     * @return CompiledExpression
728
     */
729 11
    protected function getArray(Node\Expr\Array_ $expr)
730
    {
731 11
        if ($expr->items === array()) {
732 8
            return new CompiledExpression(CompiledExpression::ARR, array());
733
        }
734
735 3
        $resultArray = array();
736
737 3
        foreach ($expr->items as $item) {
738 3
            $compiledValueResult = $this->compile($item->value);
739 3
            if ($item->key) {
740 1
                $compiledKeyResult = $this->compile($item->key);
741 1
                switch ($compiledKeyResult->getType()) {
742 1
                    case CompiledExpression::INTEGER:
743 1
                    case CompiledExpression::DOUBLE:
744 1
                    case CompiledExpression::BOOLEAN:
745 1
                    case CompiledExpression::NULL:
746 1
                    case CompiledExpression::STRING:
747 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
748 1
                        break;
749
                    default:
750
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
751
                        return new CompiledExpression(CompiledExpression::ARR);
752
                        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...
753 1
                }
754 1
            } else {
755 2
                $resultArray[] = $compiledValueResult->getValue();
756
            }
757 3
        }
758
759 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
760
    }
761
762
    /**
763
     * Convert const fetch expr to CompiledExpression
764
     *
765
     * @param Node\Expr\ConstFetch $expr
766
     * @return CompiledExpression
767
     */
768
    protected function constFetch(Node\Expr\ConstFetch $expr)
769
    {
770
        if ($expr->name instanceof Node\Name) {
771
            if ($expr->name->parts[0] === 'true') {
772
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
773
            }
774
775
            if ($expr->name->parts[0] === 'false') {
776
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
777
            }
778
        }
779
780
        /**
781
         * @todo Implement check
782
         */
783
784
        $expression = new Expression($this->context);
785
        $compiledExpr = $expression->compile($expr->name);
786
787
        return $compiledExpr;
788
    }
789
}
790