
本文详解 php 在 while 循环内使用 foreach 时因数组累积导致的重复渲染问题,通过重置数组、重构逻辑和优化 sql 查询,彻底解决嵌套复选框列表中层级数据重复显示的常见 bug。
本文详解 php 在 while 循环内使用 foreach 时因数组累积导致的重复渲染问题,通过重置数组、重构逻辑和优化 sql 查询,彻底解决嵌套复选框列表中层级数据重复显示的常见 bug。
在构建多级嵌套复选框(如“集团下属公司 → 模块 → 权限钩子”)时,开发者常误将数组追加逻辑置于外层 while 循环内部,却未及时清空中间数组,从而引发严重的重复渲染——这正是您代码中 $namearray[] = $name; 和 $modearray[] = $modname; 所致的核心问题。
? 问题根源分析
您的原始代码中:
while($row = mysqli_fetch_array($result_concern)){
$sisid = $row['Id'];
$name = $row['Name'];
$namearray[] = $name; // ❌ 错误:每次循环都向同一数组追加,导致后续 foreach 遍历越来越长
foreach($namearray as $data){ ... }
}第一次循环:$namearray = ['A'] → 输出 1 项
第二次循环:$namearray = ['A', 'B'] → 输出 2 项(含重复的 A)
第三次循环:$namearray = ['A', 'B', 'C'] → 输出 3 项(A、B 各重复一次)
以此类推 —— 层级越深,重复越严重。
同理,$modearray[] 在内层 while($rowmodule...) 中也持续累积,加剧了嵌套重复。
✅ 正确解决方案
✅ 方案一:重置数组(快速修复)
在每次进入新层级前显式清空数组:
立即学习“PHP免费学习笔记(深入)”;
while($row = mysqli_fetch_array($result_concern)){
$sisid = $row['Id'];
$name = $row['Name'];
// ✅ 重置:确保每次只处理当前 concern
$namearray = [$name]; // 或 $namearray = []; $namearray[] = $name;
foreach($namearray as $data){
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" name="sister_concern" class="chkMainConcern" id="chkMainConcern'.$sisid.'" value="'.$sisid.'"> '.$name.'</li>';
// ✅ 获取模块:推荐使用预查询 + 分组,而非循环内查库(见下方优化)
$sql_module = "SELECT * FROM Module WHERE Status='Active'";
$result_module = mysqli_query($link, $sql_module);
echo '<ul class="modulename'.$sisid.'" style="display:none;">';
while($rowmodule = mysqli_fetch_array($result_module)){
$modid = $rowmodule['Id'];
$modname = $rowmodule['Name'];
// ✅ 清空模块名数组(若仍需 foreach)
$modearray = [$modname];
foreach($modearray as $val){
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" value="'.$modid.'"> '.$modname.'</li>';
// ✅ 权限钩子查询(同样建议预加载,此处暂保留)
$sql_modhooks = "SELECT * FROM Module_hooks WHERE Status='Active' AND ModuleID='$modid'";
$result_modhooks = mysqli_query($link, $sql_modhooks);
echo '<ul class="modulehooks'.$modid.'" style="display:none;">';
while($row_modhooks = mysqli_fetch_array($result_modhooks)){
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" value="'.$modid.'"> '.$row_modhooks['display_txt'].'</li>';
}
echo '</ul>';
}
}
echo '</ul>';
}
}✅ 方案二:根本性优化(推荐 ✨)
避免循环内执行 SQL,改用单次查询 + 逻辑分组,大幅提升性能与可维护性:
// 1. 一次性获取所有活跃集团下属公司
$sql_concern = "SELECT Id, Name FROM sister_concern WHERE Base_user=? AND Status='Active'";
$stmt = mysqli_prepare($link, $sql_concern);
mysqli_stmt_bind_param($stmt, 'i', $User_id);
mysqli_stmt_execute($stmt);
$all_concerns = mysqli_fetch_all(mysqli_stmt_get_result($stmt), MYSQLI_ASSOC);
// 2. 预加载所有活跃模块
$sql_module = "SELECT Id, Name FROM Module WHERE Status='Active'";
$modules = mysqli_fetch_all(mysqli_query($link, $sql_module), MYSQLI_ASSOC);
// 3. 预加载所有模块权限钩子(按 ModuleID 分组)
$sql_hooks = "SELECT ModuleID, Id, display_txt FROM Module_hooks WHERE Status='Active'";
$all_hooks = mysqli_fetch_all(mysqli_query($link, $sql_hooks), MYSQLI_ASSOC);
$hooks_by_module = [];
foreach($all_hooks as $hook) {
$hooks_by_module[$hook['ModuleID']][] = $hook;
}
// 4. 渲染结构(无重复、无嵌套查询)
echo '<ul>';
foreach($all_concerns as $concern) {
$sisid = $concern['Id'];
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" name="sister_concern" class="chkMainConcern" id="chkMainConcern'.$sisid.'" value="'.$sisid.'"> '.$concern['Name'].'</li>';
echo '<ul class="modulename'.$sisid.'" style="display:none;">';
foreach($modules as $module) {
$modid = $module['Id'];
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" value="'.$modid.'"> '.$module['Name'].'</li>';
// 渲染该模块下的权限钩子
if (isset($hooks_by_module[$modid]) && !empty($hooks_by_module[$modid])) {
echo '<ul class="modulehooks'.$modid.'" style="display:none;">';
foreach($hooks_by_module[$modid] as $hook) {
echo '<li style="list-style-type:none;">';
echo '<input type="checkbox" value="'.$modid.'"> '.$hook['display_txt'].'</li>';
}
echo '</ul>';
}
}
echo '</ul>';
}
echo '</ul>';⚠️ 关键注意事项
-
永远不要在循环内执行相同 SQL:
SELECT * FROM Module被执行了 N 次(N = 下属公司数),极易拖垮数据库; -
禁用
mysql_*函数*:您使用的是已废弃的 `mysqli_` 过程式风格,强烈建议升级为 PDO 或 MySQLi 面向对象 + 预处理语句**,防止 SQL 注入; -
HTML 语义化:嵌套
<ul><li></ul>是正确的,但请确保每层value具有业务唯一性(例如权限钩子应绑定HookID而非ModuleID); -
前端联动:配合 JavaScript 控制
.modulenameX/.modulehooksY的显隐逻辑,实现真正的可折叠树形结构。
通过重置数组或(更优地)采用预加载+分组策略,您不仅能彻底消除重复,还能获得更健壮、可扩展、易调试的权限管理界面。



















