Expression::compile()   B
last analyzed

Complexity

Conditions 10
Paths 10

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
nc 10
nop 1
dl 0
loc 57
ccs 0
cts 30
cp 0
crap 110
rs 7.0715
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 phpDocumentor\Reflection\DocBlockFactory;
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 104 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
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37
        $this->context = $context;
38
        $this->eventManager = $eventManager;
39
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45
    protected function factory($expr)
46
    {
47
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53
            case Node\Expr\FuncCall::class:
54
                return new Expression\FunctionCall();
55
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60
            case Node\Expr\New_::class:
61
                return new Expression\Operators\NewOp();
62
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67
            case Node\Expr\AssignOp\Pow::class:
68
                return new Expression\AssignOp\Pow();
69
            case Node\Expr\AssignOp\Plus::class:
70
                return new Expression\AssignOp\Plus();
71
            case Node\Expr\AssignOp\Minus::class:
72
                return new Expression\AssignOp\Minus();
73
            case Node\Expr\AssignOp\Mul::class:
74
                return new Expression\AssignOp\Mul();
75
            case Node\Expr\AssignOp\Div::class:
76
                return new Expression\AssignOp\Div();
77
            case Node\Expr\AssignOp\Mod::class:
78
                return new Expression\AssignOp\Mod();
79
            case Node\Expr\AssignOp\BitwiseOr::class:
80
                return new Expression\AssignOp\BitwiseOr();
81
            case Node\Expr\AssignOp\BitwiseAnd::class:
82
                return new Expression\AssignOp\BitwiseAnd();
83
            case Node\Expr\AssignOp\BitwiseXor::class:
84
                return new Expression\AssignOp\BitwiseXor();
85
            case Node\Expr\AssignOp\Concat::class:
86
                return new Expression\AssignOp\Concat();
87
            case Node\Expr\AssignOp\ShiftLeft::class:
88
                return new Expression\AssignOp\ShiftLeft();
89
            case Node\Expr\AssignOp\ShiftRight::class:
90
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95
            case Node\Expr\BinaryOp\Identical::class:
96
                return new Expression\BinaryOp\Identical();
97
            case Node\Expr\BinaryOp\Concat::class:
98
                return new Expression\Operators\Concat();
99
            case Node\Expr\BinaryOp\NotIdentical::class:
100
                return new Expression\BinaryOp\NotIdentical();
101
            case Node\Expr\BinaryOp\Equal::class:
102
                return new Expression\BinaryOp\Equal();
103
            case Node\Expr\BinaryOp\NotEqual::class:
104
                return new Expression\BinaryOp\NotEqual();
105
            case Node\Expr\BinaryOp\Spaceship::class:
106
                return new Expression\BinaryOp\SpaceShip();
107
            case Node\Expr\BinaryOp\Coalesce::class:
108
                return new Expression\BinaryOp\Coalesce();
109
                
110
            /**
111
             * @link http://php.net/manual/en/language.operators.increment.php
112
             */
113
            case Node\Expr\PostInc::class:
114
                return new Expression\Operators\PostInc();
115
            case Node\Expr\PostDec::class:
116
                return new Expression\Operators\PostDec();
117
            case Node\Expr\PreInc::class:
118
                return new Expression\Operators\PreInc();
119
            case Node\Expr\PreDec::class:
120
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124
            case Node\Expr\BinaryOp\Div::class:
125
                return new Expression\Operators\Arithmetical\Div();
126
            case Node\Expr\BinaryOp\Plus::class:
127
                return new Expression\Operators\Arithmetical\Plus();
128
            case Node\Expr\BinaryOp\Minus::class:
129
                return new Expression\Operators\Arithmetical\Minus();
130
            case Node\Expr\BinaryOp\Mul::class:
131
                return new Expression\Operators\Arithmetical\Mul();
132
            case Node\Expr\BinaryOp\Mod::class:
133
                return new Expression\Operators\Arithmetical\Mod();
134
            case Node\Expr\BinaryOp\Pow::class:
135
                return new Expression\Operators\Arithmetical\Pow();
136
137
            /**
138
             * Bitwise
139
             * @link http://php.net/manual/ru/language.operators.bitwise.php
140
             */
141
            case Node\Expr\BinaryOp\BitwiseOr::class:
142
                return new Expression\Operators\Bitwise\BitwiseOr();
143
            case Node\Expr\BinaryOp\BitwiseXor::class:
144
                return new Expression\Operators\Bitwise\BitwiseXor();
145
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146
                return new Expression\Operators\Bitwise\BitwiseAnd();
147
            case Node\Expr\BinaryOp\ShiftRight::class:
148
                return new Expression\Operators\Bitwise\ShiftRight();
149
            case Node\Expr\BinaryOp\ShiftLeft::class:
150
                return new Expression\Operators\Bitwise\ShiftLeft();
151
            case Node\Expr\BitwiseNot::class:
152
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156
            case Node\Expr\BinaryOp\BooleanOr::class:
157
                return new Expression\Operators\Logical\BooleanOr();
158
            case Node\Expr\BinaryOp\BooleanAnd::class:
159
                return new Expression\Operators\Logical\BooleanAnd();
160
            case Node\Expr\BooleanNot::class:
161
                return new Expression\Operators\Logical\BooleanNot();
162
            case Node\Expr\BinaryOp\LogicalAnd::class:
163
                return new Expression\Operators\Logical\LogicalAnd();
164
            case Node\Expr\BinaryOp\LogicalOr::class:
165
                return new Expression\Operators\Logical\LogicalOr();
166
            case Node\Expr\BinaryOp\LogicalXor::class:
167
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172
            case Node\Expr\BinaryOp\Greater::class:
173
                return new Expression\Operators\Comparison\Greater();
174
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175
                return new Expression\Operators\Comparison\GreaterOrEqual();
176
            case Node\Expr\BinaryOp\Smaller::class:
177
                return new Expression\Operators\Comparison\Smaller();
178
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184
            case Node\Expr\Cast\Array_::class:
185
                return new Expression\Casts\ArrayCast();
186
            case Node\Expr\Cast\Bool_::class:
187
                return new Expression\Casts\BoolCast();
188
            case Node\Expr\Cast\Int_::class:
189
                return new Expression\Casts\IntCast();
190
            case Node\Expr\Cast\Double::class:
191
                return new Expression\Casts\DoubleCast();
192
            case Node\Expr\Cast\Object_::class:
193
                return new Expression\Casts\ObjectCast();
194
            case Node\Expr\Cast\String_::class:
195
                return new Expression\Casts\StringCast();
196
            case Node\Expr\Cast\Unset_::class:
197
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203
            case Node\Expr\Array_::class:
204
                return new Expression\ArrayOp();
205
            case Node\Expr\Assign::class:
206
                return new Expression\Assign();
207
            case Node\Expr\AssignRef::class:
208
                return new Expression\AssignRef();
209
            case Node\Expr\Closure::class:
210
                return new Expression\Closure();
211
            case Node\Expr\ConstFetch::class:
212
                return new Expression\ConstFetch();
213
            case Node\Expr\ClassConstFetch::class:
214
                return new Expression\ClassConstFetch();
215
            case Node\Expr\PropertyFetch::class:
216
                return new Expression\PropertyFetch();
217
            case Node\Expr\StaticPropertyFetch::class:
218
                return new Expression\StaticPropertyFetch();
219
            case Node\Expr\ArrayDimFetch::class:
220
                return new Expression\ArrayDimFetch();
221
            case Node\Expr\UnaryMinus::class:
222
                return new Expression\Operators\UnaryMinus();
223
            case Node\Expr\UnaryPlus::class:
224
                return new Expression\Operators\UnaryPlus();
225
            case Node\Expr\Exit_::class:
226
                return new Expression\ExitOp();
227
            case Node\Expr\Isset_::class:
228
                return new Expression\IssetOp();
229
            case Node\Expr\Print_::class:
230
                return new Expression\PrintOp();
231
            case Node\Expr\Empty_::class:
232
                return new Expression\EmptyOp();
233
            case Node\Expr\Eval_::class:
234
                return new Expression\EvalOp();
235
            case Node\Expr\ShellExec::class:
236
                return new Expression\ShellExec();
237
            case Node\Expr\ErrorSuppress::class:
238
                return new Expression\ErrorSuppress();
239
            case Node\Expr\Include_::class:
240
                return new Expression\IncludeOp();
241
            case Node\Expr\Clone_::class:
242
                return new Expression\CloneOp();
243
            case Node\Expr\Ternary::class:
244
                return new Expression\Ternary();
245
            case Node\Expr\Yield_::class:
246
                return new Expression\YieldOp();
247
            case Node\Expr\YieldFrom::class:
248
                return new Expression\YieldFrom();
249
            case Node\Expr\Variable::class:
250
                return new Expression\Variable();
251
        }
