首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP + MySQL 實現無限級分類完整教學與代碼實例

     編輯:PHP 綜合知識

前言

在 Web 開發中,商品分類、文章分類、評論回覆等場景常需要支援「無限級分類」(Infinite Category / 無限分類)。本文將詳細介紹如何使用 PHP 搭配 MySQL 資料庫,實作一套完整的無限分類系統,包含新增、刪除、編輯、移動節點等核心功能,並提供資料表結構與前台調用範例,方便直接套用至專案中。

資料庫設計:採用遞迴關聯模式

最常見且簡單的做法是在資料表中加入 f_id(父級 ID)欄位,指向同一張表的主鍵 id。頂層節點的 f_id 設為 0 或 NULL。

SQL 建表語句

CREATE TABLE IF NOT EXISTS `p_newsclass` (
  `id` INT(7) NOT NULL AUTO_INCREMENT,
  `f_id` INT(7) NOT NULL DEFAULT 0 COMMENT '父級分類 ID',
  `name` VARCHAR(255) NOT NULL COMMENT '分類名稱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=10;

初始化測試資料

INSERT INTO `p_newsclass` (`id`, `f_id`, `name`) VALUES
(3, 0, '中國'),
(4, 3, '福建'),
(5, 4, '龍巖市'),
(7, 4, '廈門市'),
(9, 5, '漳平市');

核心類別:SortClass 完整實作

以下類別封裝了分類樹的建構、遍歷、層級計算、CRUD 及節點移動等操作。程式碼已修正原始版本中的語法錯誤(如 emptyempty → empty、$_post → $_POST 等),並改用 mysqli 預處理語句提升安全性。

<?php
class SortClass {
    private $data   = [];   // id => name
    private $child  = [];   // parent_id => [child_ids]
    private $layer  = [];   // id => depth
    private $parent = [];   // id => parent_id
    private $link;
    private $table;

    public function __construct(mysqli $link, string $table) {
        $this->link  = $link;
        $this->table = $table;
        $this->setNode(0, -1, '頂極節點'); // 虛擬根節點

        $sql = "SELECT `id`, `f_id`, `name` FROM `{$this->table}`";
        $res = $this->link->query($sql);
        while ($row = $res->fetch_assoc()) {
            $this->setNode($row['id'], $row['f_id'], $row['name']);
        }
    }

    private function setNode(int $id, int $parent, string $value): void {
        $parent = $parent ?? 0;
        $this->data[$id]   = $value;
        $this->child[$id]  = [];
        $this->child[$parent][] = $id;
        $this->parent[$id] = $parent;
        $this->layer[$id]  = isset($this->layer[$parent]) ? $this->layer[$parent] + 1 : 0;
    }

    /** 遞迴取得指定節點下所有子孫 ID(含自身) */
    public function getChilds(int $id = 0): array {
        $list = [$id];
        $this->getList($list, $id);
        return $list;
    }

    private function getList(array &$tree, int $root): void {
        foreach ($this->child[$root] as $id) {
            $tree[] = $id;
            if (!empty($this->child[$id])) {
                $this->getList($tree, $id);
            }
        }
    }

    /** 取得節點名稱 */
    public function getValue(int $id): string {
        return $this->data[$id] ?? '';
    }

    /** 取得節點層級,可選擇回傳縮排字串 */
    public function getLayer(int $id, string $space = ''): string|int {
        return $space ? str_repeat($space, $this->layer[$id]) : $this->layer[$id];
    }

    /** 取得直接父節點 ID */
    public function getParent(int $id): int {
        return $this->parent[$id] ?? -1;
    }

    /** 取得所有祖先節點 ID 陣列(由根至父) */
    public function getParents(int $id): array {
        $parents = [];
        while (($pid = $this->parent[$id] ?? -1) !== -1) {
            $parents[] = $pid;
            $id = $pid;
        }
        return array_reverse($parents);
    }

    /** 取得直接子節點 ID 陣列 */
    public function getChild(int $id): array {
        return $this->child[$id] ?? [];
    }

    // ---------- CRUD 操作 ----------
    public function addNode(string $name, int $pid): bool {
        $stmt = $this->link->prepare("INSERT INTO `{$this->table}` (`f_id`, `name`) VALUES (?, ?)");
        $stmt->bind_param('is', $pid, $name);
        return $stmt->execute();
    }

    public function modNode(int $cid, string $newName): bool {
        $stmt = $this->link->prepare("UPDATE `{$this->table}` SET `name` = ? WHERE `id` = ?");
        $stmt->bind_param('si', $newName, $cid);
        return $stmt->execute();
    }

    public function delNode(int $cid): bool {
        $allChilds = $this->getChilds($cid);
        $ids       = array_merge([$cid], $allChilds);
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $this->link->prepare("DELETE FROM `{$this->table}` WHERE `id` IN ($placeholders)");
        $types = str_repeat('i', count($ids));
        $stmt->bind_param($types, ...$ids);
        return $stmt->execute();
    }

    public function moveNode(int $cid, int $topId): bool {
        $stmt = $this->link->prepare("UPDATE `{$this->table}` SET `f_id` = ? WHERE `id` = ?");
        $stmt->bind_param('ii', $topId, $cid);
        return $stmt->execute();
    }
}
?>

前台表單範例:新增、編輯、刪除、移動分類

搭配簡單的 HTML 表單與 JavaScript,即可完成後台管理介面。下方程式碼示範如何產生巢狀下拉選單、顯示分類樹、處理表單提交。

<?php
// 連線資料庫(建議改用 mysqli)
$link = new mysqli('localhost', 'root', '', 'class');
$link->set_charset('utf8');

require_once 'SortClass.php';
$tree = new SortClass($link, 'p_newsclass');

// 處理表單動作
$op = $_REQUEST['op'] ?? '';
if ($op) {
    switch ($op) {
        case 'add':
            $tree->addNode($_POST['cname'], (int)$_POST['pid']);
            break;
        case 'mod':
            $tree->modNode((int)$_POST['cid'], $_POST['cname']);
            break;
        case 'del':
            $tree->delNode((int)$_GET['cid']);
            break;
        case 'move':
            $tree->moveNode((int)$_POST['who'], (int)$_POST['to']);
            break;
    }
    // 重導向避免重複提交
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// 取得完整分類樹(含虛擬根節點 0 的所有子孫)
$category = $tree->getChilds(0);

// 產生下拉選單的輔助函式
function makeSelect(array $ids, SortClass $tree, string $name): string {
    $html = '<select name="' . $name . '">';
    foreach ($ids as $id) {
        if ($id === 0) continue; // 跳過虛擬根節點
        $indent = $tree->getLayer($id, '|— ');
        $html .= '<option value="' . $id . '">' . htmlspecialchars($indent . $tree->getValue($id)) . '</option>';
    }
    return $html . '</select>';
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>無限分類管理示範</title>
    <style>
        body {font-family: sans-serif; font-size: 14px;}
        ul {list-style: none; padding-left: 1.5em;}
        li {margin: 4px 0;}
        a {cursor: pointer; color: #0066cc; text-decoration: none;}
        a:hover {text-decoration: underline;}
    </style>
</head>
<body>
<h2>新增分類</h2>
<form method="post">
    名稱:<input type="text" name="cname" required>
    所屬分類:
    <input type="hidden" name="op" value="add">
    <button type="submit">送出</button>
</form>

<h2>移動分類</h2>
<form method="post">
    選擇分類:
    移動到:
    <input type="hidden" name="op" value="move">
    <button type="submit">執行移動</button>
</form>

<h2>分類列表</h2>
<ul>
    <?php foreach ($category as $id): if ($id === 0) continue; ?>
        <li>
            getLayer($id, '|— ') . htmlspecialchars($tree->getValue($id)) ?>
            <a href="?op=del&cid=" onclick="return confirm('確定刪除?')">刪除</a>
            <a href="#" onclick="editNode(, 'getValue($id), ENT_QUOTES) ?>')">編輯</a>
        </li>
    <?php endforeach; ?>
</ul>

<!-- 編輯用隱藏表單 -->
<form id="editForm" method="post" style="display:none;">
    <input type="hidden" name="op" value="mod">
    <input type="hidden" name="cid" id="editCid">
    新名稱:<input type="text" name="cname" id="editName" required>
    <button type="submit">更新</button>
</form>

<script>
function editNode(cid, cname) {
    document.getElementById('editCid').value = cid;
    document.getElementById('editName').value = cname;
    document.getElementById('editForm').style.display = 'block';
    document.getElementById('editName').focus();
}
</script>
</body>
</html>

進階優化建議

  • 效能優化:資料量大時,可改用「路徑枚舉法」或「巢狀集合模型」減少遞迴查詢。
  • 快取機制:將組裝好的分類樹 JSON 快取至 Redis 或檔案,降低資料庫負載。
  • 軟刪除:新增 is_deleted 欄位,避免誤刪導致資料遺失。
  • 排序欄位:加入 sort_order 讓同層級分類可自訂顯示順序。

總結

本文提供了一套完整、可直接部署的 PHP + MySQL 無限分類解決方案,涵蓋資料庫設計、核心類別實作、前台管理介面與進階優化方向。開發者可根據專案需求,直接整合或擴充功能(如多語系、權限控制、API 輸出等)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved