Node::clone()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 */
7
8
declare(strict_types=1);
9
10
namespace loophp\phptree\Node;
11
12
use InvalidArgumentException;
13
use ReturnTypeWillChange;
14
use Traversable;
15
16
use function array_key_exists;
17
use function count;
18
use function in_array;
19
20
/**
21
 * Class Node.
22
 */
23
class Node implements NodeInterface
24
{
25
    /**
26
     * @var array<\loophp\phptree\Node\NodeInterface>
27
     */
28
    private $children = [];
29
30
    /**
31
     * @var NodeInterface|null
32
     */
33
    private $parent;
34
35
    /**
36
     * Node constructor.
37 73
     */
38
    public function __construct(?NodeInterface $parent = null)
39 73
    {
40 73
        $this->parent = $parent;
41 73
        $this->children = [];
42
    }
43 11
44
    public function __clone()
45
    {
46 11
        /** @var NodeInterface $child */
47 9
        foreach ($this->children as $id => $child) {
48
            $this->children[$id] = $child->clone()->setParent($this);
49 11
        }
50
    }
51 54
52
    public function add(NodeInterface ...$nodes): NodeInterface
53 54
    {
54 54
        foreach ($nodes as $node) {
55
            $this->children[] = $node->setParent($this);
56
        }
57 54
58
        return $this;
59
    }
60 27
61
    public function all(): Traversable
62 27
    {
63
        yield $this;
64
65 27
        /** @var NodeInterface $child */
66 26
        foreach ($this->children() as $child) {
67
            yield from $child->all();
68 27
        }
69
    }
70 50
71
    public function children(): Traversable
72 50
    {
73 50
        yield from $this->children;
74
    }
75 10
76
    public function clone(): NodeInterface
77 10
    {
78
        return clone $this;
79
    }
80 21
81
    public function count(): int
82 21
    {
83
        return iterator_count($this->all()) - 1;
84
    }
85 26
86
    public function degree(): int
87 26
    {
88
        return count($this->children);
89
    }
90 1
91
    public function delete(NodeInterface $node, ?NodeInterface $root = null): ?NodeInterface
92 1
    {
93
        $root = $root ?? $this;
94 1
95 1
        if (null !== ($candidate = $this->find($node))) {
96 1
            if ($candidate === $root) {
97
                throw new InvalidArgumentException('Unable to delete root node.');
98
            }
99 1
100 1
            if (null !== $parent = $candidate->getParent()) {
101
                $parent->remove($node);
102
            }
103 1
104
            return $candidate->setParent(null);
105
        }
106 1
107
        return null;
108
    }
109 3
110
    public function depth(): int
111 3
    {
112
        return iterator_count($this->getAncestors());
113
    }
114 1
115
    public function find(NodeInterface $node): ?NodeInterface
116
    {
117 1
        /** @var NodeInterface $candidate */
118 1
        foreach ($this->all() as $candidate) {
119 1
            if ($candidate === $node) {
120
                return $node;
121
            }
122
        }
123 1
124
        return null;
125
    }
126 5
127
    public function getAncestors(): Traversable
128 5
    {
129
        $node = $this;
130 5
131 5
        while ($node = $node->getParent()) {
132
            yield $node;
133 5
        }
134
    }
135
136
    /**
137
     * @return Traversable<NodeInterface>
138 1
     */
139
    public function getIterator(): Traversable
140 1
    {
141 1
        yield from $this->all();
142
    }
143 16
144
    public function getParent(): ?NodeInterface
145 16
    {
146
        return $this->parent;
147
    }
148 1
149
    public function getSibblings(): Traversable
150 1
    {
151
        $parent = $this->parent;
152 1
153 1
        if (null === $parent) {
154
            return [];
155
        }
156 1
157 1
        foreach ($parent->children() as $child) {
158 1
            if ($child === $this) {
159
                continue;
160
            }
161 1
162
            yield $child;
163 1
        }
164
    }
165 1
166
    public function height(): int
167 1
    {
168
        $height = $this->depth();
169
170 1
        /** @var NodeInterface $child */
171 1
        foreach ($this->children() as $child) {
172
            $height = max($height, $child->height());
173
        }
174 1
175
        return $height;
176
    }
177 11
178
    public function isLeaf(): bool
179 11
    {
180
        return 0 === $this->degree();
181
    }
182 3
183
    public function isRoot(): bool
184 3
    {
185
        return null === $this->parent;
186
    }
187 1
188
    public function label(): string
189 1
    {
190
        return sha1(spl_object_hash($this));
191
    }
192 1
193
    public function level(int $level): Traversable
194
    {
195 1
        /** @var NodeInterface $node */
196 1
        foreach ($this->all() as $node) {
197 1
            if ($node->depth() === $level) {
198
                yield $node;
199
            }
200 1
        }
201
    }
202
203
    /**
204
     * @param mixed $offset
205
     *
206
     * @return bool
207 1
     */
208
    #[ReturnTypeWillChange]
209 1
    public function offsetExists($offset)
210
    {
211
        return array_key_exists($offset, $this->children);
212
    }
213
214
    /**
215
     * @param mixed $offset
216
     *
217 4
     * @return NodeInterface
218
     */
219 4
    #[ReturnTypeWillChange]
220
    public function offsetGet($offset)
221
    {
222
        return $this->children[$offset];
223
    }
224
225
    /**
226 2
     * @param mixed $offset
227
     * @param mixed $value
228 2
     */
229 1
    public function offsetSet($offset, $value): void
230 1
    {
231
        if (!($value instanceof NodeInterface)) {
232
            throw new InvalidArgumentException(
233
                'The value must implements NodeInterface.'
234 2
            );
235 2
        }
236
237
        $this->children[$offset] = $value->setParent($this);
238
    }
239
240
    /**
241
     * @param mixed $offset
242 1
     *
243
     * @return void
244 1
     */
245 1
    #[ReturnTypeWillChange]
246
    public function offsetUnset($offset)
247 6
    {
248
        unset($this->children[$offset]);
249 6
    }
250 6
251 6
    public function remove(NodeInterface ...$nodes): NodeInterface
252
    {
253 6
        $this->children =
254 6
            array_filter(
255
                $this->children,
256
                static function ($child) use ($nodes) {
257 6
                    return !in_array($child, $nodes, true);
258
                }
259
            );
260 1
261
        return $this;
262 1
    }
263 1
264
    public function replace(NodeInterface $node): ?NodeInterface
265
    {
266
        if (null === $parent = $this->getParent()) {
267 1
            return null;
268 1
        }
269 1
270
        // Find the key of the current node in the parent.
271 1
        foreach ($parent->children() as $key => $child) {
272
            if ($this === $child) {
273
                $parent[$key] = $node;
274
275 1
                break;
276
            }
277
        }
278 54
279
        return $parent;
280 54
    }
281
282 54
    public function setParent(?NodeInterface $node): NodeInterface
283
    {
284
        $this->parent = $node;
285 3
286
        return $this;
287 3
    }
288 3
289
    public function withChildren(?NodeInterface ...$nodes): NodeInterface
290 3
    {
291
        $clone = clone $this;
292 3
        $clone->children = [];
293 3
294 3
        $nodes = array_filter($nodes);
295
296
        return [] === $nodes ?
297
            $clone :
298
            $clone->add(...$nodes);
299
    }
300
}
301