在 Ubuntu 20.04 上使用 PHP 和 MySQL 创建动态分层列表

介绍

嵌套类别是一个分层列表,可以支持子类别直到 nth 等级。 通常,您可以在设计电子商务网站、会计系统、动物王国、地理空间数据等应用程序时使用该列表。 为了更好地理解此模型,请考虑公司阶梯。 在最高层,你有 CEO。 然后,可以有几个部门负责人向他们报告。 然后,从每个部门,您可以在监督团队下拥有数千名员工。 另一个很好的例子是使用多级目录层次结构的 Linux 文件系统。

在大多数情况下,您将使用服务器端脚本语言(例如 Python、Node.js 或 PHP)而不是在数据库级别对动态嵌套多级类别进行编码。 但是,您的脚本应该从关系数据库(例如 MySQL)中提取数据。 虽然创建分层列表可能看起来很简单,但您需要使用递归函数来实现此功能。

在本指南中,您将设置一个示例数据库,其中包含组织在一个表中并由 parent_id 柱子。 然后,您将使用递归 PHP 函数来检索类别并将它们组织成树状结构。 您的最终清单本质上是动态的。 也就是说,您可以在列表中插入或删除条目,而不会影响其他项目的顺序。

先决条件

要完成这个 PHP 多级层次列表教程,请确保您具备以下条件:

一个 Ubuntu 20.04 服务器。 一个 sudo 用户。 灯组。

1. 设置示例数据库

要维护类别的层次结构,您需要定义一个数据库,该数据库通过链接表存储连接的数据。 SSH 到您的服务器并以以下身份登录 MySQL root.

$ sudo mysql -u root -p

接下来,输入 MySQL 服务器的 root 密码,然后按 ENTER 继续。 然后,发出以下命令以创建示例 store_db 数据库和特权 store_db_user 用户。 代替 EXAMPLE_PASSWORD 具有很强的价值。

mysql> CREATE DATABASE store_db;
       CREATE USER 'store_db_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
       GRANT ALL PRIVILEGES ON store_db.* TO 'store_db_user'@'localhost';           
       FLUSH PRIVILEGES;

确认下面的输出以确保您已成功创建数据库和用户。

...
Query OK, 0 rows affected (0.01 sec) 

接下来,运行 MySQL USE 切换到新的语句 store_db 数据库。

mysql> USE store_db;

通过确认下面的输出,确保您选择了新数据库。

Database changed

接下来,您将设置一个表来存储产品类别。 在这个表中,每个条目都包含一个 category_id 这是一个独特的 PRIMARY KEY 定义为 BIGINT. 然后,该 category_name 列为您的类别存储一个人类可读的字符串(例如, SHOES, T-SHIRTS, 和 COMPUTERS) 在一个 VARCHAR(50) 数据类型。 然后,该 parent_id 将子类别链接到主要类别。

创建 product_categories 通过执行以下语句来表。

mysql> CREATE TABLE products_categories (
           category_id BIGINT PRIMARY KEY,
           category_name VARCHAR(50),
           parent_id INT              
       ) ENGINE = InnoDB;

验证下面的输出以确保您已设置表。

Query OK, 0 rows affected (0.03 sec)

一旦 store_db 数据库和 product_categories 表就位,您将插入一些示例记录以进行测试。

2. 在数据库表中插入产品类别

在这个示例应用程序中,您将假设一个类似于下面显示的输出的数据层次结构。

SHOES
--------OFFICE SHOES
--------CASUAL SHOES
--------SAFETY BOOTS
T-SHIRTS
--------POLO NECK
--------V-NECK
COMPUTERS
--------DELL
------------DESKTOPS
------------LAPTOPS
--------HP
------------NOTEBOOKS
------------WORKSTATIONS

在上面的列表中,您已经 3 主要类别。 那是, SHOES, T-SHIRTS, 和 COMPUTERS. 然后每个类别有一个或几个级别的子类别。 例如,在 COMPUTERS 类别,你已经 DELLHP 作为子类。 此外,您还进一步分类 DELLDESKTOPSLAPTOPS, 和 HPNOTEBOOKSWORKSTATIONS.

要创建将保留和存储上述层次列表关系的数据库模式,您需要输入 parent_id 每个子类别中与父类别相关的列,类似于以下输出。

