PHP array_tree 构建层级(树状)数组

在很多系统中会有层级体系,特别是在各种管理系统的菜单、分类、部门等相关模块。一般而言,系统都会把这些对象保存在数据库中,并用一个parent_id字段去记录该对象的父级对象。那么问题来了,当从数据库中重新查出对象列表后,如何重新恢复其层级关系呢?下面的这个函数可以帮到你:

<?php
/**
 * 构建层级(树状)数组
 * @param array $array 要进行处理的一维数组,经过该函数处理后,该数组自动转为树状数组
 * @param string $pid 父级ID的字段名
 * @return array|bool
 */
function array_tree(&$array,$pid = 'pid') {
  // 子元素计数器
  function array_children_count($array,$pid) {
    $counter = array();
    foreach($array as $item) {
      $count = isset($counter[$item[$pid]]) ? $counter[$item[$pid]] : 0;
      $count  ++;
      $counter[$item[$pid]] = $count;
    }
    return $counter;
  }
  // 把元素插入到对应的父元素children字段
  function array_child_append($parent,$pid,$child) {
    foreach($parent as &$item) {
      if($item['id'] == $pid) {
        if(!isset($item['children']))
          $item['children'] = array();
        $item['children'][] = $child;
      }
    }
    return $parent;
  }

  // 开始程序
  $counter = array_children_count($array,$pid);
  // 如果顶级元素为0个,那么直接返回false
  if($counter[0] == 0) 
    return false;
  // 准备顶级元素
  $tree = array();
  // 位移
  while(isset($counter[0]) && $counter[0] > 0) { // 如果顶级栏目的子元素计数器仍然大于0,那么仍然往下执行循环
    $temp = array_shift($array);
    if(isset($counter[$temp['id']]) && $counter[$temp['id']] > 0) { // 如果数组的第一个元素的子元素个数大于0,那么把该元素放置到数组的末端
      array_push($array,$temp);
    }
    else { // 相反,如果该数组的第一个元素没有子元素,那么把该元素移动到其父元素的children字段中,同时,该元素从原数组中被删除
      if($temp[$pid] == 0)
        $tree[] = $temp;
      else
        $array = array_child_append($array,$temp[$pid],$temp);
    }
    $counter = array_children_count($array,$pid);
  }

  $array = $tree;
  return $tree;
}

我来看下具体用法。

它有两个参数,第一个是传入的要处理的数组,注意,该数组必须是一维数组,而且所含字段有规定:①必须是一维数组,②必须包含id字段,③必须包含$pid对应的字段,④必须存在$pid为0的元素,也就是必须存在顶级元素。处理结束时,那些没有追溯到$pid=0的元素将被抛弃,只留下从$pid=0的顶级元素开始的树状结构,举个例子如下:

$arr = array(
    array('id' => 53,'pid' => 0),
    array('id' => 64,'pid' => 53),
    array('id' => 70,'pid' => 42)
);
array_tree($arr,'pid');

由于上面的例子中id=70这个元素的pid=42,而id=42的元素并不存在,因此在最终结果中id=70的这个元素会被抛弃。

在使用过程中,array_tree处理后,树状数组会存在一个排序问题。处理结果并不会按照一定的规则进行升序或降序进行排列,而是以逻辑处理过程中的内存顺序排列,虽然树状层级是正确的,但是同级元素的顺序是不确定的。建议你通过array_orderby函数,对处理后的结果进行排序处理。

例子1:按从大到小规则排序的菜单

$demo1 = array(
  1 => array('id' => 1,'title' => 'title1','pid' => 0),
  2 => array('id' => 2,'title' => 'title2','pid' => 1),
  3 => array('id' => 3,'title' => 'title3','pid' => 1),
  4 => array('id' => 4,'title' => 'title4','pid' => 2),
  5 => array('id' => 5,'title' => 'title5','pid' => 2),
  6 => array('id' => 6,'title' => 'title6','pid' => 0)
);
$demo1 = array_tree($demo1);// 或者直接使用array_tree($demo1);,无需返回值
array_orderby($demo1,'id','asc','children');
print_r($demo1);

例子2:菜单排列不规律,父菜单在后面

$demo2 = array(
  1 => array('id' => 1,'title' => 'title1','parent_id' => 2),
  2 => array('id' => 2,'title' => 'title2','parent_id' => 0),
  3 => array('id' => 3,'title' => 'title3','parent_id' => 1),
  4 => array('id' => 4,'title' => 'title4','parent_id' => 2),
  5 => array('id' => 5,'title' => 'title5','parent_id' => 6),
  6 => array('id' => 6,'title' => 'title6','parent_id' => 0)
);
array_tree($demo2,'parent_id');
array_orderby($demo2,'id','asc','children');
print_r($demo2);

在上面的例子中可以看出,无论一维数组中实际的层级关系怎样复杂,该程序都能很好的进行处理。注意,array_orderby不是php的内置函数,你需要到上面链接文章中获取。

2016-02-03