Completed
Pull Request — master (#122)
by Enrico
03:22 queued 13s
created

Expression::passCastString()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 6
nop 1
dl 0
loc 17
ccs 0
cts 13
cp 0
crap 42
rs 8.8571
c 0
b 0
f 0
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\Compiler\Event\ExpressionBeforeCompile;
12
use PHPSA\Context;
13
use PhpParser\Node;
14
use PHPSA\Definition\ClassDefinition;
15
use PHPSA\Exception\RuntimeException;
16
use PHPSA\Variable;
17
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
18
use Webiny\Component\EventManager\EventManager;
19
20
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 173 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...
21
{
22
    /**
23
     * @var Context
24
     */
25
    protected $context;
26
27
    /**
28
     * @var EventManager
29
     */
30
    protected $eventManager;
31
32
    /**
33
     * @param Context $context
34
     */
35 428
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 428
        $this->context = $context;
38 428
        $this->eventManager = $eventManager;
39 428
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 413
    protected function factory($expr)
46
    {
47 413
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 413
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 413
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 405
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 405
            case Node\Expr\New_::class:
61 1
                return new Expression\Operators\NewOp();
62 404
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 404
            case Node\Expr\AssignOp\Pow::class:
68
                return new Expression\AssignOp\Pow();
69 404
            case Node\Expr\AssignOp\Plus::class:
70
                return new Expression\AssignOp\Plus();
71 404
            case Node\Expr\AssignOp\Minus::class:
72
                return new Expression\AssignOp\Minus();
73 404
            case Node\Expr\AssignOp\Mul::class:
74
                return new Expression\AssignOp\Mul();
75 404
            case Node\Expr\AssignOp\Div::class:
76
                return new Expression\AssignOp\Div();
77 404
            case Node\Expr\AssignOp\Mod::class:
78
                return new Expression\AssignOp\Mod();
79 404
            case Node\Expr\AssignOp\BitwiseOr::class:
80
                return new Expression\AssignOp\BitwiseOr();
81 404
            case Node\Expr\AssignOp\BitwiseAnd::class:
82
                return new Expression\AssignOp\BitwiseAnd();
83 404
            case Node\Expr\AssignOp\BitwiseXor::class:
84
                return new Expression\AssignOp\BitwiseXor();
85 404
            case Node\Expr\AssignOp\Concat::class:
86
                return new Expression\AssignOp\Concat();
87 404
            case Node\Expr\AssignOp\ShiftLeft::class:
88
                return new Expression\AssignOp\ShiftLeft();
89 404
            case Node\Expr\AssignOp\ShiftRight::class:
90
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 404
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 376
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 375
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 361
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 327
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105
            /**
106
             * @link http://php.net/manual/en/language.operators.increment.php
107
             */
108 310
            case Node\Expr\PostInc::class:
109 8
                return new Expression\Operators\PostInc();
110 302
            case Node\Expr\PostDec::class:
111 8
                return new Expression\Operators\PostDec();
112 294
            case Node\Expr\PreInc::class:
113 8
                return new Expression\Operators\PreInc();
114 286
            case Node\Expr\PreDec::class:
115 8
                return new Expression\Operators\PreDec();
116
            /**
117
             * Arithmetical
118
             */
119 278
            case Node\Expr\BinaryOp\Div::class:
120 37
                return new Expression\Operators\Arithmetical\Div();
121 241
            case Node\Expr\BinaryOp\Plus::class:
122 45
                return new Expression\Operators\Arithmetical\Plus();
123 196
            case Node\Expr\BinaryOp\Minus::class:
124 18
                return new Expression\Operators\Arithmetical\Minus();
125 178
            case Node\Expr\BinaryOp\Mul::class:
126 35
                return new Expression\Operators\Arithmetical\Mul();
127 143
            case Node\Expr\BinaryOp\Mod::class:
128 35
                return new Expression\Operators\Arithmetical\Mod();
129 108
            case Node\Expr\BinaryOp\Pow::class:
130 21
                return new Expression\Operators\Arithmetical\Pow();
131
132
            /**
133
             * Bitwise
134
             * @link http://php.net/manual/ru/language.operators.bitwise.php
135
             */
136 87
            case Node\Expr\BinaryOp\BitwiseOr::class:
137
                return new Expression\Operators\Bitwise\BitwiseOr();
138 87
            case Node\Expr\BinaryOp\BitwiseXor::class:
139
                return new Expression\Operators\Bitwise\BitwiseXor();
140 87
            case Node\Expr\BinaryOp\BitwiseAnd::class:
141
                return new Expression\Operators\Bitwise\BitwiseAnd();
142 87
            case Node\Expr\BinaryOp\ShiftRight::class:
143
                return new Expression\Operators\Bitwise\ShiftRight();
144 87
            case Node\Expr\BinaryOp\ShiftLeft::class:
145
                return new Expression\Operators\Bitwise\ShiftLeft();
146 87
            case Node\Expr\BitwiseNot::class:
147
                return new Expression\Operators\Bitwise\BitwiseNot();
148
            /**
149
             * Logical
150
             */
151 87
            case Node\Expr\BinaryOp\BooleanOr::class:
152 10
                return new Expression\Operators\Logical\BooleanOr();
153 77
            case Node\Expr\BinaryOp\BooleanAnd::class:
154 5
                return new Expression\Operators\Logical\BooleanAnd();
155 72
            case Node\Expr\BooleanNot::class:
156 5
                return new Expression\Operators\Logical\BooleanNot();
157
            /**
158
             * Comparison
159
             */
160 67
            case Node\Expr\BinaryOp\Greater::class:
161 12
                return new Expression\Operators\Comparison\Greater();
162 55
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
163 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
164 43
            case Node\Expr\BinaryOp\Smaller::class:
165 12
                return new Expression\Operators\Comparison\Smaller();
166 31
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
167 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
168
            /**
169
             * Other
170
             */
171 19
            case Node\Expr\Closure::class:
172
                return new Expression\Closure();
173 19
            case Node\Expr\UnaryMinus::class:
174 9
                return new Expression\Operators\UnaryMinus();
175 10
            case Node\Expr\UnaryPlus::class:
176 9
                return new Expression\Operators\UnaryPlus();
177 1
        }
178
179 1
        return false;
180
    }
181
182
    /**
183
     * @param object|string $expr
184
     * @return CompiledExpression
185
     */
186 428
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 704 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...
187
    {
188 428
        if (is_string($expr)) {
189 10
            return new CompiledExpression(CompiledExpression::STRING, $expr);
190
        }
191
192 428
        if (is_null($expr)) {
193 1
            return new CompiledExpression(CompiledExpression::NULL);
194
        }
195
196 428
        if (!is_object($expr)) {
197
            throw new InvalidArgumentException('$expr must be string/object/null');
198
        }
199
200 428
        $this->eventManager->fire(
201 428
            ExpressionBeforeCompile::EVENT_NAME,
202 428
            new ExpressionBeforeCompile(
203 428
                $expr,
204 428
                $this->context
205 428
            )
206 428
        );
207
208 428
        $className = get_class($expr);
209
        switch ($className) {
210 428
            case Node\Arg::class:
211
                /**
212
                 * @todo Better compile
213
                 */
214 2
                return $this->compile($expr->value);
215 428
            case Node\Expr\PropertyFetch::class:
216
                return $this->passPropertyFetch($expr);
217 428
            case Node\Stmt\Property::class:
218
                return $this->passProperty($expr);
219 428
            case Node\Expr\ClassConstFetch::class:
220
                return $this->passConstFetch($expr);
221 428
            case Node\Expr\Assign::class:
222 9
                return $this->passSymbol($expr);
223 428
            case Node\Expr\AssignRef::class:
224 1
                return $this->passSymbolByRef($expr);
225 428
            case Node\Expr\Variable::class:
226 5
                return $this->passExprVariable($expr);
227
            /**
228
             * Cast operators
229
             */
230 428
            case Node\Expr\Cast\Bool_::class:
231
                return $this->passCastBoolean($expr);
232 428
            case Node\Expr\Cast\Int_::class:
233
                return $this->passCastInt($expr);
234 428
            case Node\Expr\Cast\Double::class:
235
                return $this->passCastFloat($expr);
236 428
            case Node\Expr\Cast\String_::class:
237
                return $this->passCastString($expr);
238 428
            case Node\Expr\Cast\Unset_::class:
239
                return $this->passCastUnset($expr);
240
            /**
241
             * Expressions
242
             */
243 428
            case Node\Expr\Array_::class:
244 15
                return $this->getArray($expr);
245 427
            case Node\Expr\ConstFetch::class:
246 4
                return $this->constFetch($expr);
247 427
            case Node\Name::class:
248 10
                return $this->getNodeName($expr);
249 427
            case Node\Name\FullyQualified::class:
250
                return $this->getFullyQualifiedNodeName($expr);
251
            /**
252
             * Simple Scalar(s)
253
             */
254 427
            case \PHPSA\Node\Scalar\Nil::class:
255 8
                return new CompiledExpression(CompiledExpression::NULL);
256 426
            case Node\Scalar\LNumber::class:
257 284
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
258 420
            case Node\Scalar\DNumber::class:
259 138
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
260 419
            case Node\Scalar\String_::class:
261 16
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
262 415
            case \PHPSA\Node\Scalar\Boolean::class:
263 71
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
264 413
            case \PHPSA\Node\Scalar\Fake::class:
265 31
                return new CompiledExpression($expr->type, $expr->value);
266
        }
267
268 413
        $expressionCompiler = $this->factory($expr);
269 413
        if (!$expressionCompiler) {
270 1
            $this->context->debug("Expression compiler is not implemented for {$className}");
271 1
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
272
        }
273
274 413
        $result = $expressionCompiler->pass($expr, $this->context);
275 413
        if (!$result instanceof CompiledExpression) {
276
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
277
        }
278
279 413
        return $result;
280
    }
281
282
    /**
283
     * @todo Implement
284
     *
285
     * @param Node\Stmt\Property $st
286
     * @return CompiledExpression
287
     */
288
    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...
289
    {
290
        $docBlock = $st->getDocComment();
291
        if (!$docBlock) {
292
            $this->context->notice(
293
                'missing-docblock',
294
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
295
                $st
296
            );
297
298
            return new CompiledExpression();
299
        }
300
301
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
302
303
        $varTags = $phpdoc->getTagsByName('var');
304
        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...
305
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
306
            $varTag = current($varTags);
307
308
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
309
310
            try {
311
                $type = $typeResolver->resolve($varTag->getType());
312
            } catch (\InvalidArgumentException $e) {
313
                return new CompiledExpression();
314
            }
315
316
            if ($type) {
317
                switch (get_class($type)) {
318
                    case \phpDocumentor\Reflection\Types\Object_::class:
319
                        return new CompiledExpression(
320
                            CompiledExpression::OBJECT
321
                        );
322
                    case \phpDocumentor\Reflection\Types\Integer::class:
323
                        return new CompiledExpression(
324
                            CompiledExpression::INTEGER
325
                        );
326
                    case \phpDocumentor\Reflection\Types\String_::class:
327
                        return new CompiledExpression(
328
                            CompiledExpression::STRING
329
                        );
330
                    case \phpDocumentor\Reflection\Types\Float_::class:
331
                        return new CompiledExpression(
332
                            CompiledExpression::DOUBLE
333
                        );
334
                    case \phpDocumentor\Reflection\Types\Null_::class:
335
                        return new CompiledExpression(
336
                            CompiledExpression::NULL
337
                        );
338
                    case \phpDocumentor\Reflection\Types\Boolean::class:
339
                        return new CompiledExpression(
340
                            CompiledExpression::BOOLEAN
341
                        );
342
                }
343
            }
344
        }
345
346
        return new CompiledExpression();
347
    }