252
253
        return false;
254
    }
255
256
    /**
257
     * @param object|string $expr
258
     * @throws InvalidArgumentException when $expr is not string/object/null
259
     * @throws RuntimeException when compiler class does not return a CompiledExpression
260
     * @return CompiledExpression
261
     */
262
    public function compile($expr)
263
    {
264
        if (is_string($expr)) {
265
            return new CompiledExpression(CompiledExpression::STRING, $expr);
266
        }
267
268
        if (is_null($expr)) {
269
            return new CompiledExpression(CompiledExpression::NULL);
270
        }
271
272
        if (!is_object($expr)) {
273
            throw new InvalidArgumentException('$expr must be string/object/null');
274
        }
275
276
        if ($expr instanceof Node\Scalar) {
277
            $scalar = new \PHPSA\Compiler\Scalar($this->context, $this->eventManager);
278
            return $scalar->compile($expr);
279
        }
280
281
        $this->eventManager->fire(
282
            ExpressionBeforeCompile::EVENT_NAME,
283
            new ExpressionBeforeCompile(
284
                $expr,
285
                $this->context
286
            )
287
        );
288
289
        $className = get_class($expr);
290
        switch ($className) {
291
            case Node\Arg::class:
292
                /**
293
                 * @todo Better compile
294
                 */
295
                return $this->compile($expr->value);
296
297
            /**
298
             * Names
299
             */
300
            case Node\Name::class:
301
                return $this->getNodeName($expr);
302
            case Node\Name\FullyQualified::class:
303
                return $this->getFullyQualifiedNodeName($expr);
304
        }
305
306
        $expressionCompiler = $this->factory($expr);
307
        if (!$expressionCompiler) {
308
            $this->context->debug("Expression compiler is not implemented for {$className}");
309
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
310
        }
311
312
        $result = $expressionCompiler->pass($expr, $this->context);
313
        if (!$result instanceof CompiledExpression) {
314
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
315
        }
316
317
        return $result;
318
    }
