Passed
Push — master ( 19278e...10e1cd )
by butschster
07:45
created

Traverser   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Test Coverage

Coverage 56.64%

Importance

Changes 0
Metric Value
wmc 43
eloc 119
dl 0
loc 222
ccs 64
cts 113
cp 0.5664
rs 8.96
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A addVisitor() 0 3 1
A __construct() 0 4 2
A removeVisitor() 0 6 3
D traverseNode() 0 88 18
D traverse() 0 89 19

How to fix   Complexity   

Complex Class

Complex classes like Traverser 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.

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 Traverser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Stempler;
6
7
use Spiral\Stempler\Node\NodeInterface;
8
9
/**
10
 * Inspired by php-parser.
11
 *
12
 * @see https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/NodeTraverser.php
13
 */
14
final class Traverser
15
{
16
    /** @var VisitorInterface[] */
17
    private array $visitors = [];
18
    private bool $stopTraversal = false;
19
20 116
    public function __construct(array $visitors = [])
21
    {
22 116
        foreach ($visitors as $visitor) {
23 90
            $this->addVisitor($visitor);
24
        }
25
    }
26
27
    /**
28
     * Adds visitor.
29
     */
30 116
    public function addVisitor(VisitorInterface $visitor): void
31
    {
32 116
        $this->visitors[] = $visitor;
33
    }
34
35
    public function removeVisitor(VisitorInterface $visitor): void
36
    {
37
        foreach ($this->visitors as $index => $added) {
38
            if ($added === $visitor) {
39
                unset($this->visitors[$index]);
40
                break;
41
            }
42
        }
43
    }
44
45
    /**
46
     * Traverses an array of nodes using added visitors.
47
     *
48
     * @template TNode of NodeInterface
49
     *
50
     * @param TNode[] $nodes
51
     * @return NodeInterface[]
52
     * @throws \Throwable
53
     */
54 95
    public function traverse(array $nodes, VisitorContext $context = null): array
55
    {
56 95
        $context ??= new VisitorContext();
57
58 95
        $ctx = clone $context;
59 95
        foreach ($nodes as $index => $node) {
60 95
            if ($this->stopTraversal) {
61
                break;
62
            }
63
64 95
            $traverseChildren = true;
65 95
            $breakVisitorID = null;
66
67 95
            if ($node instanceof NodeInterface) {
68 94
                $ctx = $context->withNode($node);
69
            }
70
71 95
            foreach ($this->visitors as $visitorID => $visitor) {
72 95
                $result = $visitor->enterNode($node, $ctx);
73
74
                switch (true) {
75
                    case $result === null:
76 95
                        break;
77
78 74
                    case $result instanceof NodeInterface:
79 68
                        $node = $result;
80 68
                        break;
81
82 52
                    case $result === VisitorInterface::DONT_TRAVERSE_CHILDREN:
83
                        $traverseChildren = false;
84
                        break;
85
86 52
                    case $result === VisitorInterface::DONT_TRAVERSE_CURRENT_AND_CHILDREN:
87 52
                        $traverseChildren = false;
88 52
                        $breakVisitorID = $visitorID;
89 52
                        break 2;
90
91
                    case $result === VisitorInterface::STOP_TRAVERSAL:
92
                        $this->stopTraversal = true;
93
94
                        break 3;
95
96
                    default:
97
                        throw new \LogicException(
98
                            'enterNode() returned invalid value of type ' . \gettype($result)
99
                        );
100
                }
101
            }
102
103
            // sub nodes
104 95
            if ($traverseChildren && $node instanceof NodeInterface) {
105 94
                $nodes[$index] = $this->traverseNode($node, $ctx);
106 94
                if ($this->stopTraversal) {
107
                    break;
108
                }
109
            }
110
111 95
            foreach ($this->visitors as $visitorID => $visitor) {
112 95
                $result = $visitor->leaveNode($node, $ctx);
113
114
                switch (true) {
115
                    case $result === null:
116 95
                        break;
117
118 72
                    case $result instanceof NodeInterface:
119 53
                        $nodes[$index] = $result;
120 53
                        break;
121
122 63
                    case $result === VisitorInterface::REMOVE_NODE:
123 63
                        unset($nodes[$index]);
124 63
                        break;
125
126
                    case $result === VisitorInterface::STOP_TRAVERSAL:
127
                        $this->stopTraversal = true;
128
                        break 3;
129
130
                    default:
131
                        throw new \LogicException(
132
                            'leaveNode() returned invalid value of type ' . gettype($result)
133
                        );
134
                }
135
136 95
                if ($breakVisitorID === $visitorID) {
137 48
                    break;
138
                }
139
            }
140
        }
141
142 95
        return \array_values($nodes);
143
    }
144
145
    /**
146
     * Recursively traverse a node.
147
     */
148 94
    private function traverseNode(NodeInterface $node, VisitorContext $context): NodeInterface
149
    {
150 94
        $ctx = clone $context;
151 94
        foreach ($node as $name => $_) {
152 94
            $child = &$node->$name;
153 94
            if (is_array($child)) {
154 94
                $child = $this->traverse($child, $ctx);
155 94
                if ($this->stopTraversal) {
156
                    break;
157
                }
158
159 94
                continue;
160
            }
161
162 83
            if (!$child instanceof NodeInterface) {
163 83
                continue;
164
            }
165
166 42
            $ctx = $context->withNode($child);
167
168 42
            $traverseChildren = true;
169 42
            $breakVisitorID = null;
170
171 42
            foreach ($this->visitors as $visitorID => $visitor) {
172 42
                $result = $visitor->enterNode($child, $ctx);
173
                switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing Spiral\Stempler\VisitorI...:DONT_TRAVERSE_CHILDREN of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Spiral\Stempler\VisitorInterface::STOP_TRAVERSAL of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Spiral\Stempler\VisitorI...SE_CURRENT_AND_CHILDREN of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
174
                    case $result === null:
175 42
                        break;
176
177
                    case $result instanceof NodeInterface:
178
                        $child = $result;
179
                        break;
180
181
                    case VisitorInterface::DONT_TRAVERSE_CHILDREN:
182
                        $traverseChildren = false;
183
                        break;
184
185
                    case VisitorInterface::DONT_TRAVERSE_CURRENT_AND_CHILDREN:
186
                        $traverseChildren = false;
187
                        $breakVisitorID = $visitorID;
188
                        break 2;
189
190
                    case VisitorInterface::STOP_TRAVERSAL:
191
                        $this->stopTraversal = true;
192
                        break 3;
193
194
                    default:
195
                        throw new \LogicException(
196
                            'enterNode() returned invalid value of type ' . \gettype($result)
197
                        );
198
                }
199
            }
200
201 42
            if ($traverseChildren) {
202 42
                $child = $this->traverseNode($child, $ctx);
203 42
                if ($this->stopTraversal) {
204
                    break;
205
                }
206
            }
207
208 42
            foreach ($this->visitors as $visitorID => $visitor) {
209 42
                $result = $visitor->leaveNode($child, $ctx);
210
211
                switch (true) {
212
                    case $result === null:
213 42
                        break;
214
215
                    case $result instanceof NodeInterface:
216
                        $child = $result;
217
                        break;
218
219
                    case $result === VisitorInterface::STOP_TRAVERSAL:
220
                        $this->stopTraversal = true;
221
                        break 3;
222
223
                    default:
224
                        throw new \LogicException(
225
                            'leaveNode() returned invalid value of type ' . \gettype($result)
226
                        );
227
                }
228
229 42
                if ($breakVisitorID === $visitorID) {
230
                    break;
231
                }
232
            }
233
        }
234
235 94
        return $node;
236
    }
237
}
238