Completed
Push — master ( 662a49...9d1eac )
by Дмитрий
9s
created

ControlFlowGraph::passExpr()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 15.2734

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 10
nop 1
dl 0
loc 36
ccs 15
cts 24
cp 0.625
crap 15.2734
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\ControlFlow;
7
8
use PhpParser\Node\Stmt\Function_;
9
use PHPSA\ControlFlow\Node;
10
11
class ControlFlowGraph
12
{
13
    /**
14
     * @var int
15
     */
16
    protected $lastBlockId = 1;
17
18
    /**
19
     * @var Block
20
     */
21
    protected $root;
22
23
    /**
24
     * @var Block[]
25
     */
26
    protected $labels;
27
28
    /**
29
     * @todo
30
     *
31
     * @var \PhpParser\Node\Stmt\Goto_[]
32
     */
33
    protected $unresolvedGotos;
34
35
    /**
36
     * @param $statement
37
     */
38 6
    public function __construct($statement)
39
    {
40 6
        $this->root = new Block($this->lastBlockId++);
41
42 6
        if ($statement instanceof Function_) {
43 6
            if ($statement->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statement->stmts of type PhpParser\Node[] 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...
44 5
                $this->passNodes($statement->stmts, $this->root);
45 5
            }
46 6
        }
47 6
    }
48
49
    /**
50
     * @param \PhpParser\Node[] $nodes
51
     * @param Block $block
52
     */
53 5
    protected function passNodes(array $nodes, Block $block)
54
    {
55 5
        foreach ($nodes as $stmt) {
56 5
            switch (get_class($stmt)) {
57 5
                case \PhpParser\Node\Stmt\Goto_::class:
58
                    if (isset($this->labels[$stmt->name])) {
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
59
                        $block->addChildren(
60
                            new Node\JumpNode($this->labels[$stmt->name])
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
61
                        );
62
                    } else {
63
                        $this->unresolvedGotos[] = $stmt;
64
                    }
65
                    break;
66 5
                case \PhpParser\Node\Expr\Assign::class:
67
                    $this->passAssign($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Expr\Assign>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
68
                    break;
69 5
                case \PhpParser\Node\Stmt\Return_::class:
70 4
                    $this->passReturn($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Return_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
71 4
                    break;
72 1
                case \PhpParser\Node\Stmt\For_::class:
73
                    $block = $this->passFor($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\For_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
74
                    break;
75 1
                case \PhpParser\Node\Stmt\If_::class:
76
                    $block = $this->passIf($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\If_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
77
                    break;
78 1
                case \PhpParser\Node\Stmt\While_::class:
79
                    $block = $this->passWhile($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\While_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
80
                    break;
81 1
                case \PhpParser\Node\Stmt\Do_::class:
82
                    $block = $this->passDo($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Do_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
83
                    break;
84 1
                case \PhpParser\Node\Stmt\Throw_::class:
85
                    $this->passThrow($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\Throw_>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
86
                    break;
87 1
                case \PhpParser\Node\Expr\Exit_::class:
88
                    $block->addChildren(new Node\ExitNode());
89
                    break;
90 1
                case \PhpParser\Node\Stmt\Label::class:
91
                    $block = $this->createNewBlockIfNeeded($block);
92
                    $block->label = $stmt->name;
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
93
                    $this->labels[$block->label] = $block;
94
                    break;
95 1
                case \PhpParser\Node\Stmt\TryCatch::class:
96
                    $block = $this->passTryCatch($stmt, $block);
0 ignored issues
show
Compatibility introduced by
$stmt of type object<PhpParser\Node> is not a sub-type of object<PhpParser\Node\Stmt\TryCatch>. It seems like you assume a concrete implementation of the interface PhpParser\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
97
                    break;
98 1
                case \PhpParser\Node\Stmt\Nop::class:
99
                    // ignore commented code
100
                    break;
101 1
                default:
102 1
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
103 1
                    break;
104 5
            }
105 5
        }
106 5
    }
107
108
    /**
109
     * If current block is not empty, lets create a new one
110
     *
111
     * @param Block $block
112
     * @return Block
113
     */
114
    protected function createNewBlockIfNeeded(Block $block)
115
    {
116
        if (!$block->getChildren()) {
117
            $next = new Block($this->lastBlockId++);
118
119
            $next->setExit(
120
                $block
121
            );
122
123
            return $next;
124
        }
125
126
        return $block;
127
    }
128
129
    /**
130
     * @param \PhpParser\Node\Expr $expr
131
     * @return Node\AbstractNode
132
     */
133 4
    protected function passExpr(\PhpParser\Node\Expr $expr)
134
    {
135 4
        switch (get_class($expr)) {
136 4
            case \PhpParser\Node\Expr\BinaryOp\NotIdentical::class:
137
                return new Node\Expr\BinaryOp\NotIdentical();
138
139 4
            case \PhpParser\Node\Expr\BinaryOp\Identical::class:
140
                return new Node\Expr\BinaryOp\Identical();
141
142 4
            case \PhpParser\Node\Expr\BinaryOp\NotEqual::class:
143
                return new Node\Expr\BinaryOp\NotEqual();
144
145 4
            case \PhpParser\Node\Expr\BinaryOp\Equal::class:
146
                return new Node\Expr\BinaryOp\Equal();
147
148 4
            case \PhpParser\Node\Expr\BinaryOp\Smaller::class:
149
                return new Node\Expr\BinaryOp\Smaller();
150
151 4
            case \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class:
152
                return new Node\Expr\BinaryOp\SmallerOrEqual();
153
154 4
            case \PhpParser\Node\Expr\BinaryOp\Greater::class:
155
                return new Node\Expr\BinaryOp\Greater();
156
157 4
            case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class:
158
                return new Node\Expr\BinaryOp\GreaterOrEqual();
159
160 4
            case \PhpParser\Node\Expr\Instanceof_::class:
161
                return new Node\Expr\InstanceOfExpr();
162
163 4
            default:
164 4
                echo 'Unimplemented ' . get_class($expr) . PHP_EOL;
165 4
        }
166
167 4
        return new Node\UnknownNode();
168
    }
169
170
    /**
171
     * @param \PhpParser\Node\Stmt\If_ $if
172
     * @param Block $block
173
     * @return Block
174
     */
175
    protected function passIf(\PhpParser\Node\Stmt\If_ $if, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $if. 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...
176
    {
177
        $trueBlock = new Block($this->lastBlockId++);
178
        $this->passNodes($if->stmts, $trueBlock);
179
180
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
181
182
        $elseBlock = null;
183
184
        if ($if->else) {
185
            if ($if->else->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if->else->stmts of type PhpParser\Node[] 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...
186
                $elseBlock = new Block($this->lastBlockId++);
187
                $this->passNodes($if->else->stmts, $elseBlock);
188
189
                $jumpIf->setElse($elseBlock);
190
            }
191
        }
192
193
        $block->addChildren(
194
            $jumpIf
195
        );
196
197
        $exitBlock = new Block($this->lastBlockId++);
198
        $trueBlock->setExit($exitBlock);
199
200
        if ($elseBlock) {
201
            $elseBlock->setExit($exitBlock);
202
        }
203
204
        return $exitBlock;
205
    }
206
207
    /**
208
     * @param \PhpParser\Node\Stmt\For_ $for
209
     * @param Block $block
210
     * @return Block
211
     */
212
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
213
    {
214
        $this->passNodes($for->init, $block);
215
216
        $block->setExit(
217
            $loop = new Block($this->lastBlockId++)
218
        );
219
        $this->passNodes($for->stmts, $loop);
220
221
        $loop->setExit(
222
            $after = new Block($this->lastBlockId++)
223
        );
224
        return $after;
225
    }
226
227
    /**
228
     * @param \PhpParser\Node\Stmt\Do_ $do
229
     * @param Block $block
230
     * @return Block
231
     */
232
    protected function passDo(\PhpParser\Node\Stmt\Do_ $do, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $do. 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...
233
    {
234
        $loop = new Block($this->lastBlockId++);
235
        $this->passNodes($do->stmts, $loop);
236
237
        $block->setExit($loop);
238
239
        $cond = new Block($this->lastBlockId++);
240
        $loop->setExit($cond);
241
242
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
243
        $cond->addChildren($jumpIf);
244
245
        $exitBlock = new Block($this->lastBlockId++);
246
        $jumpIf->setElse($exitBlock);
247
248
        return $exitBlock;
249
    }
250
251
    /**
252
     * @param \PhpParser\Node\Stmt\While_ $while
253
     * @param Block $block
254
     * @return Block
255
     */
256
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
257
    {
258
        $cond = new Block($this->lastBlockId++);
259
        $block->setExit(
260
            $cond
261
        );
262
263
        $loop = new Block($this->lastBlockId++);
264
265
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
266
        $cond->addChildren($jumpIf);
267
268
        $this->passNodes($while->stmts, $loop);
269
270
        $loop->addChildren(new Node\JumpNode($cond));
271
        //$loop->setExit($cond);
272
273
        $after = new Block($this->lastBlockId++);
274
        $jumpIf->setElse($after);
275
276
        return $after;
277
    }
278
279
    /**
280
     * @param \PhpParser\Node\Stmt\Throw_ $throw_
281
     * @param Block $block
282
     */
283
    protected function passThrow(\PhpParser\Node\Stmt\Throw_ $throw_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $throw_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
284
    {
285
        $block->addChildren(new Node\ThrowNode());
286
    }
287
288
    /**
289
     * @param \PhpParser\Node\Expr\Assign $assign
290
     * @param Block $block
291
     */
292
    protected function passAssign(\PhpParser\Node\Expr\Assign $assign, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $assign is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
293
    {
294
        $block->addChildren(new Node\AssignNode());
295
    }
296
297
    /**
298
     * @param \PhpParser\Node\Stmt\Return_ $return
299
     * @param Block $block
300
     */
301 4
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return, Block $block)
302
    {
303 4
        if ($return->expr) {
304 4
            $block->addChildren(
305 4
                new Node\ReturnNode(
306 4
                    $this->passExpr(
307 4
                        $return->expr
308 4
                    )
309 4
                )
310 4
            );
311 4
        } else {
312
            $block->addChildren(
313
                new Node\ReturnNode()
314
            );
315
        }
316 4
    }
317
318
    /**
319
     * @param \PhpParser\Node\Stmt\TryCatch $stmt
320
     * @param Block $block
321
     * @return Block
322
     */
323
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
324
    {
325
        $try = new Block($this->lastBlockId++);
326
        $this->passNodes($stmt->stmts, $try);
327
328
        $block->setExit($try);
329
330
        if ($stmt->finally) {
331
            $finally = new Block($this->lastBlockId++);
332
            $this->passNodes($stmt->finally->stmts, $finally);
333
334
            $try->setExit($finally);
335
            return $finally;
336
        }
337
338
        return $try;
339
    }
340
341
    /**
342
     * @return Block
343
     */
344
    public function getRoot()
345
    {
346
        return $this->root;
347
    }
348
}
349