電商系統中商品分類通常為樹狀結構(無限級)。後台需要在下拉框中展示層級縮進的分類列表,前台需生成導航菜單、麵包屑等。
<?php
/**
* 獲取分類選項數組(帶縮進)
* @param int $specCatId 指定分類 ID,0 為頂級
* @param array $arr 可選,預載入的分類數據
* @return array 鍵為 cat_id,值為帶縮進的 cat_name
*/
function catOptions(int $specCatId = 0, array $arr = []): array {
static $catOptions = []; // 靜態緩存,避免重複查詢
// 緩存命中
if (isset($catOptions[$specCatId])) {
return $catOptions[$specCatId];
}
// 首次調用:初始化靜態緩存結構
if (!isset($catOptions[0])) {
// 1. 從數據庫一次性讀取所有分類
// 假設表結構:cat_id, parent_id, cat_name, sort_order, is_show
$sql = "SELECT cat_id, parent_id, cat_name, sort_order FROM ecs_category WHERE is_show = 1 ORDER BY sort_order, cat_id";
$rows = $GLOBALS['db']->getAll($sql); // ECShop 資源庫方法
// 2. 構建父子關係映射
$children = [];
$names = [];
foreach ($rows as $row) {
$children[$row['parent_id']][] = $row['cat_id'];
$names[$row['cat_id']] = $row['cat_name'];
}
// 3. 遞歸構建帶層級的選項數組
$catOptions[0] = buildOptions(0, $children, $names);
}
// 返回指定父分類下的子分類選項
return $catOptions[$specCatId] ?? [];
}
/**
* 遞歸構建選項
*/
function buildOptions(int $parentId, array $children, array $names, int $level = 0): array {
$options = [];
$indent = str_repeat(' ', $level); // HTML 縮進
foreach ($children[$parentId] ?? [] as $catId) {
$options[$catId] = $indent . $names[$catId];
// 遞歸處理子分類
$childOpts = buildOptions($catId, $children, $names, $level + 1);
$options += $childOpts; // 合並數組,保持鍵不變
}
return $options;
}
?>
static $catOptions 在請求生命周期內只查一次庫,多次調用極快。parent_id,內存中構建 $children[parent_id] = [child_ids] 映射,遞歸遍歷 O(N)。$level,生成 實現視覺層級。cat_id,值為顯示名,直接用於 <select> 的 option value。<?php
class CategoryTree {
private $pdo;
private $cacheKey = 'category_tree_options';
private $ttl = 3600; // 1 小時緩存
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function getOptions(int $parentId = 0): array {
$tree = $this->getFullTree();
return $this->buildOptions($tree, $parentId);
}
private function getFullTree(): array {
// 嘗試從 Redis/APCu 獲取
$cached = apcu_fetch($this->cacheKey);
if ($cached !== false) return $cached;
// 查庫構建
$stmt = $this->pdo->query("SELECT cat_id, parent_id, cat_name, sort_order FROM category WHERE is_show = 1 ORDER BY sort_order, cat_id");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$tree = ['children' => [], 'names' => []];
foreach ($rows as $row) {
$tree['children'][$row['parent_id']][] = $row['cat_id'];
$tree['names'][$row['cat_id']] = $row['cat_name'];
}
apcu_store($this->cacheKey, $tree, $this->ttl);
return $tree;
}
private function buildOptions(array $tree, int $parentId, int $level = 0): array {
$options = [];
$indent = str_repeat('── ', $level); // CLI/後台友好
foreach ($tree['children'][$parentId] ?? [] as $catId) {
$options[$catId] = $indent . $tree['names'][$catId];
$options += $this->buildOptions($tree, $catId, $level + 1);
}
return $options;
}
}
// 使用
$tree = new CategoryTree($pdo);
$options = $tree->getOptions(0); // 所有分類
$subOptions = $tree->getOptions(5); // 某分類下的子分類
?>
<select name="cat_id">
$name): ?>
</select>
path (如 1/5/12/) 和 depth 字段,單次查詢即可獲取子樹、祖先鏈,無需遞歸。WHERE path LIKE '1/5/%',極快。ECShop 的無限級分類實現是典型的「鄰接表 + 內存遞歸 + 靜態緩存」模式,適合中小數據量。大型系統建議遷移至閉包表或物化路徑模型,配合 Redis 緩存,實現高性能分類查詢。