樂猪先生

理解PHP无限级分类

2018-02-07

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

实验环境

1
2
3
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来进行访问即可。

代码仓库

1
待补充(gitee)

实验介绍

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

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

创建数据库、表

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

1
2
3
4
5
6
7
8
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;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,内容大致如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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类以后,可以使用下面一段简单的代码来测试下是否可以正常连接到数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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页面内容,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?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页面,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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的文件来处理前台提交过来的数据,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?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语句的时候要主要字符串类型的数据需要加引号('),不然语句无法执行成功。