PHP无限极分类

一、参考资料

http://www.php.cn/php-weizijiaocheng-360446.html
http://www.php.cn/keywords-%E6%97%A0%E9%99%90%E6%9E%81%E5%88%86%E7%B1%BB.html

本文博客部分内容是上述网上内容搬运过来的。

二、场景

无限极分类在web网站中应用很多,比如无限极菜单,无限极文件夹展开。因为最近的项目中有用到树的结构,其实就是无限极菜单的存储。
在某次面试中也有提及,所以这里集合上述网上的资料总结一下。
使用场景:
1、需要获取所有的节点,也就是无限极菜单的树形结构分类。
2、获取某个节点的所有叶子节点。
3、获取某个节点的所有子孙节点。
4、非叶子节点的分页展示。

三、数据库存储

无限极分类其实就是一棵树,所有的节点作为一个存储元素。每个节点可以有任意个孩子节点,且只有一个父节点。
MySQL数据存储,结合项目的数据表存储结构如下:

CREATE TABLE t_tree_info (
  `Fid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '标签自增ID',
    `Fname` varchar(255)  NOT NULL DEFAULT '' COMMENT '节点名称',
    `Fpid` bigint(20) unsigned NOT NULL DEFAULT 0  COMMENT '父节点id',
    `Fadd_time` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00'  COMMENT '创建时间',
  `Fmodify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`Fid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='无限极分类菜单存储表';

核心字段就是节点自身唯一标识Fid,和对应的父节点标识Fpid

四、技术实现

全局数据存储节点

节点数据在项目中是MySQL存储的,现在为了测试,把MySQL数据直接存放到数组里面,全局使用。数据存储如下:

    private static $listData = [
        [
            "Fid" => 1,
            "Fpid" => 8,
            "Fname" => '首页',
        ],
        [
            "Fid" => 2,
            "Fpid" => 8,
            "Fname" => '博客',
        ],
        [
            "Fid" => 3,
            "Fpid" => 8,
            "Fname" => '官网',
        ],
        [
            "Fid" => 4,
            "Fpid" => 2,
            "Fname" => '个人博客',
        ],
        [
            "Fid" => 5,
            "Fpid" => 2,
            "Fname" => '他人博客',
        ],
        [
            "Fid" => 6,
            "Fpid" => 8,
            "Fname" => '测试1',
        ],
        [
            "Fid" => 7,
            "Fpid" => 8,
            "Fname" => '测试2',
        ],
        [
            "Fid" => 8,
            "Fpid" => 0,
            "Fname" => '无限极分类',
        ],
        [
            "Fid" => 9,
            "Fpid" => 5,
            "Fname" => '女性栏目',
        ],
        [
            "Fid" => 10,
            "Fpid" => 5,
            "Fname" => '男性栏目',
        ],
    ];

引用方式实现无限极分类

思路:
1、即所有待处理的数据进行包装成下标为主键Fid(pk)的数组,便于由Fpid获取对应的父栏目。
2、对包装的数据进行循环,如果为根节点,则将其引用添加到tree中,否则,将其引用添加到其父类的子元素中。这样虽然tree中,只是添加了根节点,但是每个根节点如果有子元素,其中包含了子元素的引用。故能形成树型。

个人觉得引用的设计思路相比递归的思路更容易理解,更直观一些。

代码如下:

    /**
     * 把返回的数据集转换成Tree
     * @param array $list 要转换的数据集
     * @param string $pk 自增字段(栏目Fid)
     * @param string $pid parent标记字段
     * @return array
     */
    public static function quote_make_tree($list, $pk = 'Fid', $pid = 'Fpid',$child = '_child', $root = 0)
    {
        $tree = $packData = [];
        foreach ($list as $data) {
            $packData[$data[$pk]] = $data;
        }
        foreach ($packData as $key =>$val) {
            if ($val[$pid] == $root) {//代表跟节点
              $tree[] = & $packData[$key];
            } else {
              //找到其父类
              $packData[$val[$pid]][$child][] = & $packData[$key];
            }
        }
        return $tree;
    }

引用调用树:

    /**
     * 引用生成树
     * @return
     */
    public static function quote_tree_test()
    {
        $tree = self::make_tree(self::$listData, $pk = 'id', $pid = 'pid', $child = '_child', $root = 0);
        echo json_encode($tree);
    }

返回结果如下:

[
    {
        "Fid": 8,
        "Fpid": 0,
        "Fname": "无限极分类",
        "_child": [
            {
                "Fid": 1,
                "Fpid": 8,
                "Fname": "首页"
            },
            {
                "Fid": 2,
                "Fpid": 8,
                "Fname": "博客",
                "_child": [
                    {
                        "Fid": 4,
                        "Fpid": 2,
                        "Fname": "个人博客"
                    },
                    {
                        "Fid": 5,
                        "Fpid": 2,
                        "Fname": "他人博客",
                        "_child": [
                            {
                                "Fid": 9,
                                "Fpid": 5,
                                "Fname": "女性栏目"
                            },
                            {
                                "Fid": 10,
                                "Fpid": 5,
                                "Fname": "男性栏目"
                            }
                        ]
                    }
                ]
            },
            {
                "Fid": 3,
                "Fpid": 8,
                "Fname": "官网"
            },
            {
                "Fid": 6,
                "Fpid": 8,
                "Fname": "测试1"
            },
            {
                "Fid": 7,
                "Fpid": 8,
                "Fname": "测试2"
            }
        ]
    }
]

递归方式实现无限分类

思路:
1、使用循环,分别获取所有的根节点。
2、在获取每个节点的时候,将该节点从原数据中移除,并递归方式获取其所有的子节点,一直原数据为空。

代码如下:

    /**
     * 递归生成树
     * @param  array  $list  要转换的数据集
     * @param  string  $pk    自增字段(栏目id)
     * @param  string  $pid   parent标记字段
     * @param  string  $child 孩子节点key
     * @param  integer $root  根节点标识
     * @return array
     */
    public static function recursive_make_tree($list, $pk = 'Fid', $pid = 'Fpid', $child = '_child', $root = 0)
    {
        $tree = [];
        foreach ($list as $key => $val) {
            if ($val[$pid] == $root) {
                //获取当前$pid所有子类
                unset($list[$key]);
                if (!empty($list)) {
                  $child = self::recursive_make_tree($list, $pk, $pid, $child, $val[$pk]);
                  if (!empty($child)) {
                    $val['_child'] = $child;
                  }
                }
                $tree[] = $val;
            }
        }
        return $tree;
    }

递归调用树:

    public static function recursive_tree_test()
    {
        $tree = self::recursive_make_tree(self::$listData, $pk = 'Fid', $pid = 'Fpid', $child = '_child', $root = 0);
        echo json_encode($tree);
    }

获取某个节点的所有叶子节点

思路:
1、如果当前节点不是某个节点的父节点,则说明当前节点是叶子节点。则返回当前节点。同时也是递归出口。
2、如果当前节点是某个节点的父节点,则说明当前节点不是叶子节点。针对当前节点的孩子节点进行同样递归查询。

代码如下:

    /**
     * 获取某节点的所有叶子节点
     * 如果当前节点是叶子节点,则返回本身
     * @param $pid
     * @return array
     */
    public static function getAllLeafNodeByFatherIds($pid )
    {
        $allLeafTagIds = [];
        $sql = "SELECT GROUP_CONCAT(Fid) AS Fid FROM t_tree_info WHERE Fpid={$pid}";
        $res = $this->db->fetchRow($sql);
        
        if ($res && $res['Fid']) {
            $leafTagIds = explode(',', $res['Fid']);
            foreach ($leafTagIds as $nodeTagId) {
                $curLeafTagIds = $this->getAllLeafNodeByFatherIds($nodeTagId);
                if ($curLeafTagIds) {
                    $allLeafTagIds = array_merge($allLeafTagIds, $curLeafTagIds);
                }
            }
            return $allLeafTagIds;
        } else {
            // 当前节点是叶子节点,返回该节点
            return [$tagId];
        }
    }

获取某个节点的所有子孙节点

思路:
1、如果当前节点不是某个节点的父节点,说明当前节点是叶子节点,则返回空。同时也是递归出口。
2、如果当前节点是某个节点的父节点,则合并当前节点到返回数组中;然后对该节点的所有孩子节点进行同样的递归查询。

代码如下:

    /**
     * 根据父节点获取所有的子节点
     * @param $pids
     * @return array
     */
    public static function getAllChildNodeByFatherIds($pids)
    {
        if (!is_array($pids) || empty($pids)) {
            return [];
        }
        $allChildTagIds = [];
        $tagIdsStr = implode(',', array_unique($pids));
        $sql = "SELECT GROUP_CONCAT(Fid) AS Fid FROM t_tree_info WHERE Fpid  IN({$tagIdsStr})";
        $res = $this->db->fetchRow($sql);
        
        if ($res && $res['Fid']) {
            $childTagIds = explode(',', $res['Fid']);
            // 合并当前查询出的子节点
            $allChildTagIds = array_merge($allChildTagIds, $childTagIds);
            
            $curChildTagIds = $this->getAllChildNodeByFatherIds($childTagIds);
            
            // 如果再查询出子节点
            $allChildTagIds = array_merge($allChildTagIds, $curChildTagIds);
            return $allChildTagIds;
        } else {
            return [];
        }
    }

非叶子节点的分页展示

思路:
1、获取非叶子节点,只要节点不是其他节点的父节点即可。

代码如下:

# MySQL分页查询语句
SELECT SQL_CALC_FOUND_ROWS Fid, Fname, Fpid, Fadd_time, Fmodify_time FROM t_tree_info WHERE Fid IN(SELECT DISTINCT Fpid FROM t_tree_info)  ORDER BY Fadd_time DESC LIMIT 0, 10

转载于:https://www.cnblogs.com/jaspersong/p/9047486.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值