MENU

理解PHP无限级分类

February 7, 2018 • PHP

无限级分类知识点其实理解起来比较简单。因为在实际的项目中比如典型的如电商网站、一些CMS内容发布站点等都会涉及到无限级分类的知识。所以这篇文章准备用原生PHP代码来简单的现实一些典型的无限级分类。本次文章记录的是在实验楼这个网站上的一个实验经历,整理成自己的博文

实验环境

PHP 7.0.12
CentOS7.4
MySQL 5.7

Web服务器就使用PHP内置的Server,直接在项目根目录下执行php -S 127.0.0.1:8080就可以,然后通过http://127.0.0.1:8080来进行访问即可。

代码仓库

待补充(gitee)

实验介绍

本次介绍的无限级分类采用省市结构的方式来展示,如下图
省市分类结构

最终的效果如下面图片所展示
最终效果

创建数据库、表

首先我们建立一个简单的数据表,用来存放上面的省市结构数据。表结构SQL如下

create database category;
CREATE TABLE `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自动增长id',
  `pid` int(11) NOT NULL COMMENT '父id',
  `category` varchar(255) NOT NULL COMMENT '分类名称',
  `orderid` int(11) NOT NULL DEFAULT '0' COMMENT '排序id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后我们往表里初始化一些数据,方便页面显示。

INSERT INTO `category` VALUES ('1', '0', '中国', '0');
INSERT INTO `category` VALUES ('2', '1', '河北', '0');
INSERT INTO `category` VALUES ('3', '1', '山西', '0');
INSERT INTO `category` VALUES ('4', '2', '石家庄', '0');
INSERT INTO `category` VALUES ('5', '2', '邯郸', '0');
INSERT INTO `category` VALUES ('6', '3', '太原', '0');
INSERT INTO `category` VALUES ('7', '3', '大同', '0');
INSERT INTO `category` VALUES ('8', '4', '桥东', '0');
INSERT INTO `category` VALUES ('9', '4', '桥西', '0');
INSERT INTO `category` VALUES ('10', '5', '邯山', '0');
INSERT INTO `category` VALUES ('11', '5', '丛台', '0');
INSERT INTO `category` VALUES ('12', '6', '小店', '0');
INSERT INTO `category` VALUES ('13', '6', '迎泽', '0');
INSERT INTO `category` VALUES ('14', '7', '南郊', '0');
INSERT INTO `category` VALUES ('15', '7', '新荣', '0');

数据库DB类

我们简单的封装一下DB类,以便于后面的整体复用。DB类我们采用单例的模式,以避免重复的创建数据库实例。
这里提一下单例模式3个要点

  1. 要声明一个静态变量来存储这个实例
  2. 类的构造函数必须为私有
  3. 必须建立一个获取实例的方法

那么首先创建一个db.php,内容大致如下。

<?php

class db
{
    //私有变量保存实例
    private static $_instance;

    //定义一个私有静态变量保存数据库连接
    private static $_conn;

    private function __construct() {}

    //定义获取数据库实例的方法
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    public static function connect()
    {
        if (! self::$_conn) {
            self::$_conn = new mysqli('127.0.0.1', 'root', 'root', 'category');
        }

        mysqli_query(self::$_conn, "set names UTF8");

        return self::$_conn;
    }
}

编写好db类以后,可以使用下面一段简单的代码来测试下是否可以正常连接到数据库。

<?php

include 'db.php';

$db = db::getInstance()->connect();

$result = $db->query("select * from category");
while ($row = $result->fetch_assoc()) {
    echo $row['id'] . ':' . $row['category'] . '<br>';
}

output:
1:中国
2:河北
3:山西
4:石家庄
5:邯郸
6:太原
...

分类列表展示页面(index.php)

由于在分类列表展示页面中我们需要获取所有分类列表数据,所以我们使用递归的方式来获取这个数组。首先建立一个function.php文件。编写一个getCate方法

function getCate($pid = 0, &$result = array(), $s = 0)
{
    // 分类层级前展示的空格数
    $s = $s + 4;

    $conn = db::getInstance()->connect();

    //获取数据对象
    $sql = "SELECT * FROM `category` where `pid` = $pid";
    $res = $conn->query($sql);
    while ($row = $res->fetch_assoc()) {
        $row['category'] = str_repeat("&nbsp", $s) . '|--' . $row['category'];
        $result[] = $row;
        getCate($row['id'], $result, $s);
    }
    return $result;
}

上面这个就是使用递归算法来获取所有的层级分类

函数中有三个参数:

  • 第一个参数是查父ID的所有分类。
  • 第二个参数把查到的数据及所有的自己放到这个数组地址中来。
  • 第三个是我们每次层级深度前面的加的空格数。

执行步骤:

  • 第一步是执行查询pid为0的所有子类,放到 resqult[] 数组中。
  • 第二步是循环查找第一步查出的子类,pid 为子类的 ID。查出的所有的子类,并放到 resqult[] 数组中。
  • 最后查出分类没有子类后,循环不成立,不能继续执行 getCate 函数,此时函数已经不再调用自己,开始将流程的主控权交回给上一层函数来执行,开始执行所有的数据:return $resault。

最后编写index.php页面内容,如下

<?php 
require_once('./db.php');
require_once('function.php');
 ?>
<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <title>分类管理</title>
    <link rel="stylesheet" href="styles/style.css" type="text/css"/>
</head>
<body>
        <nav class="nav">
            <a href="categoryadd.php">添加分类</a>
        </nav>

        <article class="module width_full">
            <div class="tab_container">
                <table cellspacing="0" class="tablesorter"> 
                    <thead> 
                        <tr> 
                            <th width="10%" class="tc">id</th> 

                            <th width="25%">分类名</th> 

                            <th width="10%">操作</th>
                        </tr> 
                    </thead> 
                    <tbody> 

                    <?php 
                        $rs = getCate();
                        foreach ($rs as $key => $value) {
                    ?>
                        <tr>
                            <td class="tc"><?php echo $value['id'] ?></td>

                            <td><?php echo $value['category'] ?></td> 

                            <td><a href="categoryedit.php?id=<?php echo $value['id'] ?>&pid=<?php echo $value['pid'] ?>" title="编辑">编辑</a> | <a href="action.php?action=del&id=<?php echo $value['id'] ?>" title="删除" name="del" eid="1">删除</a></td> 
                        </tr>
                    <?php 
                        }
                    ?>
                    </tbody> 
                </table>
            </div>
        </article>
</body>
</html>

分类展示列表页

添加分类页面

建立一个名为categoryadd.php页面,内容如下

<?php
require_once('./db.php');
require_once('function.php');
$conn = db::getInstance()->connect();
// $result = $conn-> query("select * from category");
$result = getCate();
// dd($result->fetch_assoc());
?>
<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8"/>
    <title>添加分类</title>
    <link rel="stylesheet" href="styles/style.css" type="text/css"/>
</head>
<body>

        <article class="module width_full">
            <div class="module_content w500">
                <fieldset>
                    <label for="txtName">上级菜单</label>
                    <select>
                        <option value="0">作为顶级菜单</option>
                        <?php foreach (getCate() as $row) { ?>
                        <option value="<?php echo $row['id']?>"><?php echo $row['category']?></option>
                        <?php } ?>
                    </select>
                </fieldset>
                <fieldset>
                    <label for="txtName">分类名称</label>
                    <input type="text" id="txtName" name="category"/>
                </fieldset>

                <div class="tc mt20">
                    <a href="javascript:void(0)" class="button green" id="btnAdd">添加</a>
                </div>
            </div>
        </article>
<script type="text/javascript" src="js/submitForm.js"></script>
</body>
</html>

效果图如下
添加分类
这里主要也是调用了getCate函数来获取到上级菜单列表,然后展示出来。分类添加的数据处理,我们稍后在编写。

分类编辑页面

在管理页面,我们可以对每条分类进行编辑操作,所以需要创建一个页面来实现分类的编辑更新操作。在项目目录下,新建categoryedit.php,编辑如下:

数据处理页面

最后到了数据的入库、编辑等操作了。所以我们创建一个action.php的文件来处理前台提交过来的数据,如下:

<?php

require_once 'db.php';
require_once 'function.php';

$action = $_GET['action'];
switch ($action) {
    case 'add':
        $pid = $_POST['pid'];
        $category = $_POST['category'];
        categoryAdd($pid, $category);
        break;

    case 'update':
        $id = $_POST['id'];
        $pid = $_POST['pid'];
        $category = $_POST['category'];
        categoryUpdate($id, $pid, $category);
        break;

    case 'del':
        $id = (int)$_GET['id'];
        categoryDel($id);
        break;

    default:
        exit('非法操作');

}

function categoryAdd($pid, $category) {
    $conn = db::getInstance()->connect();

    $sql = "INSERT INTO `category` (`pid`, `category`) VALUES ({$pid}, '{$category}')"; //注意string类型要加单引号
    // echo $sql;exit;
    $res = $conn->query($sql);
    if ($res) {
        echo "<script>alert('添加分类成功');location.href='index.php';</script>";
        exit;
    } else {
        echo "<script>alert('添加分类失败');history.back(-1)</script>";
    }
}

function categoryDel($id) {
    $conn = db::getInstance()->connect();
    //查询当前分类下是否有子分类
    $sql = "SELECT count(*) AS `count` FROM `category` WHERE `pid` = '{$id}'";
    $res = $conn->query($sql);
    $result = $res->fetch_array();
    if ($result[0] > 0) {
        echo "<script>alert('请删除其子类后在删除');location.href='index.php';</script>";
        exit;
    } else {
        //删除操作
        $sql = "DELETE FROM `category` WHERE `id` = '{$id}'";
        $res = $conn->query($sql);
        if ($res) {
            echo "<script>alert('删除成功');location.href='index.php';</script>";
        }
    }
}

function categoryUpdate($id, $pid, $category) {
    $conn = db::getInstance()->connect();
    $sql = "UPDATE `category` SET `pid` = '{$pid}', `category` = '{$category}' WHERE `id` = '{$id}'";
    $res = $conn->query($sql);

    if ($res) {
        echo "<script>alert('修改成功');location.href='index.php';</script>";
    }
}

这个action.php文件主要就是接受不同的动作、接受前台提交过来的数据,然后拼接SQL语句以后执行对应的数据库操作。注意:在拼接sql语句的时候要主要字符串类型的数据需要加引号('),不然语句无法执行成功。