xref: /plugin/combo/ComboStrap/TreeNode.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeaunamespace ComboStrap;
4*04fd306cSNickeau
5*04fd306cSNickeau/**
6*04fd306cSNickeau * A class to modeling a tree
7*04fd306cSNickeau */
8*04fd306cSNickeauabstract class TreeNode
9*04fd306cSNickeau{
10*04fd306cSNickeau
11*04fd306cSNickeau    /**
12*04fd306cSNickeau     * @var TreeNode[]
13*04fd306cSNickeau     */
14*04fd306cSNickeau    private array $children = [];
15*04fd306cSNickeau
16*04fd306cSNickeau    private TreeNode $parentNode;
17*04fd306cSNickeau
18*04fd306cSNickeau    /**
19*04fd306cSNickeau     * @var int the child number when added
20*04fd306cSNickeau     * used to get a unique identifier in the tree
21*04fd306cSNickeau     */
22*04fd306cSNickeau    private int $levelChildIdentifier;
23*04fd306cSNickeau
24*04fd306cSNickeau
25*04fd306cSNickeau    /**
26*04fd306cSNickeau     * @throws ExceptionBadState - if the node has already been added and is not the same than in the tree
27*04fd306cSNickeau     */
28*04fd306cSNickeau    public function appendChild(TreeNode $treeNode): TreeNode
29*04fd306cSNickeau    {
30*04fd306cSNickeau        try {
31*04fd306cSNickeau            $levelChildIdentifier = $treeNode->getLevelChildIdentifier();
32*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
33*04fd306cSNickeau            // no level child identifier
34*04fd306cSNickeau            $childCount = count($this->children);
35*04fd306cSNickeau            $levelChildIdentifier = $childCount + 1;
36*04fd306cSNickeau            $treeNode->setLevelChildIdentifier($levelChildIdentifier);
37*04fd306cSNickeau            $treeNode->setParent($this);
38*04fd306cSNickeau            $this->children[$levelChildIdentifier] = $treeNode;
39*04fd306cSNickeau            return $this;
40*04fd306cSNickeau        }
41*04fd306cSNickeau        $actualTreeNode = $this->children[$levelChildIdentifier];
42*04fd306cSNickeau        if ($actualTreeNode !== $treeNode) {
43*04fd306cSNickeau            throw new ExceptionBadState("The node ($treeNode) was already added but not on this level");
44*04fd306cSNickeau        }
45*04fd306cSNickeau        return $this;
46*04fd306cSNickeau    }
47*04fd306cSNickeau
48*04fd306cSNickeau    public function getLocalId()
49*04fd306cSNickeau    {
50*04fd306cSNickeau
51*04fd306cSNickeau    }
52*04fd306cSNickeau
53*04fd306cSNickeau
54*04fd306cSNickeau    public
55*04fd306cSNickeau    function __toString()
56*04fd306cSNickeau    {
57*04fd306cSNickeau        return $this->getTreeIdentifier();
58*04fd306cSNickeau    }
59*04fd306cSNickeau
60*04fd306cSNickeau
61*04fd306cSNickeau    public
62*04fd306cSNickeau    function getChildCount(): int
63*04fd306cSNickeau    {
64*04fd306cSNickeau        return sizeof($this->children);
65*04fd306cSNickeau    }
66*04fd306cSNickeau
67*04fd306cSNickeau    /**
68*04fd306cSNickeau     * @return TreeNode[] - empty array for a leaf
69*04fd306cSNickeau     */
70*04fd306cSNickeau    public function getChildren(): array
71*04fd306cSNickeau    {
72*04fd306cSNickeau        return $this->children;
73*04fd306cSNickeau    }
74*04fd306cSNickeau
75*04fd306cSNickeau
76*04fd306cSNickeau    public function hasChildren(): bool
77*04fd306cSNickeau    {
78*04fd306cSNickeau        return sizeof($this->children) > 0;
79*04fd306cSNickeau    }
80*04fd306cSNickeau
81*04fd306cSNickeau    public function hasParent(): bool
82*04fd306cSNickeau    {
83*04fd306cSNickeau        return isset($this->parentNode);
84*04fd306cSNickeau    }
85*04fd306cSNickeau
86*04fd306cSNickeau    /**
87*04fd306cSNickeau     * @return TreeNode
88*04fd306cSNickeau     * @throws ExceptionNotFound
89*04fd306cSNickeau     */
90*04fd306cSNickeau    public function getFirstChild(): TreeNode
91*04fd306cSNickeau    {
92*04fd306cSNickeau        $childrenKeys = array_keys($this->children);
93*04fd306cSNickeau        if (sizeof($childrenKeys) === 0) {
94*04fd306cSNickeau            throw new ExceptionNotFound("This node has no child");
95*04fd306cSNickeau        }
96*04fd306cSNickeau        return $this->children[$childrenKeys[0]];
97*04fd306cSNickeau    }
98*04fd306cSNickeau
99*04fd306cSNickeau    /**
100*04fd306cSNickeau     * A hierarchical tree identifier composed
101*04fd306cSNickeau     * of the node id of each level separated by a point
102*04fd306cSNickeau     * @return string
103*04fd306cSNickeau     */
104*04fd306cSNickeau    function getTreeIdentifier(): string
105*04fd306cSNickeau    {
106*04fd306cSNickeau
107*04fd306cSNickeau        $treeIdentifier = $this->getLevelChildIdentifier();
108*04fd306cSNickeau        $parent = $this;
109*04fd306cSNickeau        while (true) {
110*04fd306cSNickeau            try {
111*04fd306cSNickeau                $parent = $parent->getParent();
112*04fd306cSNickeau                $parentLevelNodeIdentifier = $parent->getLevelChildIdentifier();
113*04fd306cSNickeau                if ($parentLevelNodeIdentifier !== "") {
114*04fd306cSNickeau                    $treeIdentifier = "{$parentLevelNodeIdentifier}.$treeIdentifier";
115*04fd306cSNickeau                } else {
116*04fd306cSNickeau                    $treeIdentifier = "$treeIdentifier";
117*04fd306cSNickeau                }
118*04fd306cSNickeau            } catch (ExceptionNotFound $e) {
119*04fd306cSNickeau                // no parent anymore
120*04fd306cSNickeau                break;
121*04fd306cSNickeau            }
122*04fd306cSNickeau
123*04fd306cSNickeau        }
124*04fd306cSNickeau        return $treeIdentifier;
125*04fd306cSNickeau    }
126*04fd306cSNickeau
127*04fd306cSNickeau
128*04fd306cSNickeau    private function setParent(TreeNode $parent)
129*04fd306cSNickeau    {
130*04fd306cSNickeau        $this->parentNode = $parent;
131*04fd306cSNickeau    }
132*04fd306cSNickeau
133*04fd306cSNickeau    /**
134*04fd306cSNickeau     * @throws ExceptionNotFound
135*04fd306cSNickeau     */
136*04fd306cSNickeau    public function getParent(): TreeNode
137*04fd306cSNickeau    {
138*04fd306cSNickeau        if (!isset($this->parentNode)) {
139*04fd306cSNickeau            throw new ExceptionNotFound("No parent node");
140*04fd306cSNickeau        }
141*04fd306cSNickeau        return $this->parentNode;
142*04fd306cSNickeau    }
143*04fd306cSNickeau
144*04fd306cSNickeau    private function setLevelChildIdentifier(int $levelIdentifier)
145*04fd306cSNickeau    {
146*04fd306cSNickeau        $this->levelChildIdentifier = $levelIdentifier;
147*04fd306cSNickeau    }
148*04fd306cSNickeau
149*04fd306cSNickeau    public function detachBeforeAppend(){
150*04fd306cSNickeau
151*04fd306cSNickeau        unset($this->parentNode->children[$this->levelChildIdentifier]);
152*04fd306cSNickeau        unset($this->levelChildIdentifier);
153*04fd306cSNickeau        unset($this->parentNode);
154*04fd306cSNickeau
155*04fd306cSNickeau    }
156*04fd306cSNickeau
157*04fd306cSNickeau    /**
158*04fd306cSNickeau     * @throws ExceptionNotFound
159*04fd306cSNickeau     */
160*04fd306cSNickeau    private function getLevelChildIdentifier(): int
161*04fd306cSNickeau    {
162*04fd306cSNickeau        if (!isset($this->levelChildIdentifier)) {
163*04fd306cSNickeau            throw new ExceptionNotFound("No child identifier level found");
164*04fd306cSNickeau        }
165*04fd306cSNickeau        return $this->levelChildIdentifier;
166*04fd306cSNickeau    }
167*04fd306cSNickeau
168*04fd306cSNickeau
169*04fd306cSNickeau}
170