两种方式实现PHP+MySQL无限分类

第一种方法

  这种方法是很常见、很传统的一种,表结构:

表:category
id int 主键,自增  
name varchar 分类名称  
pid int 父类id,默认0  

  顶级分类的pid默认是0。想取出某个分类的子分类树的时候,基本思路就是递归,出于效率问题不建议每次递归都查询数据库,通常的做法是先讲所有分类取出来,保存到PHP数组里,再进行处理,最后还可以将结果缓存起来以提高下次请求的效率。

  先来构建一个原始数组,这个直接从数据库中读取取出来:

$categories = array(
    array('id'=>1,'name'=>'电脑','pid'=>0),
    array('id'=>2,'name'=>'手机','pid'=>0),
    array('id'=>3,'name'=>'笔记本','pid'=>1),
    array('id'=>4,'name'=>'台式机','pid'=>1),
    array('id'=>5,'name'=>'智能机','pid'=>2),
    array('id'=>6,'name'=>'功能机','pid'=>2),
    array('id'=>7,'name'=>'超级本','pid'=>3),
    array('id'=>8,'name'=>'游戏本','pid'=>3),
);

  目标是将它转化为下面这种结构:

电脑
    笔记本
        超级本
        游戏本
    台式机
手机
    智能机
    功能机

  用数组来表示的话,可以增加一个children键来存储它的子分类:

array(  
    //1对应id,方便直接读取
    1 => array(
        'id'=>1,
        'name'=>'电脑',
        'pid'=>0,
        children=>array(
            &array(
                'id'=>3,
                'name'=>'笔记本',
                'pid'=>1,
                'children'=>array(
                    //此处省略
                )
            ),
            &array(
                'id'=>4,
                'name'=>'台式机',
                'pid'=>1,
                'children'=>array(
                    //此处省略
                )
            ),
        )
    ),
    //其他分类省略
)

  处理过程:

$tree = array();
//第一步,将分类id作为数组key,并创建children单元
foreach($categories as $category){  
    $tree[$category['id']] = $category;
    $tree[$category['id']]['children'] = array();
}

//第二部,利用引用,将每个分类添加到父类children数组中,这样一次遍历即可形成树形结构。
foreach ($tree as $k=>$item) {  
    if ($item['pid'] != 0) {
        $tree[$item['pid']]['children'][] = &$tree[$k];
    }
}

print_r($tree);  

  结果如下:

Array  
(
    [1] => Array
        (
            [id] => 1
            [name] => 电脑
            [pid] => 0
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [name] => 笔记本
                            [pid] => 1
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [id] => 7
                                            [name] => 超级本
                                            [pid] => 3
                                            [children] => Array
                                                (
                                                )

                                        )

                                    [1] => Array
                                        (
                                            [id] => 8
                                            [name] => 游戏本
                                            [pid] => 3
                                            [children] => Array
                                                (
                                                )

                                        )

                                )

                        )

                    [1] => Array
                        (
                            [id] => 4
                            [name] => 台式机
                            [pid] => 1
                            [children] => Array
                                (
                                )

                        )

                )

        )

    [2] => Array
        (
            [id] => 2
            [name] => 手机
            [pid] => 0
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 5
                            [name] => 智能机
                            [pid] => 2
                            [children] => Array
                                (
                                )

                        )

                    [1] => Array
                        (
                            [id] => 6
                            [name] => 功能机
                            [pid] => 2
                            [children] => Array
                                (
                                )

                        )

                )

        )

    [3] => Array
        (
            [id] => 3
            [name] => 笔记本
            [pid] => 1
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 7
                            [name] => 超级本
                            [pid] => 3
                            [children] => Array
                                (
                                )

                        )

                    [1] => Array
                        (
                            [id] => 8
                            [name] => 游戏本
                            [pid] => 3
                            [children] => Array
                                (
                                )

                        )

                )

        )

    [4] => Array
        (
            [id] => 4
            [name] => 台式机
            [pid] => 1
            [children] => Array
                (
                )

        )

    [5] => Array
        (
            [id] => 5
            [name] => 智能机
            [pid] => 2
            [children] => Array
                (
                )

        )

    [6] => Array
        (
            [id] => 6
            [name] => 功能机
            [pid] => 2
            [children] => Array
                (
                )

        )

    [7] => Array
        (
            [id] => 7
            [name] => 超级本
            [pid] => 3
            [children] => Array
                (
                )

        )

    [8] => Array
        (
            [id] => 8
            [name] => 游戏本
            [pid] => 3
            [children] => Array
                (
                )

        )

)

  优点:关系清楚,修改上下级关系简单。

  缺点:使用PHP处理,如果分类数量庞大,效率也会降低。

  一个应用类:

