使用递归构建 N 级类别(category)树
建立多级嵌套分类树或菜单总是个问题。许多内容管理系统和网站只允许一定程度的嵌套。为了避免多重连接带来的性能问题,有些网站最多只允许 3-4 级嵌套。现在,我们将探讨如何在不影响性能的情况下,借助递归创建 N 级嵌套分类树或菜单。以下是我们的解决方案:
-
我们将为数据库中的类别定义表结构。
-
我们将在不使用任何连接或多重查询的情况下获取表中的所有类别。这将是一个使用简单选择语句的单一数据库查询。
-
我们将建立一个类别数组,以便利用递归来显示嵌套类别或菜单。
假设我们的数据库中有一个简单的表结构来存储类别,它看起来是这样的:
CREATE TABLE `categories` (
`id` int(11) NOT NULL,
`categoryName` varchar(100) NOT NULL,
`parentCategory` int(11) DEFAULT 0,
`sortInd` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
为简单起见,我们假设表中不需要其他字段。此外,我们还在表中添加了一些数据,如下所示:
Id | categoryName | parentCategory | sortInd |
---|---|---|---|
1 |
First |
0 |
0 |
2 |
Second |
1 |
0 |
3 |
Third |
1 |
1 |
4 |
Fourth |
3 |
0 |
5 |
Fifth |
4 |
0 |
6 |
Sixth |
5 |
0 |
7 |
Seventh |
6 |
0 |
8 |
Eight |
7 |
0 |
9 |
Ninth |
1 |
0 |
10 |
Tenth |
2 |
1 |
现在,我们已经为数据库创建了一个表结构,并假设输入了一些示例数据。让我们建立一个查询来检索这些数据,这样我们就可以进入递归解决方案了:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/4.php[]
前面代码的核心部分是我们如何在数组中存储类别。我们根据父类别来存储结果。这将帮助我们以递归方式显示类别的子类别。这看起来非常简单。现在,基于类别数组,让我们编写递归函数来分层显示类别:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/4.php[]
前面的代码实际上是递归显示所有类别及其子类别。我们选取一个层级,首先打印该层级上的类别。紧接着,我们将使用代码 showCategoryTree($categories, $category->id)
检查它是否有任何子级类别。现在,如果我们使用根层级(层级 0)调用递归函数,那么输出结果如下:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/4.php[]
其输出如下:
First
-Second
--Tenth
-Third
---Fourth
----fifth
-----Sixth
------seventh
-------Eighth
-Nineth
正如我们所看到的,不考虑类别级别或多个查询的深度,我们只需一个简单的查询和递归函数即可构建嵌套类别或菜单。如果我们想要具有动态显示和隐藏功能,我们可以使用 <ul>
和 <li>
来创建嵌套菜单。这对于在不涉及实现块的情况下高效解决问题至关重要,例如具有固定级别的连接或固定级别的类别。前面的例子完美地展示了尾递归,我们不需要等待递归返回任何东西,随着我们的前进,结果已经显示出来了。
构建嵌套评论回复系统
很多时候,我们都面临着以适当方式显示评论回复的挑战。按时间顺序显示有时并不符合我们的需要。我们可能需要将每条评论的回复显示在实际评论的下方。换句话说,我们需要嵌套评论回复系统或线程评论。
我们想要构建类似于以下屏幕截图的东西:

我们可以沿用嵌套类别部分的相同步骤。不过,这次我们将使用一些用户界面元素,使其看起来更真实。假设我们有一个名为 comments
的表,其中包含以下数据和列。为简单起见,我们不讨论多表关系。我们假设用户名与评论存储在同一个表中:
Id | comments | username | Datetime | parentID | postID |
---|---|---|---|---|---|
1 |
First comment |
Mizan |
2016-10-01 15:10:20 |
0 |
1 |
2 |
First reply |
Adiyan |
2016-10-02 04:09:10 |
1 |
1 |
3 |
Reply of first reply |
Mikhael |
2016-10-03 11:10:47 |
2 |
1 |
4 |
Reply of reply of first reply |
Arshad |
2016-10-04 21:22:45 |
3 |
1 |
5 |
Reply of reply of reply of first reply |
Anam |
2016-10-05 12:01:29 |
4 |
1 |
6 |
Second comment |
Keith |
2016-10-01 15:10:20 |
0 |
1 |
7 |
First comment of second post |
Milon |
2016-10-02 04:09:10 |
0 |
2 |
8 |
Third comment |
Ikrum |
2016-10-03 11:10:47 |
0 |
1 |
9 |
Second comment of second post |
Ahmed |
2016-10-04 21:22:45 |
0 |
2 |
10 |
Reply of second comment of second post |
Afsar |
2016-10-18 05:18:24 |
9 |
2 |
现在,让我们编写一条准备语句来获取一篇文章中的所有评论。然后,我们可以构建一个与嵌套类别类似的数组:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/5.php[]
现在,我们有了数组和其中所需的所有数据;我们现在可以编写一个函数,递归调用该函数以适当的缩进显示评论:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/5.php[]
由于我们在 PHP 代码中添加了一些 HTML 元素,因此我们需要一些基本的 CSS 来使其工作。以下是我们为使设计简洁而编写的 CSS 代码。没有什么花哨的东西,只是用纯 CSS 来创建层叠效果,并为注释的每个部分创建一些基本样式:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-03.adoc - include::example$Chapter05/5.php[]
如前所述,我们在此并不试图制作复杂的东西,只是为了响应速度快、设备友好等。我们的假设是,您可以将逻辑集成到应用程序的不同部分,而不会出现任何问题。
以下是数据和前面代码的输出结果:

从前面两个示例中,我们可以看出,创建嵌套内容非常简单,无需多个查询,也不受嵌套连接语句的限制。我们甚至不需要自连接来生成嵌套数据。