Tree.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. namespace laytp\library;
  3. /**
  4. * 通用树型类 - laytp极速后台框架
  5. * @Author: JunStar
  6. * @Version: 1.0.0
  7. * @Date: 2022-1-7 21:48:38
  8. * @LastModified by: JunStar
  9. * @LastModified time: 2022-1-8 21:45:22
  10. */
  11. class Tree
  12. {
  13. protected static $instance;
  14. /**
  15. * 生成树型结构所需要的2维数组
  16. * @var array
  17. */
  18. public $map = [];
  19. public $mapName = 'id';
  20. public $pidName = 'pid';
  21. public $childName = 'children';
  22. /**
  23. * 初始化
  24. * @access public
  25. * @return Tree
  26. */
  27. public static function instance()
  28. {
  29. if (is_null(self::$instance)) {
  30. self::$instance = new static();
  31. }
  32. return self::$instance;
  33. }
  34. /**
  35. * 初始化方法
  36. * 注意,参数一定要是数组,数据库select()->toArray();否则调用getRootTree会内存溢出
  37. * @param array 2维数组,例如:
  38. * array(
  39. * 1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'),
  40. * 2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'),
  41. * 3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'),
  42. * 4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'),
  43. * 5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'),
  44. * 6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'),
  45. * 7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二')
  46. * )
  47. * @return $this
  48. */
  49. public function init($arr = [])
  50. {
  51. $map = [];
  52. //生成以id为key的map
  53. foreach ($arr as &$it){
  54. $map[$it['id']] = &$it;
  55. }
  56. $this->map = $map;
  57. return $this;
  58. }
  59. /**
  60. * 获取所有根树
  61. * @return array
  62. */
  63. public function getRootTrees()
  64. {
  65. $map = $this->map;
  66. $res = [];
  67. $rootTree = [];
  68. foreach ($map as $id => &$item) {
  69. // 获取出每一条数据的父id
  70. $pid = &$item[$this->pidName];
  71. // 如果在map中没有设置过当前$item的pid索引, 说明$item是根节点
  72. if(!isset($map[$pid])){
  73. //将根节点的item的引用保存到$res中
  74. $res[] = &$item;
  75. }else{
  76. // 如果在map中有设置过当前$item的pid索引, 则将当前item加入到他父亲的叶子节点中
  77. // 此处关键需要理解内存地址引用,修改了$pItem,其实就是修改了$map[$pid]的值
  78. $pItem = &$map[$pid];
  79. $pItem[$this->childName][] = &$item;
  80. }
  81. }
  82. // 循环res,将pid不为0的过滤掉,剩下的就是所有的根树
  83. if($res){
  84. foreach($res as $k=>$value){
  85. if($value[$this->pidName] == 0){
  86. $rootTree[] = $value;
  87. }
  88. }
  89. }
  90. return $rootTree;
  91. }
  92. /**
  93. * 获取所有树
  94. */
  95. public function getTrees(){
  96. $map = $this->map;
  97. $res = [];
  98. foreach ($map as $id => &$item) {
  99. // 获取出每一条数据的父id
  100. $pid = &$item[$this->pidName];
  101. // 如果在map中没有设置过当前$item的pid索引, 说明$item是根节点
  102. if(!isset($map[$pid])){
  103. //将根节点的item的引用保存到$res中
  104. $res[] = &$item;
  105. }else{
  106. // 如果在map中有设置过当前$item的pid索引, 则将当前item加入到他父亲的叶子节点中
  107. // 此处关键需要理解内存地址引用,修改了$pItem,其实就是修改了$map[$pid]的值
  108. $pItem = &$map[$pid];
  109. $pItem[$this->childName][] = &$item;
  110. }
  111. }
  112. return $res;
  113. }
  114. /**
  115. * 获取某棵子树
  116. * 思路:
  117. * 1.先获取当前节点的所有父级节点
  118. * 2.组合一个根树的map,每个key都是id值,getRootTree是自增的
  119. * 3.使用所有父级节点,直接在根树的map中索引得到子树
  120. */
  121. /**
  122. * 获取某ID的所有子级ID
  123. * @param $ids string|array 要查询子级的ID
  124. * @param bool $withSelf 返回的结果是否包含$ids
  125. * @return array
  126. */
  127. public function getChildIds($ids, $withSelf=true){
  128. $map = $this->map;
  129. $res = [];//最终结果
  130. if(!is_array($ids)){
  131. $ids = explode(',', $ids);
  132. $sourceIds = $ids;
  133. }else{
  134. $sourceIds = $ids;
  135. }
  136. $state = true;
  137. while($state){
  138. $otherIds = [];
  139. foreach ($ids as $id) {
  140. foreach ($map as $key => $value) {
  141. if($value[$this->pidName] == $id){
  142. $res[] = $value[$this->mapName];//找到我的下级立即添加到最终结果中
  143. $otherIds[] = $value[$this->mapName];//将我的下级id保存起来用来下轮循环他的下级
  144. }
  145. }
  146. }
  147. if(!$otherIds){
  148. $state = false;
  149. }else{
  150. $ids = $otherIds;//foreach中找到的我的下级集合,用来下次循环
  151. }
  152. }
  153. if($withSelf){
  154. foreach($sourceIds as $sId){
  155. $res[] = intval($sId);
  156. }
  157. }
  158. return $res;
  159. }
  160. /**
  161. * 获取某ID的所有父级ID
  162. * @param $ids string|array 要查询父级的ID
  163. * @param bool $withSelf 返回的结果是否包含$id
  164. * @return array
  165. */
  166. public function getParentIds($ids, $withSelf=true){
  167. $map = $this->map;
  168. $res = [];//最终结果
  169. if(!is_array($ids)){
  170. $ids = explode(',', $ids);
  171. $sourceIds = $ids;
  172. }else{
  173. $sourceIds = $ids;
  174. }
  175. $state = true;
  176. while($state){
  177. $otherIds = [];
  178. foreach ($ids as $id) {
  179. foreach ($map as $key => $value) {
  180. if($value[$this->mapName] == $map[$id][$this->pidName]){
  181. $res[] = $value[$this->mapName];//找到我的上级立即添加到最终结果中
  182. $otherIds[] = $value[$this->mapName];//将我的上级id保存起来用来下次轮循环他的下级
  183. }
  184. }
  185. }
  186. if(!$otherIds){
  187. $state = false;
  188. }else{
  189. $ids = $otherIds;//foreach中找到的我的上级集合,用来下次循环
  190. }
  191. }
  192. if($withSelf){
  193. foreach($sourceIds as $sId){
  194. $res[] = intval($sId);
  195. }
  196. }
  197. return $res;
  198. }
  199. }