萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

ECShop 無限級分類遞歸算法實現解析

業務場景

電商系統中商品分類通常為樹狀結構(無限級)。後台需要在下拉框中展示層級縮進的分類列表,前台需生成導航菜單、麵包屑等。

ECShop 經典實現核心代碼

<?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;
}
?>

關鍵設計點解析

  1. 靜態變量緩存:static $catOptions 在請求生命周期內只查一次庫,多次調用極快。
  2. 鄰接表模型:數據庫存 parent_id,內存中構建 $children[parent_id] = [child_ids] 映射,遞歸遍歷 O(N)。
  3. 層級縮進:遞歸傳遞 $level,生成    實現視覺層級。
  4. 鍵值保持:返回數組鍵為 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/%',極快。
  • 緩存全量樹:Redis 存儲 JSON 序列化樹,前端直接渲染,後台更新時清理緩存。

總結

ECShop 的無限級分類實現是典型的「鄰接表 + 內存遞歸 + 靜態緩存」模式,適合中小數據量。大型系統建議遷移至閉包表或物化路徑模型,配合 Redis 緩存,實現高性能分類查詢。

copyright © 萬盛學電腦網 all rights reserved