348
349
    /**
350
     * @param Node\Expr\Variable $expr
351
     * @return CompiledExpression
352
     */
353 1
    public function declareVariable(Node\Expr\Variable $expr)
354
    {
355 1
        $variable = $this->context->getSymbol($expr->name);
356 1
        if (!$variable) {
357
            $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...
358
            $this->context->addVariable($variable);
359
        }
360
361 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
362
    }
363
364
    /**
365
     * @param Node\Name\FullyQualified $expr
366
     * @return CompiledExpression
367
     */
368
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
369
    {
370
        $this->context->debug('Unimplemented FullyQualified', $expr);
371
372
        return new CompiledExpression;
373
    }
374
375
    /**
376
     * @param Node\Name $expr
377
     * @return CompiledExpression
378
     */
379 10
    public function getNodeName(Node\Name $expr)
380
    {
381 10
        $nodeString = $expr->toString();
382 10
        if ($nodeString === 'null') {
383
            return new CompiledExpression(CompiledExpression::NULL);
384
        }
385
386 10
        if (in_array($nodeString, ['parent'], true)) {
387
            /** @var ClassDefinition $scope */
388
            $scope = $this->context->scope;
389
            assert($scope instanceof ClassDefinition);
390
391
            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...
392
                $definition = $scope->getExtendsClassDefinition();
393
                if ($definition) {
394
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
395
                }
396
            } else {
397
                $this->context->notice(
398
                    'no-parent',
399
                    'Cannot access parent:: when current class scope has no parent',
400
                    $expr
401
                );
402
            }
403
        }
