1. <?php
    2. namespace Topxia\Common;
    3. /**
    4. * Class Tree 多叉树 数据结构类
    5. * 对树的操作是递归的,如有特殊需求导致递归会爆栈 可以改用stack来实现递归的效果
    6. * @package Topxia\Common
    7. */
    8. class Tree
    9. {
    10. /**
    11. * @var array<Tree>
    12. */
    13. private $children = array();
    14. /**
    15. * @var Tree
    16. */
    17. private $parent;
    18. /**
    19. * @var mixed
    20. */
    21. public $data;
    22. public function __construct($data = array(), Tree $parent = null)
    23. {
    24. $this->data = $data;
    25. $this->parent = $parent;
    26. }
    27. /**
    28. * Like ArrayToolkit::column
    29. * @param $key
    30. * @return mixed
    31. */
    32. public function column($key)
    33. {
    34. return $this->reduce(function ($ret, $tree) use ($key){
    35. if(!empty($tree->data[$key])){
    36. array_push($ret, $tree->data[$key]);
    37. }
    38. return $ret;
    39. }, array());
    40. }
    41. /**
    42. * each Tree Node call closure
    43. * @param \Closure $closure
    44. * @return $this
    45. */
    46. public function each(\Closure $closure)
    47. {
    48. $closure($this);
    49. foreach ($this->getChildren() as $child) {
    50. $child->each($closure);
    51. }
    52. return $this;
    53. }
    54. /**
    55. * Like array_reduce
    56. * @see http://php.net/manual/zh/function.array-reduce.php
    57. * @param \Closure $closure
    58. * @param null $initial
    59. * @return mixed
    60. */
    61. public function reduce(\Closure $closure, $initial = null)
    62. {
    63. is_null($initial) ? $ret = $this : $ret = $initial;
    64. $ret = $closure($ret ,$this);
    65. foreach ($this->children as $child){
    66. $ret = $child->reduce($closure, $ret);
    67. }
    68. return $ret;
    69. }
    70. /**
    71. * @return array<type($this->data)>
    72. */
    73. public function toArray()
    74. {
    75. $ret = $this->data;
    76. $ret['children'] = array();
    77. foreach ($this->getChildren() as $child) {
    78. array_push($ret['children'], $child->toArray());
    79. }
    80. return $ret;
    81. }
    82. /**
    83. * @param \Closure $closure
    84. * @return Tree
    85. */
    86. public function find(\Closure $closure)
    87. {
    88. if($closure($this)){
    89. return $this;
    90. }
    91. $ret = null;
    92. foreach ($this->children as $child){
    93. $ret = $child->find($closure);
    94. if(!is_null($ret)){
    95. break;
    96. }
    97. }
    98. return $ret;
    99. }
    100. public function findToParent(\Closure $closure)
    101. {
    102. $parent = $this;
    103. $ret = array();
    104. while (!is_null($parent)){
    105. if($closure($parent)){
    106. $ret[] = $parent;
    107. }
    108. $parent = $parent->getParent();
    109. }
    110. return array_pop($ret);
    111. }
    112. public static function buildWithArray(array $input, $rootId = 0, $key = 'id', $parentKey = 'parentId')
    113. {
    114. $root = new self(array(
    115. $key => $rootId
    116. ));
    117. // 方便找到父节点
    118. $map = array(
    119. $rootId => $root
    120. );
    121. $buildingArray = $input;
    122. while (!empty($buildingArray)) {
    123. $buildingCount = count($buildingArray);
    124. foreach ($buildingArray as $index => $value) {
    125. if (isset($map[$value[$parentKey]])) {
    126. $parent = $map[$value[$parentKey]];
    127. $tree = new Tree($value, $parent);
    128. $parent->addChild($tree);
    129. $map[$value[$key]] = $tree;
    130. unset($buildingArray[$index]);
    131. }
    132. }
    133. //一次构建树后剩下元素不变。 说明这些元素的父节点不存在树的节点里,是构建不出的树的
    134. if($buildingCount === count($buildingArray)){
    135. break;
    136. }
    137. }
    138. return $root;
    139. }
    140. /**
    141. * @param Tree $child
    142. * @return $this
    143. */
    144. public function addChild(Tree $child)
    145. {
    146. array_push($this->children, $child);
    147. return $this;
    148. }
    149. /**
    150. * @return array<Tree>
    151. */
    152. public function getChildren()
    153. {
    154. return $this->children;
    155. }
    156. /**
    157. * @return Tree
    158. */
    159. public function getParent()
    160. {
    161. return $this->parent;
    162. }
    163. /**
    164. * @param Tree $parent
    165. * @return $this
    166. */
    167. public function setParent(Tree $parent)
    168. {
    169. $this->parent = $parent;
    170. return $this;
    171. }
    172. public function hasChildren()
    173. {
    174. return !empty($this->children);
    175. }
    176. }