Completed
Pull Request — master (#293)
by Дмитрий
04:59 queued 01:09
created

ControlFlowGraph   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 30.93%

Importance

Changes 0
Metric Value
dl 0
loc 151
ccs 30
cts 97
cp 0.3093
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 12

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 3
D passNodes() 0 37 10
B passIf() 0 31 4
A passFor() 0 14 1
A passWhile() 0 22 1
A passThrow() 0 4 1
A passAssign() 0 4 1
A passReturn() 0 4 1
A getRoot() 0 4 1
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
    protected $lastBlockId = 1;
14
15
    /**
16
     * @var Block
17
     */
18
    protected $root;
19
20 5
    public function __construct($statement)
21
    {
22 5
        $this->root = new Block($this->lastBlockId++);
23
24 5
        if ($statement instanceof Function_) {
25 5
            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...
26 4
                $this->passNodes($statement->stmts, $this->root);
27 4
            }
28 5
        }
29 5
    }
30
31 4
    protected function passNodes(array $nodes, Block $block)
32
    {
33 4
        foreach ($nodes as $stmt) {
34 4
            switch (get_class($stmt)) {
35 4
                case \PhpParser\Node\Expr\Assign::class:
36
                    $this->passAssign($stmt, $block);
37
                    break;
38 4
                case \PhpParser\Node\Stmt\Return_::class:
39 3
                    $this->passReturn($stmt, $block);
40 3
                    break;
41 1
                case \PhpParser\Node\Stmt\For_::class:
42
                    $block = $this->passFor($stmt, $block);
43
                    break;
44 1
                case \PhpParser\Node\Stmt\If_::class:
45
                    $block = $this->passIf($stmt, $block);
46
                    break;
47 1
                case \PhpParser\Node\Stmt\While_::class:
48
                    $block = $this->passWhile($stmt, $block);
49
                    break;
50 1
                case \PhpParser\Node\Stmt\Throw_::class:
51
                    $this->passThrow($stmt, $block);
52
                    break;
53 1
                case \PhpParser\Node\Expr\Exit_::class:
54
                    $block->addChildren(new Node\ExitNode());
55
                    break;
56 1
                case \PhpParser\Node\Stmt\Label::class:
57
                    $block->setExit(
58
                        $block = new Block($this->lastBlockId++)
59
                    );
60
                    $block->label = $stmt->name;
61
                    break;
62 1
                default:
63 1
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
64 1
                    break;
65 4
            }
66 4
        }
67 4
    }
68
69
    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...
70
    {
71
        $trueBlock = new Block($this->lastBlockId++);
72
        $this->passNodes($if->stmts, $trueBlock);
73
74
        $jumpIf = new Node\JumpIfNode($trueBlock);
75
76
        $elseBlock = null;
77
78
        if ($if->else) {
79
            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...
80
                $elseBlock = new Block($this->lastBlockId++);
81
                $this->passNodes($if->else->stmts, $elseBlock);
82
83
                $jumpIf->setElse($elseBlock);
84
            }
85
        }
86
87
        $block->addChildren(
88
            $jumpIf
89
        );
90
91
        $exitBlock = new Block($this->lastBlockId++);
92
        $trueBlock->setExit($exitBlock);
93
94
        if ($elseBlock) {
95
            $elseBlock->setExit($exitBlock);
96
        }
97
98
        return $exitBlock;
99
    }
100
101
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
102
    {
103
        $this->passNodes($for->init, $block);
104
105
        $block->setExit(
106
            $loop = new Block($this->lastBlockId++)
107
        );
108
        $this->passNodes($for->stmts, $loop);
109
110
        $loop->setExit(
111
            $after = new Block($this->lastBlockId++)
112
        );
113
        return $after;
114
    }
115
116
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
117
    {
118
        $cond = new Block($this->lastBlockId++);
119
        $block->setExit(
120
            $cond
121
        );
122
123
        $loop = new Block($this->lastBlockId++);
124
125
        $jumpIf = new Node\JumpIfNode($loop);
126
        $cond->addChildren($jumpIf);
127
128
        $this->passNodes($while->stmts, $loop);
129
130
        $loop->addChildren(new Node\JumpNode($cond));
131
        //$loop->setExit($cond);
132
133
        $after = new Block($this->lastBlockId++);
134
        $jumpIf->setElse($after);
135
136
        return $after;
137
    }
138
139
    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...
140
    {
141
        $block->addChildren(new Node\ThrowNode());
142
    }
143
144
    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...
145
    {
146
        $block->addChildren(new Node\AssignNode());
147
    }
148
149 3
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $return_ 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...
150
    {
151 3
        $block->addChildren(new Node\ReturnNode());
152 3
    }
153
154
    /**
155
     * @return Block
156
     */
157
    public function getRoot()
158
    {
159
        return $this->root;
160
    }
161
}
162