404
405 10
        if (in_array($nodeString, ['self', 'static'], true)) {
406
            return CompiledExpression::fromZvalValue($this->context->scope);
407
        }
408
409 10
        if (defined($nodeString)) {
410 1
            return CompiledExpression::fromZvalValue(constant($expr));
411
        }
412
413 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
414
    }
415
416
    /**
417
     * (bool) {$expr}
418
     *
419
     * @param Node\Expr\Cast\Bool_ $expr
420
     * @return CompiledExpression
421
     */
422
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
423
    {
424
        $compiledExpression = $this->compile($expr->expr);
425
426
        switch ($compiledExpression->getType()) {
427
            case CompiledExpression::BOOLEAN:
428
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
429
                return $compiledExpression;
430
            case CompiledExpression::DOUBLE:
431
            case CompiledExpression::INTEGER:
432
            case CompiledExpression::NUMBER:
433
            case CompiledExpression::STRING:
434
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
435
        }
436
437
        return new CompiledExpression();
438
    }
439
440
    /**
441
     * (int) {$expr}
442
     *
443
     * @param Node\Expr\Cast\Int_ $expr
444
     * @return CompiledExpression
445
     */
446
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
447
    {
448
        $compiledExpression = $this->compile($expr->expr);
449
450
        switch ($compiledExpression->getType()) {
451
            case CompiledExpression::INTEGER:
452
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
453
                return $compiledExpression;
454
            case CompiledExpression::BOOLEAN:
455
            case CompiledExpression::DOUBLE:
456
            case CompiledExpression::NUMBER:
457
            case CompiledExpression::STRING:
458
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
459
        }
460
461
        return new CompiledExpression();
462
    }
