PAYATHON 2026

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>。

备注:内容仅供参考。