Completed
Push — master ( 196386...55f8b4 )
by Дмитрий
05:49
created

Expression::passPropertyFetch()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 39
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 39
ccs 0
cts 29
cp 0
rs 6.7272
cc 7
eloc 27
nc 8
nop 1
crap 56
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
            return new CompiledExpression();
256
        }
257
258
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
259
260
        $varTags = $phpdoc->getTagsByName('var');
261
        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...
262
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
263
            $varTag = current($varTags);
264
265
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
266
267
            try {
268
                $type = $typeResolver->resolve($varTag->getType());
269
            } catch (\InvalidArgumentException $e) {
270
                return new CompiledExpression();
271
            }
272
273
            if ($type) {
274
                switch (get_class($type)) {
275
                    case \phpDocumentor\Reflection\Types\Object_::class:
276
                        return new CompiledExpression(
277
                            CompiledExpression::OBJECT
278
                        );
279
                    case \phpDocumentor\Reflection\Types\Integer::class:
280
                        return new CompiledExpression(
281
                            CompiledExpression::INTEGER
282
                        );
283
                    case \phpDocumentor\Reflection\Types\String_::class:
284
                        return new CompiledExpression(
285
                            CompiledExpression::STRING
286
                        );
287
                    case \phpDocumentor\Reflection\Types\Float_::class:
288
                        return new CompiledExpression(
289
                            CompiledExpression::DOUBLE
290
                        );
291
                    case \phpDocumentor\Reflection\Types\Null_::class:
292
                        return new CompiledExpression(
293
                            CompiledExpression::NULL
294
                        );
295
                    case \phpDocumentor\Reflection\Types\Boolean::class:
296
                        return new CompiledExpression(
297
                            CompiledExpression::BOOLEAN
298
                        );
299
                }
300
            }
301
        }
302
303
        return new CompiledExpression();
304
    }
305
306
    /**
307
     * @param Node\Expr\Variable $expr
308
     * @return CompiledExpression
309
     */
310
    public function declareVariable(Node\Expr\Variable $expr)
311
    {
312
        $variable = $this->context->getSymbol($expr->name);
313
        if (!$variable) {
314
            $variable = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
315
            $this->context->addVariable($variable);
316
        }
317
318
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
319
    }
320
321
    /**
322
     * @param Node\Name\FullyQualified $expr
323
     * @return CompiledExpression
324
     */
325
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
326
    {
327
        $this->context->debug('Unimplemented FullyQualified', $expr);
328
        
329
        return new CompiledExpression;
330
    }
331
332
    /**
333
     * @param Node\Name $expr
334
     * @return CompiledExpression
335
     */
336
    public function getNodeName(Node\Name $expr)
337
    {
338
        if ($expr->toString() === 'null') {
339
            return new CompiledExpression(CompiledExpression::NULL);
340
        }
341
342
        if (in_array($expr, ['parent'], true)) {
343
            /** @var ClassDefinition $scope */
344
            $scope = $this->context->scope;
345
            assert($scope instanceof ClassDefinition);
346
347
            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...
348
                $definition = $scope->getExtendsClassDefinition();
349
                if ($definition) {
350
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
351
                }
352
            } else {
353
                $this->context->notice(
354
                    'no-parent',
355
                    'Cannot access parent:: when current class scope has no parent',
356
                    $expr
357
                );
358
            }
359
        }
360
361
        if (in_array($expr, ['self', 'static'], true)) {
362
            return CompiledExpression::fromZvalValue($this->context->scope);
363
        }
364
365
        if (defined($expr)) {
366
            return CompiledExpression::fromZvalValue(constant($expr));
367
        }
368
369
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
370
    }
371
372
    /**
373
     * (bool) {$expr}
374
     *
375
     * @param Node\Expr\Cast\Bool_ $expr
376
     * @return CompiledExpression
377
     */