463
464
    /**
465
     * (float) {$expr}
466
     *
467
     * @param Node\Expr\Cast\Double $expr
468
     * @return CompiledExpression
469
     */
470
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
471
    {
472
        $compiledExpression = $this->compile($expr->expr);
473
474
        switch ($compiledExpression->getType()) {
475
            case CompiledExpression::DOUBLE:
476
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
477
                return $compiledExpression;
478
            case CompiledExpression::BOOLEAN:
479
            case CompiledExpression::INTEGER:
480
            case CompiledExpression::NUMBER:
481
            case CompiledExpression::STRING:
482
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
483
        }
484
485
        return new CompiledExpression();
486
    }
487
488
    /**
489
     * (string) {$expr}
490
     *
491
     * @param Node\Expr\Cast\String_ $expr
492
     * @return CompiledExpression
493
     */
494
    protected function passCastString(Node\Expr\Cast\String_ $expr)
495
    {
496
        $compiledExpression = $this->compile($expr->expr);
497
498
        switch ($compiledExpression->getType()) {
499
            case CompiledExpression::STRING:
500
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
501
                return $compiledExpression;
502
            case CompiledExpression::BOOLEAN:
503
            case CompiledExpression::INTEGER:
504
            case CompiledExpression::NUMBER:
505
            case CompiledExpression::DOUBLE:
506
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
507
        }
508
509
        return new CompiledExpression();
510
    }
511
512
    /**
513
     * (unset) {$expr}
514
     *
515
     * @param Node\Expr\Cast\Unset_ $expr
516
     * @return CompiledExpression
517
     */
518
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
519
    {
520
        $compiledExpression = $this->compile($expr->expr);
521
522
        switch ($compiledExpression->getType()) {
523
            case CompiledExpression::NULL:
524
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
525
                return $compiledExpression;
526
        }
527
528
        return new CompiledExpression(CompiledExpression::NULL, null);
529
    }
530
531
    /**
532
     * @param Node\Expr\PropertyFetch $expr
533
     * @return CompiledExpression
534
     */
