電商分類、CMS 欄目、組織架構、評論回覆等層級不固定的樹狀結構,均需無限級分類支援。本文分析兩種主流 PHP 實作:遞迴組裝法與單次查詢組裝法。
CREATE TABLE `category` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `parent_id` INT DEFAULT 0, -- 0 表示頂層 `sort` INT DEFAULT 0 );
class CategoryModel {
// 取得完整樹狀結構
public function getTree() {
$data = $this->select(); // SELECT * FROM category ORDER BY sort
return $this->buildTree($data);
}
// 遞迴建構樹
private function buildTree(array $data, $parentId = 0, $level = 0) {
static $tree = [];
foreach ($data as $item) {
if ($item['parent_id'] == $parentId) {
$item['level'] = $level;
$tree[] = $item;
$this->buildTree($data, $item['id'], $level + 1);
}
}
return $tree;
}
}
// 使用
$model = new CategoryModel();
$tree = $model->getTree();
// 輸出:扁平化陣列,按層級排序,可直接用 level 縮排顯示
xdebug.max_nesting_level(預設 256),極深層級可能溢位。function buildTreeIterative(array $items) {
$tree = [];
$children = [];
// 1. 建立 id => item 映射,並初始化 children 陣列
foreach ($items as &$item) {
$item['children'] = [];
$children[$item['id']] = &$item; // 使用引用
}
unset($item);
// 2. 單次遍歷掛載子節點
foreach ($items as &$item) {
$pid = $item['parent_id'];
if ($pid == 0) {
$tree[] = &$item; // 頂層節點
} elseif (isset($children[$pid])) {
$children[$pid]['children'][] = &$item; // 掛載到父節點
}
}
return $tree;
}
// 使用
$data = $db->query("SELECT * FROM category ORDER BY sort")->fetchAll(PDO::FETCH_ASSOC);
$tree = buildTreeIterative($data);
// $tree 為多維陣列,每節點含 children 子陣列
WHERE parent_id IN (...) 只載入特定分支。WITH RECURSIVE category_tree AS ( SELECT id, name, parent_id, 0 AS level, CAST(name AS CHAR(1000)) AS path FROM category WHERE parent_id = 0 UNION ALL SELECT c.id, c.name, c.parent_id, ct.level + 1, CONCAT(ct.path, ' > ', c.name) FROM category c JOIN category_tree ct ON c.parent_id = ct.id ) SELECT * FROM category_tree ORDER BY path;
優點:資料庫直接回傳層級與路徑,應用層無需組裝。缺點:需 MySQL 8.0+,複雜查詢可能較慢。
function renderSelect($tree, $selectedId = 0) {
$html = '';
return $html;
}
| 策略 | 查詢子孫 | 查詢祖先 | 移動子樹 | 適用場景 |
|---|---|---|---|---|
| Adjacency List (parent_id) | 需遞迴/CTE | 簡單 | 簡單 | 通用、寫多讀少 |
| Path Enumeration (path: 1/5/12/) | LIKE '1/5/%' | 簡單 | 需更新路徑 | 讀多、層級固定 |
| Nested Set (lft, rgt) | 極快 | 極快 | 複雜 | 極少變動、大量讀取 |
| Closure Table (祖先關係表) | JOIN 查詢 | JOIN 查詢 | 批次寫入 | 高頻讀寫、複雜查詢 |
理解各種儲存策略與組裝演算法的優劣,依專案規模與查詢模式選型,才能打造高效能、易維護的無限級分類系統。