Completed
Pull Request — master (#251)
by
unknown
11:10
created

Expression   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 445
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 91

Test Coverage

Coverage 86.32%

Importance

Changes 0
Metric Value
dl 0
loc 445
ccs 202
cts 234
cp 0.8632
rs 1.3043
c 0
b 0
f 0
wmc 103
lcom 1
cbo 91

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D factory() 0 210 82
C compile() 0 73 10
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 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 862
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 862
        $this->context = $context;
38 862
        $this->eventManager = $eventManager;
39 862
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 851
    protected function factory($expr)
46
    {
47 851
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 851
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 851
            case Node\Expr\FuncCall::class:
54 9
                return new Expression\FunctionCall();
55 847
            case Node\Expr\StaticCall::class:
56 1
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 846
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 846
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 846
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 825
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 808
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 788
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 768
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 750
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 739
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 727
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 715
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 703
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 689
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 677
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 665
            case Node\Expr\BinaryOp\Identical::class:
96 30
                return new Expression\BinaryOp\Identical();
97 637
            case Node\Expr\BinaryOp\Concat::class:
98 14
                return new Expression\Operators\Concat();
99 623
            case Node\Expr\BinaryOp\NotIdentical::class:
100 16
                return new Expression\BinaryOp\NotIdentical();
101 609
            case Node\Expr\BinaryOp\Equal::class:
102 45
                return new Expression\BinaryOp\Equal();
103 580
            case Node\Expr\BinaryOp\NotEqual::class:
104 19
                return new Expression\BinaryOp\NotEqual();
105 563
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 554
            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 552
            case Node\Expr\PostInc::class:
114 11
                return new Expression\Operators\PostInc();
115 544
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 536
            case Node\Expr\PreInc::class:
118 10
                return new Expression\Operators\PreInc();
119 528
            case Node\Expr\PreDec::class:
120 9
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 520
            case Node\Expr\BinaryOp\Div::class:
125 37
                return new Expression\Operators\Arithmetical\Div();
126 483
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 442
            case Node\Expr\BinaryOp\Minus::class:
129 24
                return new Expression\Operators\Arithmetical\Minus();
130 418
            case Node\Expr\BinaryOp\Mul::class:
131 37
                return new Expression\Operators\Arithmetical\Mul();
132 383
            case Node\Expr\BinaryOp\Mod::class:
133 35
                return new Expression\Operators\Arithmetical\Mod();
134 348
            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 327
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 315
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 303
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 291
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 279
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 267
            case Node\Expr\BitwiseNot::class:
152 6
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 262
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 247
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 16
                return new Expression\Operators\Logical\BooleanAnd();
160 234
            case Node\Expr\BooleanNot::class:
161 11
                return new Expression\Operators\Logical\BooleanNot();
162 226
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 213
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 198
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 183
            case Node\Expr\BinaryOp\Greater::class:
173 14
                return new Expression\Operators\Comparison\Greater();
174 171
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 13
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 159
            case Node\Expr\BinaryOp\Smaller::class:
177 15
                return new Expression\Operators\Comparison\Smaller();
178 147
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 13
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 135
            case Node\Expr\Cast\Array_::class:
185 3
                return new Expression\Casts\ArrayCast();
186 134
            case Node\Expr\Cast\Bool_::class:
187 8
                return new Expression\Casts\BoolCast();
188 128
            case Node\Expr\Cast\Int_::class:
189 8
                return new Expression\Casts\IntCast();
190 122
            case Node\Expr\Cast\Double::class:
191 8
                return new Expression\Casts\DoubleCast();
192 116
            case Node\Expr\Cast\Object_::class:
193 2
                return new Expression\Casts\ObjectCast();
194 115
            case Node\Expr\Cast\String_::class:
195 7
                return new Expression\Casts\StringCast();
196 109
            case Node\Expr\Cast\Unset_::class:
197 2
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 108
            case Node\Expr\Array_::class:
204 39
                return new Expression\ArrayOp();
205 79
            case Node\Expr\Assign::class:
206 37
                return new Expression\Assign();
207 59
            case Node\Expr\AssignRef::class:
208 1
                return new Expression\AssignRef();
209 59
            case Node\Expr\Closure::class:
210
                return new Expression\Closure();
211 59
            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 862
    public function compile($expr)
263
    {
264 862
        if (is_string($expr)) {
265 24
            return new CompiledExpression(CompiledExpression::STRING, $expr);
266
        }
267
268 862
        if (is_null($expr)) {
269 10
            return new CompiledExpression(CompiledExpression::NULL);
270
        }
271
272 862
        if (!is_object($expr)) {
273
            throw new InvalidArgumentException('$expr must be string/object/null');
274
        }
275
276 862
        if ($expr instanceof Node\Scalar) {
277 802
            $scalar = new \PHPSA\Compiler\Scalar($this->context, $this->context->getEventManager());
278 802
            return $scalar->compile($expr);
279
        }
280
281 851
        $this->eventManager->fire(
282 851
            ExpressionBeforeCompile::EVENT_NAME,
283 851
            new ExpressionBeforeCompile(
284 851
                $expr,
285 851
                $this->context
286 851
            )
287 851
        );
288
289 851
        $className = get_class($expr);
290
        switch ($className) {
291 851
            case Node\Arg::class:
292
                /**
293
                 * @todo Better compile
294
                 */
295 2
                return $this->compile($expr->value);
296
297
            /**
298
             * Expressions
299
             */
300 851
            case Node\Name::class:
301 32
                return $this->getNodeName($expr);
302 851
            case Node\Name\FullyQualified::class:
303
                return $this->getFullyQualifiedNodeName($expr);
304
305
            /**
306
             * Simple Scalar(s)
307
             */
308
//            case \PHPSA\Node\Scalar\Nil::class:
309
//                return new CompiledExpression(CompiledExpression::NULL);
310
//            case Node\Scalar\LNumber::class:
311
//                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
312
//            case Node\Scalar\DNumber::class:
313
//                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
314
//            case Node\Scalar\String_::class:
315
//                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
316
//            case \PHPSA\Node\Scalar\Boolean::class:
317
//                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
318
//            case \PHPSA\Node\Scalar\Fake::class:
319
//                return new CompiledExpression($expr->type, $expr->value);
320
        }
321
322 851
        $expressionCompiler = $this->factory($expr);
323 851
        if (!$expressionCompiler) {
324
            $this->context->debug("Expression compiler is not implemented for {$className}");
325
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
326
        }
327
328 851
        $result = $expressionCompiler->pass($expr, $this->context);
329 851
        if (!$result instanceof CompiledExpression) {
330
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
331
        }
332
333 851
        return $result;
334
    }
335
336
    /**
337
     * @todo Implement - does not belong in this file
338
     *
339
     * @param Node\Stmt\Property $st
340
    public function passProperty(Node\Stmt\Property $st)
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) {
351
            $varTag = current($varTags);
352
353
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
354
355
            try {
356
                $type = $typeResolver->resolve($varTag->getType());
357
            } catch (\InvalidArgumentException $e) {
358
                return new CompiledExpression();
359
            }
360
361
            if ($type) {
362
                switch (get_class($type)) {
363
                    case \phpDocumentor\Reflection\Types\Object_::class:
364
                        return new CompiledExpression(
365
                            CompiledExpression::OBJECT
366
                        );
367
                    case \phpDocumentor\Reflection\Types\Integer::class:
368
                        return new CompiledExpression(
369
                            CompiledExpression::INTEGER
370
                        );
371
                    case \phpDocumentor\Reflection\Types\String_::class:
372
                        return new CompiledExpression(
373
                            CompiledExpression::STRING
374
                        );
375
                    case \phpDocumentor\Reflection\Types\Float_::class:
376
                        return new CompiledExpression(
377
                            CompiledExpression::DOUBLE
378
                        );
379
                    case \phpDocumentor\Reflection\Types\Null_::class:
380
                        return new CompiledExpression(
381
                            CompiledExpression::NULL
382
                        );
383
                    case \phpDocumentor\Reflection\Types\Boolean::class:
384
                        return new CompiledExpression(
385
                            CompiledExpression::BOOLEAN
386
                        );
387
                }
388
            }
389
        }
390
391
        return new CompiledExpression();
392
    }
393
*/
394
395
396
    /**
397
     * @param Node\Expr\Variable $expr
398
     * @param mixed $value
399
     * @param int $type
400
     * @return CompiledExpression
401
     */
402 2
    public function declareVariable(Node\Expr\Variable $expr, $value = null, $type = CompiledExpression::UNKNOWN)
403
    {
404 2
        $variable = $this->context->getSymbol($expr->name);
405 2
        if (!$variable) {
406 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...
407 2
            $this->context->addVariable($variable);
408 2
        }
409
410 2
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
411
    }
412
413
    /**
414
     * @param Node\Name\FullyQualified $expr
415
     * @return CompiledExpression
416
     */
417
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
418
    {
419
        $this->context->debug('Unimplemented FullyQualified', $expr);
420
421
        return new CompiledExpression();
422
    }
423
424
    /**
425
     * @param Node\Name $expr
426
     * @return CompiledExpression
427
     */
428 32
    public function getNodeName(Node\Name $expr)
429
    {
430 32
        $nodeString = $expr->toString();
431 32
        if ($nodeString === 'null') {
432 2
            return new CompiledExpression(CompiledExpression::NULL);
433
        }
434
435 31
        if (in_array($nodeString, ['parent'], true)) {
436
            /** @var ClassDefinition $scope */
437
            $scope = $this->context->scope;
438
            assert($scope instanceof ClassDefinition);
439
440
            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...
441
                $definition = $scope->getExtendsClassDefinition();
442
                if ($definition) {
443
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
444
                }
445
            } else {
446
                $this->context->notice(
447
                    'no-parent',
448
                    'Cannot access parent:: when current class scope has no parent',
449
                    $expr
450
                );
451
            }
452
        }
453
454 31
        if (in_array($nodeString, ['self', 'static'], true)) {
455 1
            return CompiledExpression::fromZvalValue($this->context->scope);
456
        }
457
458 30
        if (defined($nodeString)) {
459 11
            return CompiledExpression::fromZvalValue(constant($expr));
460
        }
461
462 22
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
463
    }
464
}
465