535
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
536
    {
537
        $propertNameCE = $this->compile($expr->name);
538
539
        $scopeExpression = $this->compile($expr->var);
540
        if ($scopeExpression->isObject()) {
541
            $scopeExpressionValue = $scopeExpression->getValue();
542
            if ($scopeExpressionValue instanceof ClassDefinition) {
543
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
544
                if ($propertyName) {
545
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
546
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
547
                        return $this->compile($property);
548
                    } else {
549
                        $this->context->notice(
550
                            'undefined-property',
551
                            sprintf(
552
                                'Property %s does not exist in %s scope',
553
                                $propertyName,
554
                                $scopeExpressionValue->getName()
555
                            ),
556
                            $expr
557
                        );
558
                    }
559
                }
560
            }
561
562
            return new CompiledExpression(CompiledExpression::UNKNOWN);
563
        } elseif (!$scopeExpression->canBeObject()) {
564
            return new CompiledExpression(CompiledExpression::UNKNOWN);
565
        }
566
567
        $this->context->notice(
568
            'property-fetch-on-non-object',
569
            "It's not possible to fetch property on not object",
570
            $expr,
571
            Check::CHECK_BETA
572
        );
573
574
        return new CompiledExpression(CompiledExpression::UNKNOWN);
575
    }
576
577
    /**
578
     * @param Node\Expr\ClassConstFetch $expr
579
     * @return CompiledExpression
580
     */
581
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
582
    {
583
        $leftCE = $this->compile($expr->class);
584
        if ($leftCE->isObject()) {
585
            $leftCEValue = $leftCE->getValue();
586
            if ($leftCEValue instanceof ClassDefinition) {
587
                if (!$leftCEValue->hasConst($expr->name, true)) {
588
                    $this->context->notice(
589
                        'undefined-const',
590
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
591
                        $expr
592
                    );
593
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
594
                }
595
596
                return new CompiledExpression();
597
            }
598
        }
599
600
        $this->context->debug('Unknown const fetch', $expr);
601
        return new CompiledExpression();
602
    }
603
604
    /**
605
     * @param Node\Expr\Assign $expr
606
     * @return CompiledExpression
607
     */
608 9
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 360 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...
609
    {
610 9
        $compiledExpression = $this->compile($expr->expr);
611
612 9
        if ($expr->var instanceof Node\Expr\List_) {
613
            $isCorrectType = false;
614
615
            switch ($compiledExpression->getType()) {
616
                case CompiledExpression::ARR:
617
                    $isCorrectType = true;
618
                    break;
619
            }
620
621
            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...
622
                foreach ($expr->var->vars as $key => $var) {
623
                    if ($var instanceof Node\Expr\Variable) {
624
                        if (!isset($expr->var->name)) {
625
                            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...
626
                        }
627
                        $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...
628
629
                        $symbol = $this->context->getSymbol($name);
630
                        if (!$symbol) {
631
                            $symbol = new Variable(
632
                                $name,
633
                                null,
634
                                CompiledExpression::UNKNOWN,
635
                                $this->context->getCurrentBranch()
636
                            );
637
                            $this->context->addVariable($symbol);
638
                        }
639
640
                        if (!$isCorrectType) {
641
                            $symbol->modify(CompiledExpression::NULL, null);
642
                        }
643
644
                        $symbol->incSets();
645
                    }
646
                }
647
            }
648
649
            return new CompiledExpression();
650
        }
651
652
653 9
        if ($expr->var instanceof Node\Expr\Variable) {
654 9
            $compiledExpressionName = $this->compile($expr->var->name);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $compiledExpressionName exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
655 9
            switch ($compiledExpressionName->getType()) {
656 9
                case CompiledExpression::STRING:
657 9
                    break;
658
                default:
659
                    $this->context->debug('Unexpected type of Variable name after compile');
660
                    return new CompiledExpression();
661 9
            }
662
663 9
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
664 9
            if ($symbol) {
665 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
666 2
                $this->context->modifyReferencedVariables(
667 2
                    $symbol,
668 2
                    $compiledExpression->getType(),
669 2
                    $compiledExpression->getValue()
670 2
                );
671 2
            } else {
672 9
                $symbol = new Variable(
673 9
                    $compiledExpressionName->getValue(),
674 9
                    $compiledExpression->getValue(),
675 9
                    $compiledExpression->getType(),
676 9
                    $this->context->getCurrentBranch()
677 9
                );
678 9
                $this->context->addVariable($symbol);
679
            }
680
681 9
            $symbol->incSets();
682 9
            return $compiledExpression;
683
        }
684
685
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
686
            $compiledExpression = $this->compile($expr->var->var);
