基于id_parent的多级菜单:删除父项时批量删除所有子项
这里有两种靠谱的方式来实现批量删除多级子菜单的需求,递归和循环遍历,我分别给你拆解一下,顺便提一下必须注意的安全问题:
方案一:递归获取所有子项(代码简洁直观)
递归的思路很直接:先把目标ID(Item1的id)加入待删除列表,然后递归查找每个子项的所有子孙,把它们都合并到列表里。
首先,你可以在模型里新增一个方法来收集所有层级的子孙ID:
public function getAllDescendants($parentId) { // 先获取当前父ID的直接子项 $children = $this->searchChildren($parentId); // 初始化待删除列表,先把当前父ID加进去 $descendants = [$parentId]; foreach ($children as $child) { // 递归获取每个子项的所有子孙,合并到列表中 $descendants = array_merge($descendants, $this->getAllDescendants($child['id'])); } return $descendants; }
这个方法会返回包含目标ID和所有层级子项ID的数组,之后你就可以用这个数组执行批量删除了。
方案二:循环(广度优先)获取所有子项(避免递归溢出)
如果你的菜单层级非常深(比如几十层),递归可能会导致PHP栈溢出报错。这时候用广度优先遍历的循环方式更稳定:
public function getAllDescendants($parentId) { $descendants = [$parentId]; // 用队列存储待处理的ID,先把目标ID放进去 $queue = [$parentId]; while (!empty($queue)) { // 取出队列的第一个ID $currentId = array_shift($queue); // 获取这个ID的直接子项 $children = $this->searchChildren($currentId); foreach ($children as $child) { $childId = $child['id']; // 把子项ID加入待删除列表 $descendants[] = $childId; // 把子项ID加入队列,继续处理它的子项 $queue[] = $childId; } } return $descendants; }
这种方式用队列来逐层处理所有子项,不会有递归栈的限制,适合复杂的深层级菜单。
关键优化:修复SQL注入风险
你当前的searchChildren方法直接把$id拼进SQL语句里,这是非常危险的SQL注入漏洞!必须改成参数化查询,我给你两种常见数据库扩展的写法:
如果你用PDO:
public function searchChildren($id) { $sql = "SELECT id FROM menu WHERE id_parent = ?"; $stmt = $this->db->prepare($sql); // 绑定参数并执行 $stmt->execute([$id]); // 返回所有子项的ID数组 return $stmt->fetchAll(PDO::FETCH_ASSOC); }
如果你用mysqli:
public function searchChildren($id) { $sql = "SELECT id FROM menu WHERE id_parent = ?"; $stmt = $this->db->prepare($sql); // 假设id是整数类型,用"i"绑定;如果是字符串用"s" $stmt->bind_param("i", $id); $stmt->execute(); $result = $stmt->get_result(); $children = []; while ($row = $result->fetch_assoc()) { $children[] = $row; } return $children; }
执行批量删除的示例
拿到所有待删除的ID数组后,就可以执行批量删除了,同样要用参数化查询避免注入:
// 假设Item1的id是$targetId $targetId = 1; // 获取所有要删除的ID $allDeleteIds = $this->model->getAllDescendants($targetId); // 生成IN子句的占位符,比如ids有3个的话就是"?, ?, ?" $placeholders = rtrim(str_repeat('?, ', count($allDeleteIds)), ', '); $sql = "DELETE FROM menu WHERE id IN ($placeholders)"; // PDO写法 $stmt = $this->db->prepare($sql); $stmt->execute($allDeleteIds); // mysqli写法 $stmt = $this->db->prepare($sql); // 动态绑定参数,这里需要处理类型,假设都是整数 $types = str_repeat('i', count($allDeleteIds)); $stmt->bind_param($types, ...$allDeleteIds); $stmt->execute();
这样就能一次性删除Item1和它所有层级的子项啦!
内容的提问来源于stack exchange,提问作者KvinT
相关产品推荐
相关产品推荐

