
本文介绍如何将来自单张数据库表的锦标赛选手数据,按轮次(round)动态分组并构建符合前端对阵图需求的嵌套数组结构,最终输出标准 json 格式。
本文介绍如何将来自单张数据库表的锦标赛选手数据,按轮次(round)动态分组并构建符合前端对阵图需求的嵌套数组结构,最终输出标准 json 格式。
在开发单淘汰制锦标赛(Tournament Bracket)系统时,前端通常要求数据严格遵循特定的多维数组格式:外层数组按轮次(Round 1、Round 2…)划分;每轮为一个子数组;每轮内每个对阵(match)是一个包含两名选手对象的二维子数组,例如:
[
[ // Round 1
[{"name": "Mark Alan", "id": 1}, {"name": "Steve", "id": 3}],
[{"name": "John Doe", "id": 2}, {"name": "Sean Henshaw", "id": 4}]
],
[ // Round 2
[{"name": "Mark Alan", "id": 1}, {"name": "John Doe", "id": 2}]
]
]而你的数据源是一张扁平化的关系表(含 player_id, player_name, participant_id, participant_name, round 字段),这意味着同一轮次的多场对阵可能交错出现在查询结果中,且每行仅描述一场对阵中的双方之一——这正是导致简单 foreach 循环无法自动分轮的核心难点。
✅ 正确解法是:以 round 为键进行动态分组,并累积每轮内的完整对阵对(pair)。关键在于利用 PHP 的“空合并赋值”(??=)语法初始化轮次数组,并确保每行数据都精准归入对应轮次的对阵单元中。
以下是完整、可直接运行的处理逻辑(假设 $stmt 是已执行的 mysqli_stmt):
$bracket = [];
while ($row = mysqli_fetch_array($stmt, MYSQLI_ASSOC)) {
// 步骤1:确保当前 round 键存在,初始化为空数组
$bracket[$row['round']] ??= [];
// 步骤2:构造当前对阵的双方选手对象(注意字段映射)
$matchPair = [
[
'name' => $row['player_name'],
'id' => (int)$row['player_id'] // 强制转为整型,避免 JSON 中出现字符串 ID
],
[
'name' => $row['participant_name'],
'id' => (int)$row['participant_id']
]
];
// 步骤3:将该对阵对追加到对应轮次数组中
$bracket[$row['round']][] = $matchPair;
}
// 最终输出符合前端要求的 JSON(注意:轮次索引从 1 开始,PHP 数组天然支持)
echo json_encode(array_values($bracket), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);? 重要说明与注意事项:
- 轮次顺序保障:array_values($bracket) 确保输出数组索引从 0 开始连续(即 Round 1 → index 0, Round 2 → index 1),严格匹配前端预期结构。若需保留原始 round 数字作为键名,请跳过此步,但需确认前端是否兼容关联索引。
- 数据完整性校验:上述代码假设每行数据已代表一场完整对阵(即 player 和 participant 同属一轮)。若实际表结构中一对对阵被拆分为两行(如分别记录胜者/败者),则需先通过 SQL JOIN 或 PHP 端配对逻辑预处理,再执行分组。
- ID 类型安全:显式 (int) 转换避免 MySQL 返回的字符串型 ID 导致 JSON 中出现 "id": "1"(应为 "id": 1),这对强类型前端解析至关重要。
- 性能优化建议:对于大型锦标赛,可在 SQL 查询中添加 ORDER BY round,使数据流更有序,提升 PHP 分组逻辑的局部性与可读性。
通过该方案,你无需预先知道最大轮次数,也无需嵌套循环或手动判断轮次切换——PHP 的动态数组特性与简洁语法即可优雅实现结构化数据组装。最终输出的 JSON 可直接交由前端 bracket 渲染库(如 react-bracket 或自定义 Vue 组件)消费,真正实现前后端数据契约的无缝对接。

