378
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
379
    {
380
        $expression = new Expression($this->context);
381
        $compiledExpression = $expression->compile($expr->expr);
382
383
        switch ($compiledExpression->getType()) {
384
            case CompiledExpression::BOOLEAN:
385
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
386
                return $compiledExpression;
387
            case CompiledExpression::DOUBLE:
388
            case CompiledExpression::INTEGER:
389
            case CompiledExpression::NUMBER:
390
            case CompiledExpression::STRING:
391
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
392
        }
393
394
        return new CompiledExpression();
395
    }
396
397
    /**
398
     * (int) {$expr}
399
     *
400
     * @param Node\Expr\Cast\Int_ $expr
401
     * @return CompiledExpression
402
     */
403
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
404
    {
405
        $expression = new Expression($this->context);
406
        $compiledExpression = $expression->compile($expr->expr);
407
408
        switch ($compiledExpression->getType()) {
409
            case CompiledExpression::INTEGER:
410
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
411
                return $compiledExpression;
412
            case CompiledExpression::BOOLEAN:
413
            case CompiledExpression::DOUBLE:
414
            case CompiledExpression::NUMBER:
415
            case CompiledExpression::STRING:
416
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
417
        }
418
419
        return new CompiledExpression();
420
    }
421
422
    /**
423
     * (float) {$expr}
424
     *
425
     * @param Node\Expr\Cast\Double $expr
426
     * @return CompiledExpression
427
     */
428
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
429
    {
430
        $expression = new Expression($this->context);
431
        $compiledExpression = $expression->compile($expr->expr);
432
433
        switch ($compiledExpression->getType()) {
434
            case CompiledExpression::DOUBLE:
435
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
436
                return $compiledExpression;
437
            case CompiledExpression::BOOLEAN:
438
            case CompiledExpression::INTEGER:
439
            case CompiledExpression::NUMBER:
440
            case CompiledExpression::STRING:
441
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
442
        }
443
444
        return new CompiledExpression();
445
    }
446
447
    /**
448
     * (string) {$expr}
449
     *
450
     * @param Node\Expr\Cast\String_ $expr
451
     * @return CompiledExpression
452
     */
453
    protected function passCastString(Node\Expr\Cast\String_ $expr)
454
    {
455
        $expression = new Expression($this->context);
456
        $compiledExpression = $expression->compile($expr->expr);
457
458
        switch ($compiledExpression->getType()) {
459
            case CompiledExpression::STRING:
460
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
461
                return $compiledExpression;
462
            case CompiledExpression::BOOLEAN:
463
            case CompiledExpression::INTEGER:
464
            case CompiledExpression::NUMBER:
465
            case CompiledExpression::DOUBLE:
466
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
467
        }
468
469
        return new CompiledExpression();
470
    }
471
472
    /**
473
     * (unset) {$expr}
474
     *
475
     * @param Node\Expr\Cast\Unset_ $expr
476
     * @return CompiledExpression
477
     */
478
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
479
    {
480
        $expression = new Expression($this->context);
481
        $compiledExpression = $expression->compile($expr->expr);
482
483
        switch ($compiledExpression->getType()) {
484
            case CompiledExpression::NULL:
485
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
486
                return $compiledExpression;
487
        }
488
489
        return new CompiledExpression(CompiledExpression::NULL, null);
490
    }
491
492
    /**
493
     * @param Node\Expr\PropertyFetch $expr
494
     * @return CompiledExpression
495
     */
496
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
497
    {
498
        $propertNameCE = $this->compile($expr->name);
499
500
        $scopeExpression = $this->compile($expr->var);
501
        if ($scopeExpression->isObject()) {
502
            $scopeExpressionValue = $scopeExpression->getValue();
503
            if ($scopeExpressionValue instanceof ClassDefinition) {
504
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
505
                if ($propertyName) {
506
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
507
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
508
                        return $this->compile($property);
509
                    } else {
510
                        $this->context->notice(
511
                            'undefined-property',
512
                            sprintf(
513
                                'Property %s does not exist in %s scope',
514
                                $propertyName,
515
                                $scopeExpressionValue->getName()
516
                            ),
517
                            $expr
518
                        );
519
                    }
520
                }
521
            }
522
        } elseif (!$scopeExpression->canBeObject()) {
523
            return new CompiledExpression(CompiledExpression::UNKNOWN);
524
        }
