Completed
Push — master ( 789135...16eacb )
by Enrico
11s
created

Expression::factory()   D

Complexity

Conditions 77
Paths 77

Size

Total Lines 200
Code Lines 155

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 150
CRAP Score 77.3383

Importance

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