處理無限分類、選單、組織架構等樹狀資料時,遞迴是核心技巧。本文示範如何從扁平資料庫結果建構巢狀陣列樹,並提供兩種常見遞迴模式。
資料表 otoarea:
| 欄位 | 說明 |
|---|---|
| tid | 主鍵 ID |
| oid | 父節點 ID (0 為根) |
| areaname | 節點名稱 |
<?php
class AreaService {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
/**
* 遞迴建構樹狀結構
* @param int $parentId 父節點 ID
* @return array 巢狀陣列
*/
public function buildTree(int $parentId = 0): array {
// 1. 取得直屬子節點
$stmt = $this->db->prepare("SELECT tid, areaname FROM otoarea WHERE oid = ?");
$stmt->execute([$parentId]);
$children = $stmt->fetchAll(PDO::FETCH_ASSOC);
$tree = [];
foreach ($children as $child) {
$node = [
'node' => $child['areaname'],
'value' => $child['tid'],
];
// 2. 遞迴取得子孫節點
$descendants = $this->buildTree($child['tid']);
if (!empty($descendants)) {
$node['nodes'] = $descendants;
}
$tree[] = $node;
}
return $tree;
}
}
// 使用方式
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$service = new AreaService($pdo);
$tree = $service->buildTree(0); // 從根節點開始
echo json_encode($tree, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
[
{
"node": "台灣",
"value": 1,
"nodes": [
{
"node": "台北市",
"value": 2,
"nodes": [
{ "node": "信義區", "value": 5 },
{ "node": "大安區", "value": 6 }
]
},
{
"node": "高雄市",
"value": 3
}
]
}
]
遞迴查詢會產生 N+1 次 SQL,資料量大時極慢。改為一次查詢全表,PHP 組裝:
<?php
function buildTreeOptimized(PDO $db, int $rootId = 0): array {
// 1. 一次撈取所有節點
$stmt = $db->query("SELECT tid, oid, areaname FROM otoarea");
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. 建立 ID => 節點映射,並初始化 children 陣列
$map = [];
foreach ($all as $row) {
$map[$row['tid']] = [
'node' => $row['areaname'],
'value' => $row['tid'],
'nodes' => [],
];
}
// 3. 依父子關係掛載
$tree = [];
foreach ($all as $row) {
$id = $row['tid'];
$pid = $row['oid'];
if ($pid == $rootId) {
$tree[] = &$map[$id]; // 根節點直接放入樹
} elseif (isset($map[$pid])) {
$map[$pid]['nodes'][] = &$map[$id]; // 掛載到父節點
}
// 孤兒節點(父不存在)可選擇忽略或放入特殊陣列
}
return $tree;
}
?>
| 維度 | 遞迴查詢 | 單次查詢組裝 |
|---|---|---|
| SQL 次數 | O(N) 次 (N=節點數) | 1 次 |
| 記憶體 | 低 | O(N) |
| 程式碼複雜度 | 低 | 中 |
| 適用資料量 | < 1000 節點 | 任意大小 |
| 即時性 | 最高 (每次查最新) | 需重新查詢更新 |
<?php
function renderTree(array $tree): string {
$html = '<ul>';
foreach ($tree as $node) {
$html .= '<li>' . htmlspecialchars($node['node']);
if (!empty($node['nodes'])) {
$html .= renderTree($node['nodes']);
}
$html .= '</li>';
}
$html .= '</ul>';
return $html;
}
echo renderTree($tree);
?>
buildTree($pid, $depth = 0),超過 100 層拋出異常CHECK (oid != tid) 防止自我循環希望本文助您掌握 PHP 陣列樹狀結構建構與渲染技巧。