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 语句的时候要主要字符串类型的数据需要加引号 ('),不然语句无法执行成功。

Last Modified: November 10, 2019