525
526
        $this->context->notice(
527
            'property-fetch-on-non-object',
528
            "It's not possible to fetch property on not object",
529
            $expr,
530
            Check::CHECK_BETA
531
        );
532
533
        return new CompiledExpression(CompiledExpression::UNKNOWN);
534
    }
535
536
    /**
537
     * @param Node\Expr\ClassConstFetch $expr
538
     * @return CompiledExpression
539
     */
540
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
541
    {
542
        $leftCE = $this->compile($expr->class);
543
        if ($leftCE->isObject()) {
544
            $leftCEValue = $leftCE->getValue();
545
            if ($leftCEValue instanceof ClassDefinition) {
546
                if (!$leftCEValue->hasConst($expr->name)) {
547
                    $this->context->notice(
548
                        'undefined-const',
549
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
550
                        $expr
551
                    );
552
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
553
                }
554
555
                return new CompiledExpression();
556
            }
557
        }
558
559
        $this->context->debug('Unknown const fetch', $expr);
560
        return new CompiledExpression();
561
    }
562
563
    /**
564
     * @param Node\Expr\Assign $expr
565
     * @return CompiledExpression
566
     */
567
    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...
568
    {
569
        $expression = new Expression($this->context);
570
        $compiledExpression = $expression->compile($expr->expr);
571
572
        if ($expr->var instanceof Node\Expr\List_) {
573
            $isCorrectType = false;
574
575
            switch ($compiledExpression->getType()) {
576
                case CompiledExpression::ARR:
577
                    $isCorrectType = true;
578
                    break;
579
            }
580
581
            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...
582
                foreach ($expr->var->vars as $key => $var) {
583
                    if ($var instanceof Node\Expr\Variable) {
584
                        if (!isset($expr->var->name)) {
585
                            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...
586
                        }
587
                        $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...
588
589
                        $symbol = $this->context->getSymbol($name);
590
                        if (!$symbol) {
591
                            $symbol = new Variable(
592
                                $name,
593
                                null,
594
                                CompiledExpression::UNKNOWN,
595
                                $this->context->getCurrentBranch()
596
                            );
597
                            $this->context->addVariable($symbol);
598
                        }
599
600
                        if (!$isCorrectType) {
601
                            $symbol->modify(CompiledExpression::NULL, null);
602
                        }
603
604
                        $symbol->incSets();
605
                    }
606
                }
607
            }
608
609
            return new CompiledExpression();
610
        }
611
612
        if ($expr->var instanceof Node\Expr\Variable) {
613
            $name = $expr->var->name;
614
615
            $symbol = $this->context->getSymbol($name);
616
            if ($symbol) {
617
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
618
            } else {
619
                $symbol = new Variable(
620
                    $name,
621
                    $compiledExpression->getValue(),
622
                    $compiledExpression->getType(),
623
                    $this->context->getCurrentBranch()
624
                );
625
                $this->context->addVariable($symbol);
626
            }
627
628
            $symbol->incSets();
629
            return $compiledExpression;
630
        }
631
632
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
633
            $compiledExpression = $this->compile($expr->var->var);
634
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
635
                $objectDefinition = $compiledExpression->getValue();
636
                if ($objectDefinition instanceof ClassDefinition) {
637
                    if (is_string($expr->var->name)) {
638
                        if ($objectDefinition->hasProperty($expr->var->name)) {
639
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
640
                        }
641
                    }
642
                }
643
            }
644
        }
645
646
        $this->context->debug('Unknown how to pass symbol');
647
        return new CompiledExpression();
648
    }
649
650
    /**
651
     * @param Node\Expr\AssignRef $expr
652
     * @return CompiledExpression
653
     */
