Tree.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <?php
  2. namespace ba;
  3. /**
  4. * 树
  5. */
  6. class Tree
  7. {
  8. /**
  9. * 实例
  10. * @var ?Tree
  11. */
  12. protected static ?Tree $instance = null;
  13. /**
  14. * 生成树型结构所需修饰符号
  15. * @var array
  16. */
  17. public static array $icon = array('│', '├', '└');
  18. /**
  19. * 子级数据(树枝)
  20. * @var array
  21. */
  22. protected array $children = [];
  23. /**
  24. * 初始化
  25. * @access public
  26. * @return Tree
  27. */
  28. public static function instance(): Tree
  29. {
  30. if (is_null(self::$instance)) {
  31. self::$instance = new static();
  32. }
  33. return self::$instance;
  34. }
  35. /**
  36. * 将数组某个字段渲染为树状,需自备children children可通过$this->assembleChild()方法组装
  37. * @param array $arr 要改为树状的数组
  38. * @param string $field '树枝'字段
  39. * @param int $level 递归数组层次,无需手动维护
  40. * @param bool $superiorEnd 递归上一级树枝是否结束,无需手动维护
  41. * @return array
  42. */
  43. public static function getTreeArray(array $arr, string $field = 'name', int $level = 0, bool $superiorEnd = false): array
  44. {
  45. $field = strtoupper($field);
  46. $level++;
  47. $number = 1;
  48. $total = count($arr);
  49. foreach ($arr as $key => $item) {
  50. $prefix = ($number == $total) ? self::$icon[2] : self::$icon[1];
  51. if ($level == 2) {
  52. $arr[$key][$field] = str_pad('', 4) . $prefix . $item[$field];
  53. } elseif ($level >= 3) {
  54. $arr[$key][$field] = str_pad('', 4) . ($superiorEnd ? '' : self::$icon[0]) . str_pad('', ($level - 2) * 4) . $prefix . $item[$field];
  55. }
  56. if (isset($item['children']) && $item['children']) {
  57. $arr[$key]['children'] = self::getTreeArray($item['children'], $field, $level, $number == $total);
  58. }
  59. $number++;
  60. }
  61. return $arr;
  62. }
  63. /**
  64. * 递归合并树状数组(根据children多维变二维方便渲染)
  65. * @param array $data 要合并的数组 ['id' => 1, 'pid' => 0, 'title' => '标题1', 'children' => ['id' => 2, 'pid' => 1, 'title' => ' └标题1-1']]
  66. * @return array [['id' => 1, 'pid' => 0, 'title' => '标题1'], ['id' => 2, 'pid' => 1, 'title' => ' └标题1-1']]
  67. */
  68. public static function assembleTree(array $data): array
  69. {
  70. $arr = [];
  71. foreach ($data as $v) {
  72. $children = $v['children'] ?? [];
  73. unset($v['children']);
  74. $arr[] = $v;
  75. if ($children) {
  76. $arr = array_merge($arr, self::assembleTree($children));
  77. }
  78. }
  79. return $arr;
  80. }
  81. /**
  82. * 递归的根据指定字段组装 children 数组
  83. * @param array $data 数据源 例如:[['id' => 1, 'pid' => 0, title => '标题1'], ['id' => 2, 'pid' => 1, title => '标题1-1']]
  84. * @param string $pid 存储上级id的字段
  85. * @param string $pk 主键字段
  86. * @return array ['id' => 1, 'pid' => 0, 'title' => '标题1', 'children' => ['id' => 2, 'pid' => 1, 'title' => '标题1-1']]
  87. */
  88. public function assembleChild(array $data, string $pid = 'PID', string $pk = 'ID'): array
  89. {
  90. if (!$data) return [];
  91. $pks = [];
  92. $topLevelData = []; // 顶级数据
  93. $this->children = []; // 置空子级数据
  94. foreach ($data as $item) {
  95. $pks[] = $item[$pk];
  96. // 以pid组成children
  97. $this->children[$item[$pid]][] = $item;
  98. }
  99. // 上级不存在的就是顶级,只获取它们的 children
  100. foreach ($data as $item) {
  101. if (!in_array($item[$pid], $pks)) {
  102. $topLevelData[] = $item;
  103. }
  104. }
  105. if (count($this->children) > 0) {
  106. foreach ($topLevelData as $key => $item) {
  107. $topLevelData[$key]['children'] = $this->getChildren($this->children[$item[$pk]] ?? [], $pk);
  108. }
  109. return $topLevelData;
  110. } else {
  111. return $data;
  112. }
  113. }
  114. /**
  115. * 获取 children 数组
  116. * 辅助 assembleChild 组装 children
  117. * @param array $data
  118. * @param string $pk
  119. * @return array
  120. */
  121. protected function getChildren(array $data, string $pk = 'id'): array
  122. {
  123. if (!$data) return [];
  124. foreach ($data as $key => $item) {
  125. if (array_key_exists($item[$pk], $this->children)) {
  126. $data[$key]['children'] = $this->getChildren($this->children[$item[$pk]], $pk);
  127. }
  128. }
  129. return $data;
  130. }
  131. }