Completed
Push — master ( 89c47e...c3e51a )
by Дмитрий
04:58 queued 02:05
created

Expression   F

Complexity

Total Complexity 104

Size/Duplication

Total Lines 440
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 94

Test Coverage

Coverage 87.97%

Importance

Changes 0
Metric Value
dl 0
loc 440
ccs 212
cts 241
cp 0.8797
rs 1.3043
c 0
b 0
f 0
wmc 104
lcom 1
cbo 94

6 Methods

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

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