654
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
655
    {
656
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
657
            return new CompiledExpression();
658
        }
659
660
        if ($expr->var instanceof Node\Expr\Variable) {
661
            $name = $expr->var->name;
662
663
            $expression = new Expression($this->context);
664
            $compiledExpression = $expression->compile($expr->expr);
665
666
            $symbol = $this->context->getSymbol($name);
667
            if ($symbol) {
668
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
669
670
                if ($expr->expr instanceof Node\Expr\Variable) {
671
                    $rightVarName = $expr->expr->name;
672
673
                    $rightSymbol = $this->context->getSymbol($rightVarName);
674
                    if ($rightSymbol) {
675
                        $rightSymbol->incUse();
676
                        $symbol->setReferencedTo($rightSymbol);
677
                    } else {
678
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
679
                    }
680
                }
681
682
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
683
            } else {
684
                $symbol = new Variable(
685
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 661 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...
686
                    $compiledExpression->getValue(),
687
                    $compiledExpression->getType(),
688
                    $this->context->getCurrentBranch()
689
                );
690
                $this->context->addVariable($symbol);
691
            }
692
693
            $symbol->incSets();
694
            return $compiledExpression;
695
        }
696
697
        $this->context->debug('Unknown how to pass symbol by ref');
698
        return new CompiledExpression();
699
    }
700
701
    /**
702
     * @param Node\Expr\Variable $expr
703
     * @return CompiledExpression
704
     */
705
    protected function passExprVariable(Node\Expr\Variable $expr)
706
    {
707
        $variable = $this->context->getSymbol($expr->name);
708
        if ($variable) {
709
            $variable->incGets();
710
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
711
        }
712
713
        $this->context->notice(
714
            'undefined-variable',
715
            sprintf('You trying to use undefined variable $%s', $expr->name),
716
            $expr
717
        );
718
719
        return new CompiledExpression();
720
    }
721
722
    /**
723
     * Compile Array_ expression to CompiledExpression
724
     *
725
     * @param Node\Expr\Array_ $expr
726
     * @return CompiledExpression
727
     */
728 11
    protected function getArray(Node\Expr\Array_ $expr)
729
    {
730 11
        if ($expr->items === array()) {
731 8
            return new CompiledExpression(CompiledExpression::ARR, array());
732
        }
733
734 3
        $resultArray = array();
735
736 3
        foreach ($expr->items as $item) {
737 3
            $compiledValueResult = $this->compile($item->value);
738 3
            if ($item->key) {
739 1
                $compiledKeyResult = $this->compile($item->key);
740 1
                switch ($compiledKeyResult->getType()) {
741 1
                    case CompiledExpression::INTEGER:
742 1
                    case CompiledExpression::DOUBLE:
743 1
                    case CompiledExpression::BOOLEAN:
744 1
                    case CompiledExpression::NULL:
745 1
                    case CompiledExpression::STRING:
746 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
747 1
                        break;
748
                    default:
749
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
750
                        return new CompiledExpression(CompiledExpression::ARR);
751
                        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...
752 1
                }
753 1
            } else {
754 2
                $resultArray[] = $compiledValueResult->getValue();
755
            }
756 3
        }
757
758 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
759
    }
760
761
    /**
762
     * Convert const fetch expr to CompiledExpression
763
     *
764
     * @param Node\Expr\ConstFetch $expr
765
     * @return CompiledExpression
766
     */
767
    protected function constFetch(Node\Expr\ConstFetch $expr)
768
    {
769
        if ($expr->name instanceof Node\Name) {
770
            if ($expr->name->parts[0] === 'true') {
771
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
772
            }
773
774
            if ($expr->name->parts[0] === 'false') {
775
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
776
            }
777
        }
778
779
        /**
780
         * @todo Implement check
781
         */
782
783
        $expression = new Expression($this->context);
784
        $compiledExpr = $expression->compile($expr->name);
785
786
        return $compiledExpr;
787
    }
788
}
789