在 ECShop 二次開發中,需根據分類 ID 遞歸查找頂級分類 ID。初版代碼邏輯正確,但永遠返回 null:
<?php
// ❌ 錯誤版本:遞歸調用未返回值
function getCatTopId($cat_id) {
if ($cat_id) {
$sql = "SELECT cat_id, parent_id FROM ecs_category WHERE cat_id = $cat_id AND is_show = 1";
$res = $GLOBALS['db']->getAll($sql);
if ($res[0]['parent_id'] > 0) {
getCatTopId($res[0]['parent_id']); // 遞歸調用,但沒有 return!
} else {
return $res[0]['cat_id']; // 只有最底層有 return
}
} else {
return 1;
}
// 函數隱式返回 null
}
調用棧演示 (cat_id=5, 父鏈: 5→3→1→0): getCatTopId(5) ├─ 查詢 parent_id=3 ├─ 調用 getCatTopId(3) ← 這裡缺少 return │ ├─ 查詢 parent_id=1 │ ├─ 調用 getCatTopId(1) ← 缺少 return │ │ ├─ 查詢 parent_id=0 │ │ ├─ return 1 (頂級 ID) ← 返回給 getCatTopId(1) 的調用處 │ │ └─ getCatTopId(1) 結束,隱式返回 null ← 丟失了 1! │ └─ getCatTopId(3) 結束,隱式返回 null ← 繼續丟失 └─ getCatTopId(5) 結束,返回 null ← 最終結果
<?php
// ✅ 正確版本:每層遞歸都要 return
function getCatTopId($cat_id) {
if (!$cat_id) return 1; // 預設頂級
$sql = "SELECT cat_id, parent_id FROM ecs_category WHERE cat_id = ? AND is_show = 1";
$res = $GLOBALS['db']->getAll($sql, [$cat_id]); // 使用預處理防注入
if (empty($res)) return 1; // 不存在則默認
$row = $res[0];
if ($row['parent_id'] > 0) {
// 關鍵:必須 return 遞歸調用的結果
return getCatTopId($row['parent_id']);
} else {
return $row['cat_id']; // 已是頂級
}
}
return**。return recursiveCall(...); — 這是將子調用的返回值「接力」傳遞給上一層調用者。
分類層級較深時 (超過 100-200 層),PHP 遞歸會報錯 Maximum function nesting level reached。改用循環:
<?php
function getCatTopIdIterative($cat_id) {
if (!$cat_id) return 1;
$currentId = $cat_id;
while (true) {
$sql = "SELECT cat_id, parent_id FROM ecs_category WHERE cat_id = ? AND is_show = 1";
$row = $GLOBALS['db']->getRow($sql, [$currentId]); // 只取一行
if (empty($row) || $row['parent_id'] == 0) {
return $row['cat_id'] ?? 1;
}
$currentId = $row['parent_id'];
}
}
避免遞歸查詢,在數據庫設計層面解決:
ALTER TABLE ecs_category ADD COLUMN path VARCHAR(255) DEFAULT '' COMMENT '路徑: 0/1/3/5/';
-- 查詢頂級分類:取 path 第二個節點
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(path, '/', 2), '/', -1) AS top_id
FROM ecs_category WHERE cat_id = 5;
-- 結果: 1
CREATE TABLE category_ancestors (
ancestor_id INT UNSIGNED NOT NULL,
descendant_id INT UNSIGNED NOT NULL,
depth INT UNSIGNED NOT NULL, -- 0=自身, 1=父級, 2=祖父級...
PRIMARY KEY (ancestor_id, descendant_id),
INDEX idx_descendant (descendant_id)
);
-- 查詢頂級分類 (depth 最大者,排除自身 depth=0)
SELECT ancestor_id FROM category_ancestors
WHERE descendant_id = 5 AND depth > 0
ORDER BY depth DESC LIMIT 1;
includes/lib_goods.php 或對應 lib 文件中找到 get_cat_top_id (或類似名稱),添加缺失的 return。
function getCatTopIdCached($cat_id) {
$cacheKey = "cat_top_id_{$cat_id}";
if (($topId = $redis->get($cacheKey)) !== false) return (int)$topId;
$topId = getCatTopIdIterative($cat_id); // 使用迭代版
$redis->setex($cacheKey, 86400, $topId); // 緩存 1 天
return $topId;
}
return 才能正確傳遞結果。xdebug.max_nesting_level 默認 256),深層級結構優先用迭代。