Passed
Push — master ( c776c7...570285 )
by Kirill
04:05
created

Traverser::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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