Add abstract object tree node used for selection form fields
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / TObjectTreeNode.class.php
CommitLineData
755d49f0
MS
1<?php
2namespace wcf\data;
3use wcf\util\ClassUtil;
4
5/**
6 * Default implementation of `IObjectTreeNode`.
7 *
8 * @author Matthias Schmidt
9 * @copyright 2001-2019 WoltLab GmbH
10 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11 * @package WoltLabSuite\Core\Data
12 * @since 5.2
13 */
14trait TObjectTreeNode {
15 /**
16 * child nodes
17 * @var TObjectTreeNode[]
18 */
19 protected $children = [];
20
21 /**
22 * current iterator key
23 * @var integer
24 */
25 protected $index = 0;
26
27 /**
28 * parent node object
29 * @var TObjectTreeNode
30 */
31 protected $parentNode = null;
32
33 /**
34 * Adds the given node as child node and sets the child node's parent node to this node.
35 *
36 * @param IObjectTreeNode $child added child node
37 * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class
38 */
39 public function addChild(IObjectTreeNode $child) {
40 if (!($child instanceof $this) && !ClassUtil::isDecoratedInstanceOf($child, static::class)) {
41 throw new \InvalidArgumentException("Child has to be a (decorated) instance of '" . static::class . "', but instance of '" . get_class($child) . "' given.");
42 }
43
44 $child->setParentNode($this);
45
46 $this->children[] = $child;
47 }
48
49 /**
50 * Returns the number of child nodes.
51 *
52 * @return integer
53 */
54 public function count() {
55 return count($this->children);
56 }
57
58 /**
59 * Return the currently iterated child node.
60 *
61 * @return IObjectTreeNode
62 */
63 public function current() {
64 return $this->children[$this->index];
65 }
66
67 /**
68 * Returns an iterator for the currently iterated child node by returning the node itself.
69 *
70 * @return IObjectTreeNode
71 */
72 public function getChildren() {
73 return $this->children[$this->index];
74 }
75
76 /**
77 * Returns the depth of the node within the tree.
78 *
79 * The minimum depth is `1`.
80 *
81 * @return integer
82 */
83 public function getDepth() {
84 $element = $this;
85 $depth = 1;
86
87 while ($element->parentNode->parentNode !== null) {
88 $depth++;
89 $element = $element->parentNode;
90 }
91
92 return $depth;
93 }
94
95 /**
96 * Returns the number of open parent nodes.
97 *
98 * @return integer
99 */
100 public function getOpenParentNodes() {
101 $element = $this;
102 $i = 0;
103
104 while ($element->parentNode->parentNode !== null && $element->isLastSibling()) {
105 $i++;
106 $element = $element->parentNode;
107 }
108
109 return $i;
110 }
111
112 /**
113 * Returns `true` if the node as any children and return `false` otherwise.
114 *
115 * @return boolean
116 */
117 public function hasChildren() {
118 return !empty($this->children);
119 }
120
121 /**
122 * Return the key of the currently iterated child node.
123 *
124 * @return integer
125 */
126 public function key() {
127 return $this->index;
128 }
129
130 /**
131 * Returns `true` if this node is the last sibling and `false` otherwise.
132 *
133 * @return boolean
134 */
135 public function isLastSibling() {
136 foreach ($this->parentNode as $key => $child) {
137 if ($child === $this) {
138 return $key === count($this->parentNode) - 1;
139 }
140 }
141
142 throw new \LogicException("Unreachable");
143 }
144
145 /**
146 * Moves the iteration forward to next child node.
147 */
148 public function next() {
149 $this->index++;
150 }
151
152 /**
153 * Rewind the iteration to the first child node.
154 */
155 public function rewind() {
156 $this->index = 0;
157 }
158
159 /**
160 * Sets the parent node of this node.
161 *
162 * @param IObjectTreeNode $parentNode parent node
163 * @throws \InvalidArgumentException if given object is no (deocrated) instance of this class
164 */
165 public function setParentNode(IObjectTreeNode $parentNode) {
166 if (!($parentNode instanceof $this) && !ClassUtil::isDecoratedInstanceOf($parentNode, static::class)) {
167 throw new \InvalidArgumentException("Parent has to be a (decorated) instance of '" . static::class . "', but instance of '" . get_class($parentNode) . "' given.");
168 }
169
170 $this->parentNode = $parentNode;
171 }
172
173 /**
174 * Returns `true` if current iteration position is valid and `false` otherwise.
175 *
176 * @return boolean
177 */
178 public function valid() {
179 return isset($this->children[$this->index]);
180 }
181}