+-------------+---------------+-----------+
| category_id | category_name | parent_id |
+-------------+---------------+-----------+
|           1 | SHOES         |         0 |
|           2 | T-SHIRTS      |         0 |
|           3 | COMPUTERS     |         0 |
|           4 | OFFICE SHOES  |         1 |
|           5 | CASUAL SHOES  |         1 |
|           6 | SAFETY BOOTS  |         1 |
|           7 | POLO NECK     |         2 |
|           8 | V-NECK        |         2 |
|           9 | DELL          |         3 |
|          10 | DESKTOPS      |         9 |
|          11 | LAPTOPS       |         9 |
|          12 | HP            |         3 |
|          13 | NOTEBOOKS     |        12 |
|          14 | WORKSTATIONS  |        12 |
+-------------+---------------+-----------+

为此,请将以下记录插入 products_categories 通过执行以下命令来创建表格:

首先插入主要类别。

mysql> INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (1, 'SHOES', 0);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (2, 'T-SHIRTS', 0);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (3, 'COMPUTERS', 0);

然后,输入 3 SHOES 子类别。 请注意:对于这些子类别,您使用的是 1 作为价值 parent_id 与回相关的列 SHOES 类别。

mysql> INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (4, 'OFFICE SHOES', 1);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (5, 'CASUAL SHOES', 1);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (6, 'SAFETY BOOTS', 1);

接下来,输入 2 T-SHIRTS 子类别。 同样,您正在使用 2 作为价值 parent_id 列将记录链接到 T-SHIRTS 类别。

mysql> INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (7, 'POLO NECK', 2);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (8, 'V-NECK', 2);

然后,插入 DELL 作为一个子类 category_id9 在下面 COMPUTERS 父类。 接下来,在下创建另外两个子类别 DELL 并记住改变他们的价值 parent_ids9.

mysql> INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (9, 'DELL', 3);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (10, 'DESKTOPS', 9);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (11, 'LAPTOPS', 9);

最后,创建一个 HP 子类别 category_id12 仍然在主要父母之下 COMPUTERS 类别。 然后,插入 NOTEBOOKSWORKSTATIONS 下的子类别 HP.

mysql> INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (12, 'HP', 3);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (13, 'NOTEBOOKS', 12);
       INSERT INTO products_categories(category_id, category_name, parent_id) VALUES (14, 'WORKSTATIONS', 12);

每次执行后 INSERT 命令,您应该会收到以下输出。

Query OK, 1 row affected (0.00 sec)

使用 MySQL SELECT 声明以确认来自 products_categories 桌子。

mysql> SELECT 
       category_id,
       category_name,
       parent_id
       FROM  products_categories;

您现在应该看到以下输出。

+-------------+---------------+-----------+
| category_id | category_name | parent_id |
+-------------+---------------+-----------+
|           1 | SHOES         |         0 |
|           2 | T-SHIRTS      |         0 |
|           3 | COMPUTERS     |         0 |
|           4 | OFFICE SHOES  |         1 |
|           5 | CASUAL SHOES  |         1 |
|           6 | SAFETY BOOTS  |         1 |
|           7 | POLO NECK     |         2 |
|           8 | V-NECK        |         2 |
|           9 | DELL          |         3 |
|          10 | DESKTOPS      |         9 |
|          11 | LAPTOPS       |         9 |
|          12 | HP            |         3 |
|          13 | NOTEBOOKS     |        12 |
|          14 | WORKSTATIONS  |        12 |
+-------------+---------------+-----------+
14 rows in set (0.00 sec)

从 MySQL 命令行界面退出。

mysql> QUIT;

输出。

Bye 

您现在已经格式化并将示例分层数据插入到数据库表中。 接下来,您将编写一个 PHP 脚本,该脚本使用递归函数循环遍历记录并创建嵌套列表。

3.用PHP递归函数创建嵌套列表

在编程中,您不能通过使用仅执行一次循环语句的线性函数来创建嵌套列表。 相反,您必须实现递归过程。 也就是说,您必须想出一个直接或间接调用自身的例程。

为了更好地看待事情,您应该编写一个从数据库中获取父级或基本产品类别的函数。 然后,在检索每个父记录后,脚本应递归循环以获取子类别直到 nth 等级。

为此,请使用 nano 创建一个新的 /var/www/html/products_categories.php 文件。

$ sudo nano /var/www/html/products_categories.php

