
本文介绍一种高效、可维护的 php 实现方案:仅一次数据库查询,将歌曲按首字母自动分组,并均分为左右两列(每列最多13个字母),彻底避免为 a–z 重复书写26次 sql 和模板逻辑。
本文介绍一种高效、可维护的 php 实现方案:仅一次数据库查询,将歌曲按首字母自动分组,并均分为左右两列(每列最多13个字母),彻底避免为 a–z 重复书写26次 sql 和模板逻辑。
在构建歌单类应用(如在线吉他谱库、合唱曲目索引页)时,常需将大量歌曲按标题首字母归类,并以双栏布局呈现——左栏 A–M,右栏 N–Z。若采用原始思路:为每个字母单独写一条 WHERE title LIKE 'A%' 查询 + 单独变量 $songTitleA, $songTitleB…不仅代码冗长难维护,还极易因拼写错误或遗漏导致某字母数据丢失,更违背“DRY(Don’t Repeat Yourself)”原则。
以下方案完全重构流程,核心思想是 “一次查询、内存分组、结构化渲染”,兼顾性能、可读性与扩展性:
✅ 步骤一:单次查询获取全部有序数据
<?php require_once 'config.php'; // ✅ 关键优化:只执行一次查询,按标题升序排列 $sql = "SELECT id, title, chord FROM song ORDER BY title ASC"; $stmt = $conn->prepare($sql); $stmt->execute(); $songs = $stmt->fetchAll(PDO::FETCH_ASSOC); ?>
⚠️ 注意:确保 title 字段无前导空格或不可见字符(可加 TRIM() 或在入库时清洗)。若需兼容大小写混合(如 “a Song”),建议统一转大写后再取首字母:strtoupper($song['title'][0])。
✅ 步骤二:内存中按首字母动态分组
<?php
$grouped = [];
foreach ($songs as $song) {
// 安全提取首字母(处理空标题/非字母开头)
$firstChar = !empty($song['title']) ? strtoupper($song['title'][0]) : '#';
// 过滤非字母字符(如数字、符号),归入 '#' 组(可后续单独处理)
$letter = ctype_alpha($firstChar) ? $firstChar : '#';
$grouped[$letter][] = $song;
}
// 按字母顺序排序分组键(确保 A, B, C... 显示顺序正确)
ksort($grouped);
?>✅ 步骤三:均分两栏(按字母数量,非歌曲数)
<?php // 将字母分组数组切分为左右两部分:左栏最多13个字母,右栏余下 $midpoint = (int) ceil(count($grouped) / 2); $columns = array_chunk($grouped, $midpoint, true); $leftColumn = $columns[0] ?? []; $rightColumn = $columns[1] ?? []; ?>
? 说明:array_chunk(..., true) 保留键名(即字母),确保 ['A' => [...], 'B' => [...]] 结构不丢失;ceil() 保证当总字母数为奇数(如25)时,左栏多1个(13 vs 12),符合常见排版习惯。
立即学习“PHP免费学习笔记(深入)”;
✅ 步骤四:封装可复用的渲染函数
<?php
function renderColumn(array $lettersGroup, string $columnClass = 'column'): string {
$html = "<div class=\"{$columnClass}\">\n";
$html .= " <div class=\"table-wrapper\">\n";
$html .= " <table class=\"fl-table\">\n";
foreach ($lettersGroup as $letter => $songs) {
// 表头:字母 + 空白占位列
$html .= " <tr>\n";
$html .= " <th style=\"color:black; width:80%\">{$letter}</th>\n";
$html .= " <th style=\"color:white;\"></th>\n";
$html .= " </tr>\n";
// 歌曲行:标题 + 和弦(带详情链接)
foreach ($songs as $song) {
$html .= " <tr>\n";
$html .= " <td><a style=\"margin-left:10px\" href=\"details.php?id={$song['id']}\">{$song['title']}</a></td>\n";
$html .= " <td><span style=\"margin-left:10px\">{$song['chord']}</span></td>\n";
$html .= " </tr>\n";
}
}
$html .= " </table>\n";
$html .= " </div>\n";
$html .= "</div>\n";
return $html;
}
?>✅ 步骤五:最终输出(简洁、无重复)
<div class="song-columns"> <?= renderColumn($leftColumn, 'column left') ?> <?= renderColumn($rightColumn, 'column right') ?> </div>
? 进阶提示与注意事项
- 性能友好:全程无 N+1 查询,仅 1 次 DB 请求 + PHP 内存操作,即使千级歌曲也毫秒级响应。
- 健壮性增强:对空标题、数字开头(如 “1999”)、特殊符号(如 “& Band”)做了归类处理,避免键名错误。
- 样式解耦:HTML 结构与业务逻辑分离,CSS 可通过 .left / .right 独立控制宽度、间距、响应式断点。
- 扩展灵活:如需改为三栏、按歌曲数均分、或添加“# 其他”汇总栏,只需调整 array_chunk 参数或预处理 $grouped 数组。
-
安全加固:实际生产环境务必对 $song['title'] 和 $song['chord'] 做 htmlspecialchars() 转义,防止 XSS:
htmlspecialchars($song['title'], ENT_QUOTES, 'UTF-8')
此方案将原本 26×(SQL + 变量 + 循环)的硬编码,压缩为 4 个清晰逻辑块,代码量减少 80% 以上,且具备自解释性与长期可维护性——这才是现代 PHP Web 开发应有的工程实践。



















