Completed
Push — master ( 45984e...e3e24d )
by Enrico
17:48 queued 11:36
created

Expression   F

Complexity

Total Complexity 108

Size/Duplication

Total Lines 440
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 90

Test Coverage

Coverage 86.83%

Importance

Changes 0
Metric Value
dl 0
loc 440
ccs 211
cts 243
cp 0.8683
rs 1.3043
c 0
b 0
f 0
wmc 108
lcom 1
cbo 90

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D factory() 0 210 82
C compile() 0 68 15
A declareVariable() 0 10 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 36 7

How to fix   Complexity   

Complex Class

Complex classes like Expression often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Expression, and based on these observations, apply Extract Interface, too.

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