319
320
    /**
321
     * @todo Implement - does not belong in this file
322
     *
323
     * @param Node\Stmt\Property $st
324
    public function passProperty(Node\Stmt\Property $st)
325
    {
326
        $docBlock = $st->getDocComment();
327
        if (!$docBlock) {
328
            return new CompiledExpression();
329
        }
330
331
        $phpdoc = DocBlockFactory::createInstance()->create($docBlock->getText());
332
333
        $varTags = $phpdoc->getTagsByName('var');
334
        if ($varTags) {
335
            $varTag = current($varTags);
336
337
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
338
339
            try {
340
                $type = $typeResolver->resolve($varTag->getType());
341
            } catch (\InvalidArgumentException $e) {
342
                return new CompiledExpression();
343
            }
344
345
            if ($type) {
346
                switch (get_class($type)) {
347
                    case \phpDocumentor\Reflection\Types\Object_::class:
348
                        return new CompiledExpression(
349
                            CompiledExpression::OBJECT
350
                        );
351
                    case \phpDocumentor\Reflection\Types\Integer::class:
352
                        return new CompiledExpression(
353
                            CompiledExpression::INTEGER
354
                        );
355
                    case \phpDocumentor\Reflection\Types\String_::class:
356
                        return new CompiledExpression(
357
                            CompiledExpression::STRING
358
                        );
359
                    case \phpDocumentor\Reflection\Types\Float_::class:
360
                        return new CompiledExpression(
361
                            CompiledExpression::DOUBLE
362
                        );
363
                    case \phpDocumentor\Reflection\Types\Null_::class:
364
                        return new CompiledExpression(
365
                            CompiledExpression::NULL
366
                        );
367
                    case \phpDocumentor\Reflection\Types\Boolean::class:
368
                        return new CompiledExpression(
369
                            CompiledExpression::BOOLEAN
370
                        );
371
                }
372
            }
373
        }
374
375
        return new CompiledExpression();
376
    }
377
*/
378
379
380
    /**
381
     * @param Node\Expr\Variable $expr
382
     * @param mixed $value
383
     * @param int $type
384
     * @return CompiledExpression
385
     */
386
    public function declareVariable(Node\Expr\Variable $expr, $value = null, $type = CompiledExpression::UNKNOWN)
387
    {
388
        $variable = $this->context->getSymbol($expr->name);
389
        if ($variable) {
390
            $variable->modify($type, $value);
391
        } else {
392
            $variable = new Variable($expr->name, $value, $type, $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...
393
            $this->context->addVariable($variable);
394
        }
395
396
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
397
    }
398
399
    /**
400
     * @param Node\Name\FullyQualified $expr
401
     * @return CompiledExpression
402
     */
403
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
404
    {
405
        $compiler = $this->context->getApplication()->compiler;
406
407
        $classDefinition = $compiler->getClass($expr->toString());
408
        if ($classDefinition) {
409
            return new CompiledExpression(
410
                CompiledExpression::OBJECT,
411
                $classDefinition
412
            );
413
        }
414
415
        $this->context->debug('Unimplemented FullyQualified', $expr);
416
        return new CompiledExpression();
417
    }
418
419
    /**
420
     * @param Node\Name $expr
421
     * @return CompiledExpression
422
     */
423
    public function getNodeName(Node\Name $expr)
424
    {
425
        $nodeString = $expr->toString();
426
        if ($nodeString === 'null') {
427
            return new CompiledExpression(CompiledExpression::NULL);
428
        }
429
430
        if (in_array($nodeString, ['parent'], true)) {
431
            /** @var ClassDefinition $scope */
432
            $scope = $this->context->scope;
433
            assert($scope instanceof ClassDefinition);
434
435
            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...
436
                $definition = $scope->getExtendsClassDefinition();
437
                if ($definition) {
438
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
439
                }
440
            } else {
441
                $this->context->notice(
442
                    'language_error',
443
                    'Cannot access parent:: when current class scope has no parent',
444
                    $expr
445
                );
446
            }
447
        }
448
449
        if (in_array($nodeString, ['self', 'static'], true)) {
450
            return CompiledExpression::fromZvalValue($this->context->scope);
451
        }
452
453
        if (defined($nodeString)) {
454
            return CompiledExpression::fromZvalValue(constant($expr));
455
        }
456
457
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
458
    }
459
}
460