無限級分類是 CMS、電商、論壇等系統的核心功能。本文提供一個功能完整的 PHP 類庫,支援新增、刪除、編輯、移動節點,並包含資料表結構與前台展示範例,開箱即用。
採用鄰接表模式,結構簡單高效:
CREATE TABLE `p_newsclass` (
`id` int(7) NOT NULL AUTO_INCREMENT,
`f_id` int(7) NOT NULL DEFAULT 0 COMMENT '父級 ID,0 為頂層',
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 示例資料
INSERT INTO `p_newsclass` (`id`, `f_id`, `name`) VALUES
(3, 0, '中國'),
(4, 3, '福建'),
(5, 4, '龍巖市'),
(7, 4, '廈門市'),
(9, 5, '漳平市');
SortClass<?php
class SortClass {
var $data = []; // id => name
var $child = [-1 => []]; // parent_id => [child_ids]
var $layer = [-1 => -1]; // id => depth
var $parent = []; // id => parent_id
var $link; // DB 連線資源
var $table; // 資料表名
function __construct($link, $table) {
$this->setNode(0, -1, '頂極節點'); // 虛擬根節點
$this->link = $link;
$this->table = $table;
$results = mysql_query("SELECT * FROM `$this->table`", $this->link);
while ($node = mysql_fetch_assoc($results)) {
$this->setNode($node['id'], $node['f_id'], $node['name']);
}
}
// 註冊節點到記憶體結構
function setNode($id, $parent, $value) {
$parent = $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 列表 (前序遍歷)
function getList(&$tree, $root = 0) {
foreach ($this->child[$root] as $id) {
$tree[] = $id;
if ($this->child[$id]) $this->getList($tree, $id);
}
}
// 公開 API
function getValue($id) { return $this->data[$id]; }
function getLayer($id, $space = false) {
return $space ? str_repeat($space, $this->layer[$id]) : $this->layer[$id];
}
function getParent($id) { return $this->parent[$id]; }
function getParents($id) { /* 取得所有祖先鏈 */ }
function getChild($id) { return $this->child[$id]; }
function getChilds($id = 0) { $arr = [$id]; $this->getList($arr, $id); return $arr; }
// CRUD 操作
function addNode($name, $pid) {
mysql_query("INSERT INTO `$this->table` (`f_id`,`name`) VALUES ('$pid','$name')", $this->link);
}
function modNode($cid, $newname) {
mysql_query("UPDATE `$this->table` SET `name`='$newname' WHERE `id`=$cid", $this->link);
}
function delNode($cid) {
$all = $this->getChilds($cid);
$sql = empty($all)
? "DELETE FROM `$this->table` WHERE `id`=$cid"
: "DELETE FROM `$this->table` WHERE `id` IN (" . implode(',', $all) . ",$cid)";
mysql_query($sql, $this->link);
}
function moveNode($cid, $topid) {
mysql_query("UPDATE `$this->table` SET `f_id`=$topid WHERE `id`=$cid", $this->link);
}
}
?>
<?php
// 連線資料庫 (建議改用 PDO/MySQLi)
$link = mysql_connect('localhost','root','') or die(mysql_error());
mysql_select_db('class', $link) or die(mysql_error());
mysql_query("SET NAMES utf8");
$tree = new SortClass($link, '`p_newsclass`');
// 處理表單提交
$op = $_POST['op'] ?? $_GET['op'] ?? '';
if ($op) {
switch ($op) {
case 'add': $tree->addNode($_POST['cname'], $_POST['pid']); break;
case 'mod': $tree->modNode($_POST['cid'], $_POST['cname']); break;
case 'del': $tree->delNode($_GET['cid']); break;
case 'move': $tree->moveNode($_POST['who'], $_POST['to']); break;
}
// 重導向防止重複提交
header("Location: " . $_SERVER['PHP_SELF'] . "?" . time());
exit;
}
$category = $tree->getChilds(); // 取得完整樹結構 ID 列表
?>
<!DOCTYPE html>
<html>
<head>
<style> body{font-size:12px;} ul{list-style:none;} a{cursor:pointer;} </style>
<script> function $(e){return document.getElementById(e);} function mod(cid){ $('cid').value=cid; $('op').value='mod'; $('name').style.border='1px solid red'; } </script>
</head>
<body>
<h3>新增/編輯分類</h3>
<form method="post">
名稱: <input type="text" id="name" name="cname" />
添加到: <?= makeSelect($category, 'pid') ?><br />
<input type="hidden" id="op" name="op" value="add" />
<input type="hidden" id="cid" name="cid" />
<input type="submit" value="提交" />
</form>
<h3>移動分類</h3>
<form method="post">
<?= makeSelect($category, 'who') ?> 移動到: <?= makeSelect($category, 'to') ?>
<input type="hidden" name="op" value="move" />
<input type="submit" value="提交" />
</form>
<h3>分類列表</h3>
<ul>
<?php foreach ($category as $id): ?>
<li>
<?= $tree->getLayer($id, '|- ') ?> <?= $tree->getValue($id) ?>
<a href="?op=del&cid=<?=$id?>" onclick="return confirm('確定刪除?')">刪除</a>
<a onclick="mod(<?=$id?>)">編輯</a>
</li>
<?php endforeach; ?>
</ul>
<?php
// 輔助函數:生成下拉選單
function makeSelect($array, $formname) {
global $tree;
$html = '<select name="' . $formname . '">';
foreach ($array as $id) {
$html .= '<option value="' . $id . '">' . $tree->getLayer($id, '|-') . $tree->getValue($id) . '</option>';
}
return $html . '</select>';
}
?>
</body>
</html>
mysql_* 替換為 PDO 或 MySQLi,使用預處理語句防止 SQL 注入。這個類庫展示了無限分類的核心邏輯,理解其遞迴構建與陣列映射原理,您即可根據專案需求進行擴展與優化。