NaryNode::add()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 5
nop 1
dl 0
loc 23
ccs 12
cts 12
cp 1
crap 5
rs 9.5555
c 0
b 0
f 0
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 Exception;
13
use loophp\phptree\Traverser\BreadthFirst;
14
use loophp\phptree\Traverser\TraverserInterface;
15
use OutOfBoundsException;
16
17
class NaryNode extends Node implements NaryNodeInterface
18
{
19
    /**
20
     * @var int
21
     */
22
    private $capacity;
23
24
    /**
25
     * @var TraverserInterface
26
     */
27
    private $traverser;
28
29
    /**
30
     * NaryNode constructor.
31
     *
32
     * @param int $capacity
33
     *   The maximum children a node can have. Null for no children,
34
     *   if 0 then any number of children is allowed.
35
     * @param TraverserInterface|null $traverser
36
     *   The traverser.
37
     * @param NodeInterface|null $parent
38
     *   The parent.
39
     */
40 53
    public function __construct(
41
        int $capacity = 0,
42
        ?TraverserInterface $traverser = null,
43
        ?NodeInterface $parent = null
44
    ) {
45 53
        parent::__construct($parent);
46
47 53
        $this->capacity = $capacity;
48
49 53
        $this->traverser = $traverser ?? new BreadthFirst();
50 53
    }
51
52 35
    public function add(NodeInterface ...$nodes): NodeInterface
53
    {
54 35
        $capacity = $this->capacity();
55
56 35
        if (0 === $capacity) {
57 14
            return parent::add(...$nodes);
58
        }
59
60 23
        foreach ($nodes as $node) {
61 23
            if ($this->degree() < $capacity) {
62 23
                parent::add($node);
63
64 23
                continue;
65
            }
66
67 17
            if (null !== $parent = $this->findFirstAvailableNode($this)) {
68 17
                $parent->add($node);
69
            } else {
70 1
                throw new Exception('Unable to add the node to the tree.');
71
            }
72
        }
73
74 23
        return $this;
75
    }
76
77 39
    public function capacity(): int
78
    {
79 39
        return $this->capacity;
80
    }
81
82 20
    public function getTraverser(): TraverserInterface
83
    {
84 20
        return $this->traverser;
85
    }
86
87 3
    public function offsetSet($offset, $value): void
88
    {
89 3
        if (null === $offset) {
90 2
            $this->add($value);
91
        } else {
92 2
            if (0 !== $this->capacity() && $this->capacity() - 1 < $offset) {
93 1
                throw new OutOfBoundsException('The offset is out of range.');
94
            }
95
96 2
            parent::offsetSet($offset, $value);
97
        }
98 3
    }
99
100
    /**
101
     * Find the first available node in the tree.
102
     *
103
     * When adding nodes to a NaryNode based tree, you must traverse the tree
104
     * and find the first node that can be used as a parent for the node to add.
105
     *
106
     * @param NodeInterface $tree
107
     *   The base node.
108
     *
109
     * @return NodeInterface|null
110
     *   A node, null if none are found.
111
     */
112 17
    protected function findFirstAvailableNode(NodeInterface $tree): ?NodeInterface
113
    {
114 17
        foreach ($this->getTraverser()->traverse($tree) as $candidate) {
115 17
            if (!($candidate instanceof NaryNodeInterface)) {
116 2
                continue;
117
            }
118
119 17
            $capacity = $candidate->capacity();
120
121 17
            if (null === $capacity) {
122
                continue;
123
            }
124
125 17
            if (0 !== $capacity && $candidate->degree() >= $capacity) {
126 17
                continue;
127
            }
128
129 17
            return $candidate;
130
        }
131
132 1
        return null;
133
    }
134
}
135