PHP Foreach 中正确生成不重复的 Optgroup
支付老李
结论
不要在遍历支付记录时直接输出 <optgroup>。先按 region 整理数据,再为每个地区的 mobile payments 输出一个 <optgroup>。
判断移动支付需要同时满足两个条件:
(int) $region['type'] === 20 && (int) $region['mobile'] !== 0
这样,同一地区即使有多种 mobile payment,也只会生成一个分组。
先按 region 和支付类别整理数据
<?php
$groupedRegions = [];
foreach ($regions as $region) {
$regionId = (int) $region['region'];
$typeId = (int) $region['type'];
$mobileId = (int) $region['mobile'];
if (!isset($groupedRegions[$regionId])) {
$groupedRegions[$regionId] = [
'regular' => [],
'mobile' => [],
];
}
if ($typeId === 20 && $mobileId !== 0) {
$groupedRegions[$regionId]['mobile'][] = [
'value' => $typeId . '_' . $mobileId,
'label' => $db->getMobileById($mobileId),
];
} else {
$groupedRegions[$regionId]['regular'][] = [
'value' => (string) $typeId,
'label' => $db->getTypeById($typeId),
];
}
}
整理后,每个 region 只有一个 mobile 数组。接下来统一生成 HTML 即可,不必再用 $i、$im 等计数器判断 <optgroup> 的起止位置。
生成 dropdown
<select name="payment_method">
<?php foreach ($groupedRegions as $regionId => $payments): ?>
<?php foreach ($payments['regular'] as $payment): ?>
<option value="<?= htmlspecialchars($payment['value'], ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($payment['label'], ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
<?php if ($payments['mobile'] !== []): ?>
<optgroup label="Mobile payments">
<?php foreach ($payments['mobile'] as $payment): ?>
<option value="<?= htmlspecialchars($payment['value'], ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($payment['label'], ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</optgroup>
<?php endif; ?>
<?php endforeach; ?>
</select>
示例数据会生成类似下面的结构:
<select name="payment_method">
<option value="18">...</option>
<option value="19">...</option>
<optgroup label="Mobile payments">
<option value="20_1">...</option>
<option value="20_2">...</option>
<option value="20_3">...</option>
</optgroup>
</select>
显示 region 名称
如果 dropdown 包含多个 region,可以在分组标签中加入地区名称,避免出现多个无法区分的 Mobile payments:
<?php
$regionName = $db->getRegionById($regionId);
$groupLabel = $regionName . ' - Mobile payments';
?>
<optgroup label="<?= htmlspecialchars($groupLabel, ENT_QUOTES, 'UTF-8') ?>">
最终可能得到:
<optgroup label="Europe - Mobile payments">
...
</optgroup>
<optgroup label="Asia - Mobile payments">
...
</optgroup>
注意事项
HTML 不允许嵌套 <optgroup>,所以下面的结构无效:
<optgroup label="Europe">
<optgroup label="Mobile payments">
...
</optgroup>
</optgroup>
如果 region 也必须作为分组,需要把两个层级合并到同一个 label 中:
<optgroup label="Europe - Regular payments">
...
</optgroup>
<optgroup label="Europe - Mobile payments">
...
</optgroup>
empty($regions) 可以同时处理空数组和 null,因此原来的判断可以简化为:
if (!empty($regions)) {
// 整理并输出 dropdown
}
先整理数据,再生成 HTML,就不必依赖 MySQL 的返回顺序。即使记录相互交错,也不会重复生成或错误关闭 <optgroup>。
备注:内容仅供参考。
