无限极分类

这篇博客详细介绍了PHP中实现无限级分类的三种方法:递归、引用和栈,并给出了具体的代码实现。此外,还探讨了如何根据分类ID反向查找所有父分类信息,以及在实际项目中如何应用,如面包屑导航和下拉菜单的构建。最后提到了自关联表的排序和数据结构,以及在部门和员工管理中的应用。
摘要由CSDN通过智能技术生成

无限极分类 递归
建表:
涉及无限极分类的问题建表时,都会有一个专门的分类表,表中有一个pid字段默认为0,属于一级分类,pid字段关联当前分类表的id,用来标明当前分类所属的上级分类,例如建一个问题分类表的语句如下

CREATE TABLE `quescate` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '问题分类id',
  `title` varchar(250) NOT NULL COMMENT '问题分类标题',
  `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属问题id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='问题分类表'

一、无限极分类的实现

接下来取出各级分类及分类下的子分类,自己做时用了最笨的办法:

(1)先查找一级分类(pid=0)

(2)通过上级分类查找的id作为pid,来查找下一级分类

(3)重复步骤(2)

代码实现如下

<?PHP 
    $cate = array();
    // 一级分类
    $cate_0 = $this->where('pid = 0')->select();
    if (!empty($cate_0)){
        // 二级分类
        $ids_1 = array_map('reset', $cate_0); // 使用array_column()更简单,但是需要>=PHP5.5.0的版本
        $map_1['pid'] = array('in', $ids_1);
        $cate_1 = $this->where($map_1)->select();
        if(!empty($cate_1)){
            // 三级分类
            $ids_2 = array_map('reset', $cate_1);
            $map_2['pid'] = array('in', $ids_2);
            $cate_2 = $this->where($map_2)->select();
        }
        $cate = array(0=>$cate_0, 1=>$cate_1, 2=>$cate_2);
    }
    unset($ids_1, $map_1, $ids_2, $map_2);
 
    return $cate;

但是这样的办法,有几级分类就要查找几次,太过麻烦,也会造成代码冗余。根据上述步骤很轻易看出,这其实就是一个递归。于是在优化代码的过程中,可以通过递归实现无限极分类,通过查找资料还找到了引用,栈的实现方法,下面一一介绍,为了更直观,使用的数据如下

<?php 
    $res = array(
        array('id' => 8, 'title' => '美国', 'pid' => 0),
        array('id' => 9, 'title' => '纽约', 'pid' => 8),
        array('id' => 10, 'title' => '韩国', 'pid' => 0),
        array('id' => 11, 'title' => '日本', 'pid' => 0),
        array('id' => 12, 'title' => '加拿大', 'pid' => 0),
        array('id' => 13, 'title' => '中国', 'pid' => 0),
        array('id' => 14, 'title' => '华盛顿', 'pid' => 8),
        array('id' => 16, 'title' => '洛杉矶', 'pid' => 8),
        array('id' => 17, 'title' => '北京', 'pid' => 13),
        array('id' => 18, 'title' => '上海', 'pid' => 13),
        array('id' => 19, 'title' => '广东', 'pid' => 13),
        array('id' => 20, 'title' => '深圳', 'pid' => 13),
        array('id' => 21, 'title' => '杭州', 'pid' => 13)
    );
  1. 递归,实现无限极分类。递归应该是最容易想到的方法了。其实递归函数也是借助于栈的机制实现的,但是底层对于栈的处理对于程序员来说都是透明的,程序员只需要关心应用的实现逻辑。所以说使用递归处理上述问题理解起来比较容易,代码也比较简洁。在我看来,最容易理解的方法应该是使用栈的方式实现无限极分类了(可以参考以下的第三种方法)
    主要参考博客:
    php实现无限级分类查询(递归、非递归)中开头部分递归与栈的讲解
    PHP实现无限极分类的两种方式,递归和引用中的递归方法

<?php 
    /**
     * 递归实现无限极分类
     * @param array $array 分类数据
     * @param Int $pid 父ID
     * @param Int $level 分类级别
     * @return Array $list 分好类的数组 直接遍历即可 $level可以用来遍历缩进
     */
    function getTree($array, $pid =0, $level = 0){
        //声明静态数组,避免递归调用时,多次声明导致数组覆盖
        static $list = [];
        foreach ($array as $key => $value){
            //第一次遍历,找到父节点为根节点的节点 也就是pid=0的节点
            if ($value['pid'] == $pid){
                //父节点为根节点的节点,级别为0,也就是第一级
                $value['level'] = $level;
                //把数组放到list中
                $list[] = $value;
                //把这个节点从数组中移除,减少后续递归消耗
                unset($array[$key]);
                //开始递归,查找父ID为该节点ID的节点,级别则为原级别+1
                $this->getTree($array, $value['id'], $level+1);
            }
        }
        return $list;
    }
 
    // 无限极分类的第一种方法:递归
    $list = getTree($res, 0, 1);
    /*
    * 以下是根据自己需要对分好类的数据进行的处理,这里为了保持与上边需要的数据形式一致
    */
    foreach ($list as $key=>$value){
        if ($value['level'] == 1){
            $cate[0][] = $value; // 一级分类
        }elseif ($value['level'] == 2){
            $cate[1][] = $value; // 二级分类
        }
    }
 
    return $cate;
?>
  1. 引用,实现无限极分类。此方法非常巧用引用。如果不了解引用,对下面方法的理解是困难的,建议参考PHP手册引用一章
    主要参考博客:PHP无限极分类实现中的方法一,里面的方法二只是判断将三目运算符改成了if判断,喜欢的可以看看
<?php 
    /**
     * 将数据格式转换成树形结构数组
     * @param array $items 要进行转换的数组
     * @return array $items 转换完成的数组
     */
    function arrayToTree(Array $items) {
        foreach ($items as $key=>$item) {
            $items[$item['pid']]['son'][$key] = &$items[$key];
        }
        return isset($items[0]['son']) ? $items[0]['son'] : array();
    }
 
    $cate[1] = array(); // 预定义二级分类为空
    // 将数组索引改为id值
    $res = array_reduce($res, create_function('$res, $val', '$res[$val["id"]] = $val; return $res;'));
    $list = $this->arrayToTree($res);
    /*
    * 以下是根据自己需要对分好类的数据进行的处理,这里为了保持与上边需要的数据形式一致
    */
    foreach ($list as $ke=>$val){
        if (isset($val['son'])){
            $cate[1] += $val['son']; // 二级菜单
            unset($val['son']); // 从二级菜单中去除一级菜单
        }
        $cate[0][] = $val; // 一级菜单
    }
 
    return $cate;
?>
  1. 栈,实现无限极分类。栈的实现方式应该算是最容易理解的了,当然栈的核心:“先进后出”是必须要知道才能理解好下面的方法的。有关于栈,有兴趣的可以查一查《数据结构(C语言版)》中对于栈的讲解。

主要参考博客:php实现无限级分类查询(递归、非递归)中的“非递归,即使用栈机制实现无限级栏目的查询”


<?php 
    /**
     * 非递归,即使用栈机制实现无限级栏目的查询
     * 其实栈的核心机制也就四个字:先进后出。
     * 在这对于栈的机制不多说,主要说一下如何借助栈实现无限级栏目查询:
     * 1. 首先将顶级栏目压入栈中
     * 2. 将栈顶元素出栈
     * 3. 将出栈元素存入数组中,标记其深度(其深度就是在其父栏目的深度上面加1)
     * 4. 以出栈的元素为父栏目,查找其子栏目
     * 5. 将查找到的子栏目入栈,重复步骤2
     * 6. 判断栈为空的话,流程结束;
     * @param Array $array 要进行栈操作的数组
     * @return Array $list 分好类的数组
    */
    // 自定义入栈函数
    function pushStack(&$stack, $channel, $dep)
    {
        $channel['dep'] = $dep;
        array_push($stack, $channel);
    }
    // 自定义出栈函数
    function popStack(&$stack)
    {
        return array_pop($stack);
    }
    function getStack($array)
    {
        $stack = array(); //定义一个空栈
        $list = array();  //用来保存各个栏目之间的关系以及该栏目的深度
        /*
         * 首先将顶级栏目压入栈中
         * */
        foreach ($array as $key => $val) {
            if ($val['pid'] == 0)
                pushStack($stack, $val, 1);
        }
        /*
         * 将栈中的元素出栈,查找其子栏目
         * */
        do {
            $par = popStack($stack); //将栈顶元素出栈
            /*
             * 查找以此栏目为父级栏目的id,将这些栏目入栈
             * */
            foreach ($array as $key=>$value){
                if ($value['pid'] == $par['id']){
                    pushStack($stack, $value, $par['dep']+1);
                }
            }
            // 将出栈的栏目以及该栏目的深度保存到数组中
            $list[] = $par;
        } while (count($stack) > 0);
 
        return $list;
    }
 
    //$res = $this->order('id desc')->select(); // 因为栈先进后出的特点,分类完后的数据会是原数据的倒序排序。这可以在处理数据前,将数据先倒序输出,这样得到的数据就是正序的。
    $res = array_reverse($res);
    $list = getStack($res);
    foreach ($list as $key=>$value){
        if ($value['dep'] == 1){
            $cate[0][] = $value; // 一级分类
        }elseif ($value['dep'] == 2){
            $cate[1][] = $value; // 二级分类
        }
    }
 
    return $cate;
?>

总结:通过以上三种方法可以发现:递归会是最容易想到也最常用到的一种方式,比较容易理解使用方便;引用使用非常巧妙而且代码简洁但是不太好理解;栈的实现最容易理解,但要理解并注意栈的特点:先进后出。

二、根据分类id倒着查找所有的所属父分类信息

本来想将这部分放在下一篇博客讲解的,但想趁热打铁,索性放在一篇博客里总结完。

项目中有时候需要根据分类的id倒回去查找所有所属的父级分类信息,比如面包屑,前端菜单栏里只允许展示有具体信息的分类时,我们就要涉及到这个查找。同样的这个查找也需要用到递归的实现。下面是两种递归的实现方法,但大同小异,基本原理都一样。
主要参考博文:php实现无限极分类中的“根据子类id查找出所有父级分类信息”

  1. 方法一:Yii2框架中的方法

<?php 
    /**
     * Yii2框架中,根据子类id查找所有父级分类的方法
     * @param  array $arr 所有分类列表
     * @param  Int  $id 父级分类id
     * @return array $list 所有父级分类信息
    */
    function get_parent_list($arr,$id){
        static $list=array();
        foreach($arr as $u){
 
            if($u['id']== $id){//父级分类id等于所查找的id
                $list[]=$u;
 
                if($u['pid']>0){
                    get_parent_list($arr,$u['pid']);
 
                }
            }
        }
        return $list;
    }
 
    $cate = get_parent_list($res, $pid); // 此处$pid根据查到的具体分类信息中的pid传值查询所属上级分类
    $cate = array_reverse($cate); // 此处根据自己需要做处理,我这里为了页面展示方便做了数组倒序处理
?>
  1. 方法二:递归查询分类信息,与方法一大同小异
<?php 
    function get_parents($id){
        static $list = [];
        $cateModel = new QuescateModel();
        $cate = $cateModel->where('id = '.$id)->find(); // 此处就是查询分类表中一条语句
 
        if($cate){
            $list[] = $cate;
            $id = $cate['pid'];
            if($cate['pid'] > 0){
                self::get_parents($id);
            }
        }
 
        return $list;
    }
 
    $cate = get_parents($pid); // 根据需要查要查询分级分类的分类id
    $cate = array_reverse($cate); // 此处我是根据自己前端展示分方便最数组做了倒序处理
?>

自关联

无限级自关联表的排序及下拉框展示
数据结构
部门表 sys_department
员工表 sys_staff
建表语句
功能需求
部门排序
树状图展示:部门+员工
下拉框1:部门
下拉框2:部门+员工
功能实现
部门排序
树状图
下拉框1:部门
下拉框2:部门+员工
数据结构
部门表 sys_department
通过id和parent_id无限级自关联,唯一根父级为0,同时有一个排序字段order_num,对于同一父级下的所有子部门,通过此字段排序,还有一个“level”字段,维护层级深度

员工表 sys_staff
通过department_id与部门表的id关联

建表语句
DROP TABLE IF EXISTS sys_department;
CREATE TABLE sys_department (
id int(11) NOT NULL AUTO_INCREMENT,
parent_id int(11) DEFAULT ‘0’ COMMENT ‘父级部门id’,
name_en varchar(200) DEFAULT ‘’ COMMENT ‘英文名’,
name_cn varchar(200) DEFAULT ‘’ COMMENT ‘中文名’,
level int(11) DEFAULT ‘0’,
order_num int(11) DEFAULT ‘99’,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS sys_staff;
CREATE TABLE sys_staff (
id int(11) NOT NULL AUTO_INCREMENT,
department_id int(11) DEFAULT ‘0’ COMMENT ‘部门id’,
name_en varchar(200) DEFAULT ‘’ COMMENT ‘英文名’,
name_cn varchar(200) DEFAULT ‘’ COMMENT ‘中文名’,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;

功能需求
部门排序
先展示第一个父级部门数据,之后紧跟其下的第一个子部门数据,如果子部门还有子部门继续往下展示,如果没有再展示第二个子部门……

树状图展示:部门+员工
本文使用zTree插件,zTree教程,在此仅讨论数据
在这里插入图片描述

下拉框1:部门

下拉框使用插件:select2,附教程
顺序同“部门排序”,有缩进层级效果,所有部门可选
在这里插入图片描述

下拉框2:部门+员工

顺序同“部门排序”,没有关联上部门的员工放最前面,所有部门不可选,所有员工可选
在这里插入图片描述

功能实现

部门排序

网上自关联的排序很多,但都是按id排的,稍微修改一下,使其按照排序字段order_num排序
先创建一个排序函数

delimiter ~
DROP FUNCTION getPriorityDept~
CREATE FUNCTION getPriorityDept(inID INT) RETURNS VARCHAR(255) DETERMINISTIC
begin
  DECLARE gParentID INT DEFAULT 0;
  DECLARE gPriority VARCHAR(255) DEFAULT '';
  SELECT parent_id,LPAD(order_num,4, 0) INTO gParentID,gPriority FROM sys_department WHERE ID = inID;
  WHILE gParentID > 0 DO  /*0为根*/
    SELECT parent_id,CONCAT(LPAD(order_num,4, 0), '.', gPriority) INTO gParentID,gPriority FROM sys_department WHERE ID = gParentID;
  END WHILE;
  RETURN gPriority;
end ~
delimiter ;

然后直接调用就行

SELECT * FROM sys_department ORDER BY getPriorityDept(id);

树状图
使用zTree的简单数据模式

simpleData: {
                enable: true,
                idKey: "id",
                pIdKey: "pid",
                rootPId: 0
            },

数据的查询SQL如下

#员工表数据
(       
	SELECT a.id id,CONCAT('d-',b.id) pid,a.name_cn `name`
        FROM sys_staff a 
        LEFT JOIN sys_department b ON a.department_id = b.id
)  UNION ALL
#部门表数据
 (
	SELECT CONCAT('d-',id) id,CONCAT('d-',parent_id) pid,name_cn `name` FROM sys_department
)

下拉框1:部门
首先修改一下select2源码中的样式
在select2.js文件中查找“Results.prototype.option”方法,在最终返回前插入代码,依据数据中的“level”属性动态添加缩进样式

if(data.level){
          var leftPx = 30 * parseInt(data.level);
          option.style.paddingLeft = leftPx + "px";
      }

在这里插入图片描述
然后直接调用上面的排序方法就可以了

SELECT * FROM sys_department ORDER BY getPriorityDept(id);

下拉框2:部门+员工

直接查询,将数据交给select2插件处理,部门在下拉框中作为组,不可选

<resultMap id="departmentStaff" type="map">
   <id column="did" property="id"/>
   <result column="name_cn" property="nameCn"/>
   <collection property="children" javaType="java.util.ArrayList" ofType="map">
       <result column="id" property="id"/>
       <result column="text" property="text"/>
   </collection>
</resultMap>
<select id="findDepartmentStaff" resultMap="departmentStaff">
	(SELECT a.id did,a.name_cn name_cn,b.id,b.name_cn text FROM sys_department a 
		LEFT JOIN (SELECT * FROM sys_staff WHERE del_flag = 0 ) b ON a.id = b.department_id )
    UNION ALL
    (SELECT 0 did,'*无部门*' name_cn,a.id,a.name_cn text FROM sys_staff a LEFT JOIN  sys_department b ON b.id = a.department_id
        WHERE a.del_flag = 0 AND b.id IS NULL)
    ORDER BY getPriorityDept(did)
</select>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值