Completed
Pull Request — master (#283)
by Enrico
04:27
created

Expression::factory()   D

Complexity

Conditions 82
Paths 82

Size

Total Lines 210
Code Lines 165

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 157
CRAP Score 83.0705

Importance

Changes 0
Metric Value
cc 82
eloc 165
nc 82
nop 1
dl 0
loc 210
ccs 157
cts 166
cp 0.9458
crap 83.0705
rs 4.1818
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 103 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 883
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 883
        $this->context = $context;
38 883
        $this->eventManager = $eventManager;
39 883
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 871
    protected function factory($expr)
46
    {
47 871
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 871
            case Node\Expr\MethodCall::class:
52 2
                return new Expression\MethodCall();
53 871
            case Node\Expr\FuncCall::class:
54 11
                return new Expression\FunctionCall();
55 866
            case Node\Expr\StaticCall::class:
56 1
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 865
            case Node\Expr\New_::class:
61 4
                return new Expression\Operators\NewOp();
62 865
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 865
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 844
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 827
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 807
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 787
            case Node\Expr\AssignOp\Div::class:
76 20
                return new Expression\AssignOp\Div();
77 769
            case Node\Expr\AssignOp\Mod::class:
78 13
                return new Expression\AssignOp\Mod();
79 758
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 746
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 734
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 722
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 708
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 696
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 684
            case Node\Expr\BinaryOp\Identical::class:
96 30
                return new Expression\BinaryOp\Identical();
97 656
            case Node\Expr\BinaryOp\Concat::class:
98 14
                return new Expression\Operators\Concat();
99 642
            case Node\Expr\BinaryOp\NotIdentical::class:
100 16
                return new Expression\BinaryOp\NotIdentical();
101 628
            case Node\Expr\BinaryOp\Equal::class:
102 46
                return new Expression\BinaryOp\Equal();
103 599
            case Node\Expr\BinaryOp\NotEqual::class:
104 19
                return new Expression\BinaryOp\NotEqual();
105 582
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 573
            case Node\Expr\BinaryOp\Coalesce::class:
108 3
                return new Expression\BinaryOp\Coalesce();
109
                
110
            /**
111
             * @link http://php.net/manual/en/language.operators.increment.php
112
             */
113 571
            case Node\Expr\PostInc::class:
114 11
                return new Expression\Operators\PostInc();
115 563
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 555
            case Node\Expr\PreInc::class:
118 10
                return new Expression\Operators\PreInc();
119 547
            case Node\Expr\PreDec::class:
120 9
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 539
            case Node\Expr\BinaryOp\Div::class:
125 39
                return new Expression\Operators\Arithmetical\Div();
126 502
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 461
            case Node\Expr\BinaryOp\Minus::class:
129 24
                return new Expression\Operators\Arithmetical\Minus();
130 437
            case Node\Expr\BinaryOp\Mul::class:
131 37
                return new Expression\Operators\Arithmetical\Mul();
132 402
            case Node\Expr\BinaryOp\Mod::class:
133 37
                return new Expression\Operators\Arithmetical\Mod();
134 367
            case Node\Expr\BinaryOp\Pow::class:
135 21
                return new Expression\Operators\Arithmetical\Pow();
136
137
            /**
138
             * Bitwise
139
             * @link http://php.net/manual/ru/language.operators.bitwise.php
140
             */
141 346
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 334
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 322
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 310
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 298
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 286
            case Node\Expr\BitwiseNot::class:
152 6
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 281
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 266
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 16
                return new Expression\Operators\Logical\BooleanAnd();
160 253
            case Node\Expr\BooleanNot::class:
161 12
                return new Expression\Operators\Logical\BooleanNot();
162 245
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 232
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 217
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 202
            case Node\Expr\BinaryOp\Greater::class:
173 14
                return new Expression\Operators\Comparison\Greater();
174 190
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 13
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 178
            case Node\Expr\BinaryOp\Smaller::class:
177 15
                return new Expression\Operators\Comparison\Smaller();
178 166
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 13
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 154
            case Node\Expr\Cast\Array_::class:
185 3
                return new Expression\Casts\ArrayCast();
186 153
            case Node\Expr\Cast\Bool_::class:
187 8
                return new Expression\Casts\BoolCast();
188 147
            case Node\Expr\Cast\Int_::class:
189 8
                return new Expression\Casts\IntCast();
190 141
            case Node\Expr\Cast\Double::class:
191 8
                return new Expression\Casts\DoubleCast();
192 135
            case Node\Expr\Cast\Object_::class:
193 11
                return new Expression\Casts\ObjectCast();
194 125
            case Node\Expr\Cast\String_::class:
195 7
                return new Expression\Casts\StringCast();
196 119
            case Node\Expr\Cast\Unset_::class:
197 2
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 118
            case Node\Expr\Array_::class:
204 42
                return new Expression\ArrayOp();
205 89
            case Node\Expr\Assign::class:
206 44
                return new Expression\Assign();
207 68
            case Node\Expr\AssignRef::class:
208 1
                return new Expression\AssignRef();
209 68
            case Node\Expr\Closure::class:
210
                return new Expression\Closure();
211 68
            case Node\Expr\ConstFetch::class:
212 13
                return new Expression\ConstFetch();
213 63
            case Node\Expr\ClassConstFetch::class:
214
                return new Expression\ClassConstFetch();
215 63
            case Node\Expr\PropertyFetch::class:
216
                return new Expression\PropertyFetch();
217 63
            case Node\Expr\StaticPropertyFetch::class:
218
                return new Expression\StaticPropertyFetch();
219 63
            case Node\Expr\ArrayDimFetch::class:
220
                return new Expression\ArrayDimFetch();
221 63
            case Node\Expr\UnaryMinus::class:
222 10
                return new Expression\Operators\UnaryMinus();
223 54
            case Node\Expr\UnaryPlus::class:
224 11
                return new Expression\Operators\UnaryPlus();
225 43
            case Node\Expr\Exit_::class:
226 2
                return new Expression\ExitOp();
227 41
            case Node\Expr\Isset_::class:
228 3
                return new Expression\IssetOp();
229 38
            case Node\Expr\Print_::class:
230 2
                return new Expression\PrintOp();
231 37
            case Node\Expr\Empty_::class:
232 5
                return new Expression\EmptyOp();
233 32
            case Node\Expr\Eval_::class:
234 2
                return new Expression\EvalOp();
235 30
            case Node\Expr\ShellExec::class:
236 1
                return new Expression\ShellExec();
237 29
            case Node\Expr\ErrorSuppress::class:
238 2
                return new Expression\ErrorSuppress();
239 27
            case Node\Expr\Include_::class:
240
                return new Expression\IncludeOp();
241 27
            case Node\Expr\Clone_::class:
242 1
                return new Expression\CloneOp();
243 26
            case Node\Expr\Ternary::class:
244 3
                return new Expression\Ternary();
245 24
            case Node\Expr\Yield_::class:
246 1
                return new Expression\YieldOp();
247 24
            case Node\Expr\YieldFrom::class:
248 1
                return new Expression\YieldFrom();
249 24
            case Node\Expr\Variable::class:
250 24
                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 883
    public function compile($expr)
263
    {
264 883
        if (is_string($expr)) {
265 31
            return new CompiledExpression(CompiledExpression::STRING, $expr);
266
        }
267
268 883
        if (is_null($expr)) {
269 11
            return new CompiledExpression(CompiledExpression::NULL);
270
        }
271
272 883
        if (!is_object($expr)) {
273
            throw new InvalidArgumentException('$expr must be string/object/null');
274
        }
275
276 883
        if ($expr instanceof Node\Scalar) {
277 819
            $scalar = new \PHPSA\Compiler\Scalar($this->context, $this->eventManager);
278 819
            return $scalar->compile($expr);
279
        }
280
281 872
        $this->eventManager->fire(
282 872
            ExpressionBeforeCompile::EVENT_NAME,
283 872
            new ExpressionBeforeCompile(
284 872
                $expr,
285 872
                $this->context
286 872
            )
287 872
        );
288
289 872
        $className = get_class($expr);
290
        switch ($className) {
291 872
            case Node\Arg::class:
292
                /**
293
                 * @todo Better compile
294
                 */
295 2
                return $this->compile($expr->value);
296
297
            /**
298
             * Names
299
             */
300 872
            case Node\Name::class:
301 36
                return $this->getNodeName($expr);
302 871
            case Node\Name\FullyQualified::class:
303
                return $this->getFullyQualifiedNodeName($expr);
304
        }
305
306 871
        $expressionCompiler = $this->factory($expr);
307 871
        if (!$expressionCompiler) {
308
            $this->context->debug("Expression compiler is not implemented for {$className}");
309
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
310
        }
311
312 871
        $result = $expressionCompiler->pass($expr, $this->context);
313 871
        if (!$result instanceof CompiledExpression) {
314
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
315
        }
316
317 871
        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 2
    public function declareVariable(Node\Expr\Variable $expr, $value = null, $type = CompiledExpression::UNKNOWN)
387
    {
388 2
        $variable = $this->context->getSymbol($expr->name);
389 2
        if (!$variable) {
390 2
            $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...
391 2
            $this->context->addVariable($variable);
392 2
        }
393
394 2
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
395
    }
396
397
    /**
398
     * @param Node\Name\FullyQualified $expr
399
     * @return CompiledExpression
400
     */
401
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
402
    {
403
        $this->context->debug('Unimplemented FullyQualified', $expr);
404
405
        return new CompiledExpression();
406
    }
407
408
    /**
409
     * @param Node\Name $expr
410
     * @return CompiledExpression
411
     */
412 36
    public function getNodeName(Node\Name $expr)
413
    {
414 36
        $nodeString = $expr->toString();
415 36
        if ($nodeString === 'null') {
416 2
            return new CompiledExpression(CompiledExpression::NULL);
417
        }
418
419 35
        if (in_array($nodeString, ['parent'], true)) {
420
            /** @var ClassDefinition $scope */
421
            $scope = $this->context->scope;
422
            assert($scope instanceof ClassDefinition);
423
424
            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...
425
                $definition = $scope->getExtendsClassDefinition();
426
                if ($definition) {
427
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
428
                }
429
            } else {
430
                $this->context->notice(
431
                    'language_error',
432
                    'Cannot access parent:: when current class scope has no parent',
433
                    $expr
434
                );
435
            }
436
        }
437
438 35
        if (in_array($nodeString, ['self', 'static'], true)) {
439 1
            return CompiledExpression::fromZvalValue($this->context->scope);
440
        }
441
442 34
        if (defined($nodeString)) {
443 12
            return CompiledExpression::fromZvalValue(constant($expr));
444
        }
445
446 25
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
447
    }
448
}
449