Completed
Pull Request — master (#219)
by
unknown
04:22
created

Expression   F

Complexity

Total Complexity 120

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 95

Test Coverage

Coverage 75.8%

Importance

Changes 0
Metric Value
dl 0
loc 443
ccs 213
cts 281
cp 0.758
rs 1.3043
c 0
b 0
f 0
wmc 120
lcom 1
cbo 95

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C compile() 0 70 16
C passProperty() 0 54 11
A declareVariable() 0 10 2
A getFullyQualifiedNodeName() 0 6 1
C getNodeName() 0 36 7
D factory() 0 210 82

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