NodeSearch::update()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 0
crap 2
1
<?php
2
3
4
namespace JanPiet\PhpTranspiler;
5
6
use PhpParser\Node;
7
8
class NodeSearch
9
{
10
    /**
11
     * @var \PhpParser\Node[]
12
     */
13
    private $tree;
14
15
    /**
16
     * @var Node[]
17
     */
18
    private $allNodes;
19
20
    /**
21
     * @var Node[]
22
     */
23
    private $parentChain;
24
25
    /**
26
     * NodeSearch constructor.
27
     * @param Node[] $nodes
28
     */
29 21
    public function __construct(array $nodes)
30
    {
31 21
        $this->tree = $nodes;
32 21
    }
33
34
    /**
35
     * @param \string[] ...$classNames
36
     * @return \Traversable
37
     */
38 19
    public function eachType(string ...$classNames): \Traversable
39
    {
40 19
        $this->update();
41
42 19
        foreach ($this->allNodes as $node) {
43 19
            $found = false;
44 19
            foreach ($classNames as $className) {
45 19
                if ($node instanceof  $className) {
46 19
                    $found = true;
47 19
                    break;
48
                }
49
            }
50
51 19
            if (!$found) {
52 18
                continue;
53
            }
54
55 19
            yield $node;
56
        }
57 19
    }
58
59 5
    public function findParent(string $class, Node $fromNode)
60
    {
61 5
        $this->update();
62
63
        do {
64 5
            $nodeId = spl_object_hash($fromNode);
65 5
            $fromNode = $this->parentChain[$nodeId];
66
            
67 5
            if (null === $fromNode) {
68 3
                throw new ParentNotFoundException('Could not find the parent "' . $class . '" for node');
69
            }
70 4
        } while (!$fromNode instanceof $class);
71
72 3
        return $fromNode;
73
    }
74
75
    /**
76
     * @param Node $newClass
77
     */
78 2
    public function appendToRoot(Node $newClass)
79
    {
80 2
        $this->tree[] = $newClass;
81 2
    }
82
83
    /**
84
     * @return \PhpParser\Node[]
85
     */
86 15
    public function getTree()
87
    {
88 15
        return $this->tree;
89
    }
90
91 21
    private function update()
92
    {
93 21
        $allNodes = [];
94 21
        $parentChain = [];
95
96 21
        foreach ($this->recurse($this->tree) as $parent => $node) {
97 21
            $nodeId = spl_object_hash($node);
98 21
            $allNodes[$nodeId] = $node;
99 21
            $parentChain[$nodeId] = $parent;
100
        }
101
102 21
        $this->allNodes = $allNodes;
103 21
        $this->parentChain = $parentChain;
104 21
    }
105
106
    /**
107
     * @param Node[] $nodes
108
     * @return \Traversable
109
     */
110 21
    private function recurse(array $nodes, Node $parent = null): \Traversable
111
    {
112 21
        foreach ($nodes as $node) {
113 21
            if (is_array($node)) {
114
                yield from $this->recurse($node, $node);
0 ignored issues
show
Documentation introduced by
$node is of type array, but the function expects a null|object<PhpParser\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
115
            } elseif ($node instanceof Node) {
116 21
                yield $parent => $node;
117
118 21
                $subNodeNames = $node->getSubNodeNames();
119
120 21
                foreach ($subNodeNames as $subNodeName) {
121 21
                    $subNode = $node->{$subNodeName};
122
123 21
                    if (!is_array($subNode)) {
124 21
                        $subNode = [$subNode];
125
                    }
126
127 21
                    yield from $this->recurse($subNode, $node);
128
                }
129
            }
130
        }
131 21
    }
132
133
    /**
134
     * @param $sourceNode
135
     * @param $newNode
136
     */
137 5
    public function replaceNode(Node $sourceNode, Node $newNode)
138
    {
139 5
        $this->update();
140
141 5
        $hash = spl_object_hash($sourceNode);
142
143 5
        $parents = $this->determineReplaceParents($hash);
144
145 5
        foreach ($parents as $key => $parent) {
146 5
            if ($parent === $sourceNode) {
147
                $parent[$key] = $newNode;
148
                return;
149
            }
150
151 5
            if ($this->checkReplaceSubNodes($parent, $sourceNode, $newNode)) {
152 5
                return;
153
            }
154
        }
155
        
156 1
        throw new NodeNotFoundException('Node not found, replace not possible');
157
    }
158
159
    /**
160
     * @param Node $parent
161
     * @param Node $sourceNode
162
     * @param Node $newNode
163
     * @return bool
164
     */
165 5
    private function checkReplaceSubNodes(Node $parent, Node $sourceNode, Node $newNode): bool
166
    {
167 5
        foreach ($parent->getSubNodeNames() as $subNodeName) {
168 5
            $nodes = &$parent->{$subNodeName};
169
170 5
            if (!is_array($nodes)) {
171 4
                if ($nodes === $sourceNode) {
172 2
                    $parent->{$subNodeName} = $newNode;
173 2
                    return true;
174
                }
175
176 4
                continue;
177
            }
178
179 4
            $foundKey = false;
180 4
            foreach ($nodes as $key => $node) {
181 4
                if ($node === $sourceNode) {
182 4
                    $foundKey = $key;
183
                }
184
            }
185
186 4
            if (false !== $foundKey) {
187 3
                $nodes[$foundKey] = $newNode;
188 4
                return true;
189
            }
190
        }
191
192 1
        return false;
193
    }
194
195
    /**
196
     * @param string $hash
197
     * @return \PhpParser\Node[]
198
     */
199 5
    private function determineReplaceParents(string $hash): array
200
    {
201 5
        if (array_key_exists($hash, $this->parentChain)) {
202 4
            $parents = [$this->parentChain[$hash]];
203 4
            return $parents;
204
        }
205
206 1
        $parents = $this->tree;
207 1
        return $parents;
208
    }
209
210 2
    public function removeNode(Node $nodeToRemove)
211
    {
212
        /**
213
         * @var Node $parent
214
         * @var Node $node
215
         */
216 2
        foreach ($this->recurse($this->tree) as $parent => $node) {
217 2
            if ($node !== $nodeToRemove) {
218
                continue;
219
            }
220
221 2
            if (null == $parent) {
222 2
                $index = array_search($node, $this->tree);
223
224 2
                if (false === $index) {
225
                    throw new \LogicException('Unable to find node.');
226
                }
227
228 2
                unset($this->tree[$index]);
229 2
                return;
230
            }
231
232
            throw new \Exception('Unsupported case');
233
        }
234
    }
235
}
236