
本文介绍如何将来自单一 mysql 表的扁平化对阵数据(含轮次、选手及对手信息),通过 php 动态重组为符合淘汰赛括号(tournament bracket)格式的多维嵌套数组,并支持任意轮次扩展与 json 序列化输出。
本文介绍如何将来自单一 mysql 表的扁平化对阵数据(含轮次、选手及对手信息),通过 php 动态重组为符合淘汰赛括号(tournament bracket)格式的多维嵌套数组,并支持任意轮次扩展与 json 序列化输出。
在实现在线锦标赛系统时,前端(如 JavaScript 括号可视化组件)往往要求数据严格遵循特定嵌套结构:按轮次分组 → 每轮包含若干对战组合行 → 每行含两名选手对象。而数据库通常以扁平化方式存储(单表、每行一条对阵记录),例如:
| player_id | player_name | participant_id | participant_name | round |
|---|---|---|---|---|
| 1 | Mark Alan | 3 | Steve | 1 |
| 2 | John Doe | 4 | Sean Henshaw | 1 |
| 1 | Mark Alan | 2 | John Doe | 2 |
直接遍历结果集并简单 push 到一维数组,会导致所有对阵混在一起,无法区分轮次——这正是原始代码中 $bracket[] = [...] 的局限性。
✅ 正确解法是以 round 字段为键进行分组聚合,利用 PHP 的空合并赋值运算符 ??=(PHP 7.0+)自动初始化每轮对应的子数组,并逐行追加“双人对阵单元”:
// 假设 $stmt 是已执行的 mysqli 查询语句(SELECT * FROM matches ORDER BY round)
$data = [];
while ($row = mysqli_fetch_array($stmt, MYSQLI_ASSOC)) {
// 确保该轮次数组已存在,否则初始化为空数组
$data[$row['round']] ??= [];
// 构建当前对阵行:含两名选手对象(注意字段映射:name/id)
$matchRow = [
[
'name' => $row['player_name'],
'id' => (int)$row['player_id'] // 强制转为整型,避免字符串ID
],
[
'name' => $row['participant_name'],
'id' => (int)$row['participant_id']
]
];
// 将该对阵行追加到对应轮次下
$data[$row['round']][] = $matchRow;
}
// 最终生成标准 bracket JSON(无需手动调整结构)
$bracketJson = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo $bracketJson;
? 关键要点说明:
- ??= 运算符等价于 isset($data[$row['round']]) ?: $data[$row['round']] = [];,简洁安全地完成懒初始化;
- 每个 $data[$round] 是一个二维数组:外层数组索引为轮次,内层数组每一项即一个 [选手A, 选手B] 对战组合;
- 字段映射需严格匹配目标结构:name 对应姓名字段,id 对应唯一标识(建议强制类型转换,避免 "1" 与 1 混淆);
- 数据库查询务必 ORDER BY round,确保同轮次记录连续读取(虽非必须,但利于调试与逻辑清晰);
- 若某轮无数据(如轮空),对应 $data[$round] 将为空数组,JSON 中体现为 [],前端可据此渲染占位符。
? 进阶提示:
如需支持“轮次编号从 0 开始”或“跳过中间轮次”,可在生成后用 array_values() 重排键,或使用 ksort($data) 确保轮次升序;若需校验每轮对阵数是否为 2 的幂次,可在循环后添加 count($data[$r]) === pow(2, $totalRounds - $r) 类似逻辑。
此方案完全解耦轮次数目,无需预定义轮次上限,天然适配单败/双败淘汰赛动态规模,是构建健壮赛事后台的核心数据组织模式。