<?php

/**
 *   $tree= new ShowTree($result);
 *   $arr=$tree->leaf(0);
 *   $nav=$tree->navi(15);
 */
class  ShowTree  
{
    private $result;
    private $tmp;
    private $arr;
    private $already = array();

    /**
     * 构造函数
     *
     * @param array $result 树型数据表结果集
     * @param array $fields 树型数据表字段,array(分类id,父id)
     * @param integer $root 顶级分类的父id
     */
    public function __construct($result, $fields = array('id', 'fid'), $root = 0)
    {
        $this->result = $result;
        $this->fields = $fields;
        $this->root = $root;
        $this->handler();
    }

    /**
     * 树型数据表结果集处理
     */
    private function handler()
    {
        $tmp = array();
        foreach ($this->result as $node) {
            $tmp[$node[$this->fields[1]]][] = $node;
        }
        krsort($tmp);
        for ($i = count($tmp); $i > 0; $i--) {
            foreach ($tmp as $k => $v) {
                if (!in_array($k, $this->already)) {
                    if (!$this->tmp) {
                        $this->tmp = array($k, $v);
                        $this->already[] = $k;
                        continue;
                    } else {
                        foreach ($v as $key => $value) {
                            if ($value[$this->fields[0]] == $this->tmp[0]) {
                                $tmp[$k][$key]['child'] = $this->tmp[1];
                                $this->tmp = array($k, $tmp[$k]);
                            }
                        }
                    }
                }
            }
            $this->tmp = null;
        }
        $this->tmp = $tmp;
    }

    /**
     * 反向递归
     */
    private function recur_n($arr, $id)
    {
        foreach ($arr as $v) {
            if ($v[$this->fields[0]] == $id) {
                $this->arr[] = $v;
                if ($v[$this->fields[1]] != $this->root) $this->recur_n($arr, $v[$this->fields[1]]);
            }
        }
    }

    /**
     * 正向递归
     */
    private function recur_p($arr)
    {
        foreach ($arr as $v) {
            $this->arr[] = $v[$this->fields[0]];
            if ($v['child']) $this->recur_p($v['child']);
        }
    }

    /**
     * 菜单 多维数组
     *
     * @param integer $id 分类id
     * @return array 返回分支,默认返回整个树
     */
    public function leaf($id = null)
    {
        $id = ($id == null) ? $this->root : $id;
        return $this->tmp[$id];
    }

    /**
     * 导航 一维数组
     *
     * @param integer $id 分类id
     * @return array 返回单线分类直到顶级分类
     */
    public function navi($id)
    {
        $this->arr = null;
        $this->recur_n($this->result, $id);
        krsort($this->arr);
        return $this->arr;
    }

    /**
     * 散落 一维数组
     *
     * @param integer $id 分类id
     * @return array 返回leaf下所有分类id
     */
    public function leafid($id)
    {
        $this->arr = null;
        $this->arr[] = $id;
        $this->recur_p($this->leaf($id));
        return $this->arr;
    }
}

$pdo = new pdo('mysql:host=localhost;dbname=mysqldata;charset=utf8', 'root', '123456');
$sql = "SELECT * FROM category";

$arr = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
$tree = new ShowTree($arr);
$arr = $tree->leaf(0);
$nav = $tree->navi(10);
//var_dump( $arr, $nav );
foreach ($nav as $k => $v) {  
    print_r( $v );
}
第二种方法

  在表字段中增加一个path字段:

表:category
id int 主键,自增  
name varchar 分类名称  
pid int 父类id,默认0  
path varchar 路径  

  示例数据:

id        name        pid        path  
1         电脑        0          0  
2         手机        0          0  
3         笔记本      1          0-1  
4         超级本      3          0-1-3  
5         游戏本      3          0-1-3  

  path字段记录了从根分类到上一级父类的路径,用id+'-'表示。

  这种方式,假设要查询电脑下的所有后代分类,只需要一条sql语句:

select `id`, `name`, `path` from category WHERE `path` like (select concat(path,'-',id,'%') as path from category WHERE id = 1);  

  结果:

+----+-----------+-------+
| id | name      | path  |
+----+-----------+-------+
| 3  | 笔记本 | 0-1   |
| 4  | 超级本 | 0-1-3 |
| 5  | 游戏本 | 0-1-3 |
+----+-----------+-------+

  优点:查询容易,效率高,path字段可以加索引。

  缺点:更新节点关系麻烦,需要更新所有后辈的path字段。