Completed
Pull Request — master (#293)
by Дмитрий
03:03
created

ControlFlowGraph::passNodes()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 28
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 24
nc 8
nop 2
dl 0
loc 28
rs 5.3846
c 0
b 0
f 0
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\Assign;
10
use PHPSA\ControlFlow\Node\Exit_;
11
use PHPSA\ControlFlow\Node\JumpIf;
12
use PHPSA\ControlFlow\Node\Return_;
13
use PHPSA\ControlFlow\Node\Throw_;
14
15
class ControlFlowGraph
16
{
17
    protected $lastBlockId = 1;
18
19
    /**
20
     * @var Block
21
     */
22
    protected $root;
23
24
    public function __construct($statement)
25
    {
26
        $this->root = new Block($this->lastBlockId++);
27
28
        if ($statement instanceof Function_) {
29
            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...
30
                $this->passNodes($statement->stmts, $this->root);
31
            }
32
        }
33
    }
34
35
    protected function passNodes(array $nodes, Block $block)
36
    {
37
        foreach ($nodes as $stmt) {
38
            switch (get_class($stmt)) {
39
                case \PhpParser\Node\Expr\Assign::class:
40
                    $this->passAssign($stmt, $block);
41
                    break;
42
                case \PhpParser\Node\Stmt\Return_::class:
43
                    $this->passReturn($stmt, $block);
44
                    break;
45
                case \PhpParser\Node\Stmt\For_::class:
46
                    $block = $this->passFor($stmt, $block);
47
                    break;
48
                case \PhpParser\Node\Stmt\If_::class:
49
                    $block = $this->passIf($stmt, $block);
50
                    break;
51
                case \PhpParser\Node\Stmt\Throw_::class:
52
                    $this->passThrow($stmt, $block);
53
                    break;
54
                case \PhpParser\Node\Expr\Exit_::class:
55
                    $block->addChildren(new Exit_());
56
                    break;
57
                default:
58
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
59
                    break;
60
            }
61
        }
62
    }
63
64
    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...
65
    {
66
        $jumpIf = new JumpIf($this->lastBlockId++);
0 ignored issues
show
Unused Code introduced by
The call to JumpIf::__construct() has too many arguments starting with $this->lastBlockId++.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
67
68
        $trueBlock = new Block($this->lastBlockId++);
69
        $this->passNodes($if->stmts, $trueBlock);
70
71
        $jumpIf->setIf($trueBlock);
72
73
        $elseBlock = null;
74
75
        if ($if->else) {
76
            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...
77
                $elseBlock = new Block($this->lastBlockId++);
78
                $this->passNodes($if->else->stmts, $elseBlock);
79
80
                $jumpIf->setElse($elseBlock);
81
            }
82
        }
83
84
        $block->addChildren(
85
            $jumpIf
86
        );
87
88
        $exitBlock = new Block($this->lastBlockId++);
89
        $trueBlock->setExit($exitBlock);
90
91
        if ($elseBlock) {
92
            $elseBlock->setExit($exitBlock);
93
        }
94
95
        return $exitBlock;
96
    }
97
98
99
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
100
    {
101
        $this->passNodes($for->init, $block);
102
103
        $block->setExit(
104
            $loop = new Block($this->lastBlockId++)
105
        );
106
        $this->passNodes($for->stmts, $loop);
107
108
        $loop->setExit(
109
            $after = new Block($this->lastBlockId++)
110
        );
111
        return $after;
112
    }
113
114
    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...
115
    {
116
        $block->addChildren(new Throw_());
117
    }
118
119
    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...
120
    {
121
        $block->addChildren(new Assign());
122
    }
123
124
    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...
125
    {
126
        $block->addChildren(new Return_());
127
    }
128
129
    /**
130
     * @return Block
131
     */
132
    public function getRoot()
133
    {
134
        return $this->root;
135
    }
136
}
137