687
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
688
                $objectDefinition = $compiledExpression->getValue();
689
                if ($objectDefinition instanceof ClassDefinition) {
690
                    if (is_string($expr->var->name)) {
691
                        if ($objectDefinition->hasProperty($expr->var->name)) {
692
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
693
                        }
694
                    }
695
                }
696
            }
697
        }
698
699
        $this->context->debug('Unknown how to pass symbol');
700
        return new CompiledExpression();
701
    }
702
703
    /**
704
     * @param Node\Expr\AssignRef $expr
705
     * @return CompiledExpression
706
     */
707 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
708
    {
709 1
        if ($expr->var instanceof Node\Expr\Variable) {
710 1
            $name = $expr->var->name;
711
712 1
            $compiledExpression = $this->compile($expr->expr);
713
714 1
            $symbol = $this->context->getSymbol($name);
715 1
            if ($symbol) {
716
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
717
            } else {
718 1
                $symbol = new Variable(
719 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 710 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...
720 1
                    $compiledExpression->getValue(),
721 1
                    $compiledExpression->getType(),
722 1
                    $this->context->getCurrentBranch()
723 1
                );
724 1
                $this->context->addVariable($symbol);
725
            }
726
727 1
            if ($expr->expr instanceof Node\Expr\Variable) {
728 1
                $rightVarName = $expr->expr->name;
729
730 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
731 1
                if ($rightSymbol) {
732 1
                    $rightSymbol->incUse();
733 1
                    $symbol->setReferencedTo($rightSymbol);
734 1
                } else {
735
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
736
                }
737 1
            }
738
739 1
            $symbol->incSets();
740 1
            return $compiledExpression;
741
        }
742
743
        $this->context->debug('Unknown how to pass symbol by ref');
744
        return new CompiledExpression();
745
    }
746
747
    /**
748
     * @param Node\Expr\Variable $expr
749
     * @return CompiledExpression
750
     */
751 5
    protected function passExprVariable(Node\Expr\Variable $expr)
752
    {
753 5
        $variable = $this->context->getSymbol($expr->name);
754 5
        if ($variable) {
755 5
            $variable->incGets();
756 5
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
757
        }
758
759
        $this->context->notice(
760
            'undefined-variable',
761
            sprintf('You trying to use undefined variable $%s', $expr->name),
762
            $expr
763
        );
764
765
        return new CompiledExpression();
766
    }
767
768
    /**
769
     * Compile Array_ expression to CompiledExpression
770
     *
771
     * @param Node\Expr\Array_ $expr
772
     * @return CompiledExpression
773
     */
774 15
    protected function getArray(Node\Expr\Array_ $expr)
775
    {
776 15
        if ($expr->items === []) {
777 8
            return new CompiledExpression(CompiledExpression::ARR, []);
778
        }
779
780 7
        $resultArray = [];
781
782 7
        foreach ($expr->items as $item) {
783 7
            $compiledValueResult = $this->compile($item->value);
784 7
            if ($item->key) {
785 1
                $compiledKeyResult = $this->compile($item->key);
786 1
                switch ($compiledKeyResult->getType()) {
787 1
                    case CompiledExpression::INTEGER:
788 1
                    case CompiledExpression::DOUBLE:
789 1
                    case CompiledExpression::BOOLEAN:
790 1
                    case CompiledExpression::NULL:
791 1
                    case CompiledExpression::STRING:
792 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
793 1
                        break;
794
                    default:
795
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
796
                        return new CompiledExpression(CompiledExpression::ARR);
797
                        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...
798 1
                }
799 1
            } else {
800 6
                $resultArray[] = $compiledValueResult->getValue();
801
            }
802 7
        }
803
804 7
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
805
    }
806
807
    /**
808
     * Convert const fetch expr to CompiledExpression
809
     *
810
     * @param Node\Expr\ConstFetch $expr
811
     * @return CompiledExpression
812
     */
813 4
    protected function constFetch(Node\Expr\ConstFetch $expr)
814
    {
815 4
        if ($expr->name instanceof Node\Name) {
816 4
            if ($expr->name->parts[0] === 'true') {
817 3
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
818
            }
819
820 1
            if ($expr->name->parts[0] === 'false') {
821
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
822
            }
823 1
        }
824
825
        /**
826
         * @todo Implement check
827
         */
828 1
        return $this->compile($expr->name);
829
    }
830
}
831