Traverser   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Test Coverage

Coverage 56.64%

Importance

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

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A removeVisitor() 0 6 3
A addVisitor() 0 3 1
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
19
    private bool $stopTraversal = false;
20
21 132
    public function __construct(array $visitors = [])
22
    {
23 132
        foreach ($visitors as $visitor) {
24 114
            $this->addVisitor($visitor);
25
        }
26
    }
27
28
    /**
29
     * Adds visitor.
30
     */
31 132
    public function addVisitor(VisitorInterface $visitor): void
32
    {
33 132
        $this->visitors[] = $visitor;
34
    }
35
36
    public function removeVisitor(VisitorInterface $visitor): void
37
    {
38
        foreach ($this->visitors as $index => $added) {
39
            if ($added === $visitor) {
40
                unset($this->visitors[$index]);
41
                break;
42
            }
43
        }
44
    }
45
46
    /**
47
     * Traverses an array of nodes using added visitors.
48
     *
49
     * @template TNode of NodeInterface
50
     *
51
     * @param TNode[] $nodes
52
     * @return NodeInterface[]
53
     * @throws \Throwable
54
     */
55 119
    public function traverse(array $nodes, ?VisitorContext $context = null): array
56
    {
57 119
        $context ??= new VisitorContext();
58
59 119
        $ctx = clone $context;
60 119
        foreach ($nodes as $index => $node) {
61 119
            if ($this->stopTraversal) {
62
                break;
63
            }
64
65 119
            $traverseChildren = true;
66 119
            $breakVisitorID = null;
67
68 119
            if ($node instanceof NodeInterface) {
69 118
                $ctx = $context->withNode($node);
70
            }
71
72 119
            foreach ($this->visitors as $visitorID => $visitor) {
73 119
                $result = $visitor->enterNode($node, $ctx);
74
75
                switch (true) {
76
                    case $result === null:
77 111
                        break;
78
79 84
                    case $result instanceof NodeInterface:
80 77
                        $node = $result;
81 77
                        break;
82
83 53
                    case $result === VisitorInterface::DONT_TRAVERSE_CHILDREN:
84
                        $traverseChildren = false;
85
                        break;
86
87 53
                    case $result === VisitorInterface::DONT_TRAVERSE_CURRENT_AND_CHILDREN:
88 53
                        $traverseChildren = false;
89 53
                        $breakVisitorID = $visitorID;
90 53
                        break 2;
91
92
                    case $result === VisitorInterface::STOP_TRAVERSAL:
93
                        $this->stopTraversal = true;
94
95
                        break 3;
96
97
                    default:
98
                        throw new \LogicException(
99
                            'enterNode() returned invalid value of type ' . \gettype($result),
100
                        );
101
                }
102
            }
103
104
            // sub nodes
105 119
            if ($traverseChildren && $node instanceof NodeInterface) {
106 118
                $nodes[$index] = $this->traverseNode($node, $ctx);
107 118
                if ($this->stopTraversal) {
108
                    break;
109
                }
110
            }
111
112 119
            foreach ($this->visitors as $visitorID => $visitor) {
113 119
                $result = $visitor->leaveNode($node, $ctx);
114
115
                switch (true) {
116
                    case $result === null:
117 111
                        break;
118
119 86
                    case $result instanceof NodeInterface:
120 67
                        $nodes[$index] = $result;
121 67
                        break;
122
123 64
                    case $result === VisitorInterface::REMOVE_NODE:
124 64
                        unset($nodes[$index]);
125 64
                        break;
126
127
                    case $result === VisitorInterface::STOP_TRAVERSAL:
128
                        $this->stopTraversal = true;
129
                        break 3;
130
131
                    default:
132
                        throw new \LogicException(
133
                            'leaveNode() returned invalid value of type ' . \gettype($result),
134
                        );
135
                }
136
137 119
                if ($breakVisitorID === $visitorID) {
138 49
                    break;
139
                }
140
            }
141
        }
142
143 119
        return \array_values($nodes);
144
    }
145
146
    /**
147
     * Recursively traverse a node.
148
     */
149 118
    private function traverseNode(NodeInterface $node, VisitorContext $context): NodeInterface
150
    {
151 118
        $ctx = clone $context;
152 118
        foreach ($node as $name => $_) {
153 118
            $_child = &$node->$name;
154 118
            if (\is_array($_child)) {
155 118
                $_child = $this->traverse($_child, $ctx);
156 118
                if ($this->stopTraversal) {
157
                    break;
158
                }
159
160 118
                continue;
161
            }
162
163 89
            if (!$_child instanceof NodeInterface) {
164 89
                continue;
165
            }
166
167 42
            $ctx = $context->withNode($_child);
168
169 42
            $traverseChildren = true;
170 42
            $breakVisitorID = null;
171
172 42
            foreach ($this->visitors as $visitorID => $visitor) {
173 42
                $result = $visitor->enterNode($_child, $ctx);
174
                switch (true) {
0 ignored issues
show
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...
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...: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...
175
                    case $result === null:
176 42
                        break;
177
178
                    case $result instanceof NodeInterface:
179
                        $_child = $result;
180
                        break;
181
182
                    case VisitorInterface::DONT_TRAVERSE_CHILDREN:
183
                        $traverseChildren = false;
184
                        break;
185
186
                    case VisitorInterface::DONT_TRAVERSE_CURRENT_AND_CHILDREN:
187
                        $traverseChildren = false;
188
                        $breakVisitorID = $visitorID;
189
                        break 2;
190
191
                    case VisitorInterface::STOP_TRAVERSAL:
192
                        $this->stopTraversal = true;
193
                        break 3;
194
195
                    default:
196
                        throw new \LogicException(
197
                            'enterNode() returned invalid value of type ' . \gettype($result),
198
                        );
199
                }
200
            }
201
202 42
            if ($traverseChildren) {
203 42
                $_child = $this->traverseNode($_child, $ctx);
204 42
                if ($this->stopTraversal) {
205
                    break;
206
                }
207
            }
208
209 42
            foreach ($this->visitors as $visitorID => $visitor) {
210 42
                $result = $visitor->leaveNode($_child, $ctx);
211
212
                switch (true) {
213
                    case $result === null:
214 42
                        break;
215
216
                    case $result instanceof NodeInterface:
217
                        $_child = $result;
218
                        break;
219
220
                    case $result === VisitorInterface::STOP_TRAVERSAL:
221
                        $this->stopTraversal = true;
222
                        break 3;
223
224
                    default:
225
                        throw new \LogicException(
226
                            'leaveNode() returned invalid value of type ' . \gettype($result),
227
                        );
228
                }
229
230 42
                if ($breakVisitorID === $visitorID) {
231
                    break;
232
                }
233
            }
234
        }
235
236 118
        return $node;
237
    }
238
}
239