然后,将下面的信息输入到 /var/www/html/products_categories.php 文件。 代替 EXAMPLE_PASSWORD 具有正确的值。

<?php 

    function getPdo(){

        try {

            $db_name="store_db";
            $db_user="store_db_user";
            $db_password = 'EXAMPLE_PASSWORD';
            $db_host="localhost";

            $pdo = new PDO('mysql:host=" . $db_host . "; dbname=" . $db_name, $db_user, $db_password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            return $pdo;

           } catch (PDOException $e) {
               echo $e->getMessage();
           }
    }

    try {
             $pdo = getPdo();

             $sql  = "select * from products_categories where parent_id = 0';           
             $stmt = $pdo->prepare($sql);
             $stmt->execute(); 

             $data = [];

             while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                 echo $row['category_name'] . '<br>';
                 getSubCategories($row['category_id'], 0);
             }                  

        } catch (Exception $e) {
            echo $e->getMessage();
        }

    function getSubCategories($parent_id, $level) {

        try {

            $pdo = getPdo();

            $sql = "select * from products_categories where parent_id = '$parent_id'";                

            $stmt = $pdo->prepare($sql);
            $stmt->execute(); 

            $data = [];  

            $level++;                 

            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                echo str_repeat("-", ($level * 4)) . $row['category_name'] . '<br>';                    
                getSubCategories($row['category_id'], $level);                                    
            } 

        } catch (Exception $e) {
            echo $e->getMessage();
        }                 
    }

完成编辑后保存并关闭文件。

/var/www/html/products_categories.php 解释:

在脚本的开头,您已经创建了一个 PHP getPdo() 连接到数据库并返回一个的函数 PDO 目的 ($pdo) 可以被其他函数重用。

接下来,您将连接到数据库服务器并检索所有 父母 使用语句的类别 select * from products_categories where parent_id = 0. 记住父类别有一个 parent_id0 因为它们出现在层次结构列表的顶部。

然后,您将使用以下语句循环浏览主要类别。 线 while (...) {...} 语句为在数据库表中找到的每个父类别执行。 然后,神奇的事情发生在这里,对于每个父类别,你调用一个单独的 getSubCategories 功能如下图。

         while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
             echo $row['category_name'] . '<br>';
             getSubCategories($row['category_id'], 0);
         }   

最后,函数 getSubCategories($parent_id, $level) { } 再次连接到数据库并检索请求的主类别下的任何子类别。 请注意,此函数接受两个参数。 第一个参数是 $parent_id 要检索子类别的类别。 这 $level 参数是一个动态值。 对于父类别, $level 价值永远是 0. 但是,每次调用递归函数时它都会增加一次。 你通过包括 $level++; 陈述。

当您使用 getSubCategories 例程,该函数不断调用自己,直到找到所有子类别直到 nth 等级。 当。。。的时候 getSubCategories 函数调用自身,这称为 递归.

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            echo str_repeat("-", ($level * 4)) . $row['category_name'] . '<br>';                    
            getSubCategories($row['category_id'], $level);                                    
        } 

该声明 echo str_repeat("-", ($level * 4)) 把一些格式化 - 每个列表项开头的字符,以确定记录显示时的显示深度。 您也可以使用空格(echo str_repeat(" ", ($level * 4))) 或者 * 象征 (echo str_repeat("*", ($level * 4)))。

您现在已经编写了递归函数以显示父类别和子类别。 在下一步中,您将测试您的应用程序。

4. 测试递归函数

在此步骤中,您将在 Web 浏览器上运行脚本。 打开下面的网址并替换 192.0.2.1 使用正确的公共 IP 地址或域名。

http://192.0.2.1/products_categories.php

您现在应该看到以下输出,以良好的格式显示所有类别和相关的子类别。

SHOES
--------OFFICE SHOES
--------CASUAL SHOES
--------SAFETY BOOTS
T-SHIRTS
--------POLO NECK
--------V-NECK
COMPUTERS
--------DELL
------------DESKTOPS
------------LAPTOPS
--------HP
------------NOTEBOOKS
------------WORKSTATIONS

递归函数现在按预期工作。

结论

在本指南中,您已使用 PHP 递归函数在分层列表中组织产品类别。 您可以使用此逻辑来格式化其他类型的信息,包括会计系统中的会计科目表、服务器中的文件/目录、组织结构中的部门、地理空间应用程序中的城镇和相关数据等等。

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com