HTML 表單中 `` 實現多選功能,關鍵在於 **name 屬性必須以 `[]` 結尾**,使 PHP 自動將其解析為陣列。本文詳解從前端到後端的完整處理流程。
<form method="post" action="process.php">
<fieldset>
<legend>選擇您感興趣的主題</legend>
<label><input type="checkbox" name="topics[]" value="php"> PHP</label>
<label><input type="checkbox" name="topics[]" value="javascript"> JavaScript</label>
<label><input type="checkbox" name="topics[]" value="python"> Python</label>
<label><input type="checkbox" name="topics[]" value="go"> Go</label>
<label><input type="checkbox" name="topics[]" value="rust"> Rust</label>
</fieldset>
<button type="submit">提交</button>
</form>
關鍵點:
<?php
// process.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. 獲取陣列 (未勾選時 $_POST['topics'] 不存在)
$selectedTopics = $_POST['topics'] ?? [];
// 2. 類型安全:確保是陣列
if (!is_array($selectedTopics)) {
$selectedTopics = [$selectedTopics]; // 單值轉陣列 (防範)
}
// 3. 清理與驗證
$allowedTopics = ['php', 'javascript', 'python', 'go', 'rust'];
$cleanTopics = array_intersect($selectedTopics, $allowedTopics);
// 4. 輸出結果
echo "您選擇了 " . count($cleanTopics) " 個主題:\n";
echo implode(', ', $cleanTopics);
// 5. 存儲為字串 (如需存入資料庫)
$topicsString = implode('|', $cleanTopics); // 用 | 分隔避免逗號衝突
}
?>
<?php
$topics = $_POST['topics'] ?? [];
foreach ($topics as $index => $topic) {
echo "選項 $index: $topic<br>";
}
// 帶鍵名遍歷 (如果前端用 topics[php]=php 這種寫法)
foreach ($topics as $key => $value) {
echo "鍵: $key, 值: $value<br>";
}
?>
<?php
$topics = $_POST['topics'] ?? [];
$topicString = implode(', ', $topics);
echo "選中: $topicString"; // "PHP, JavaScript, Go"
?>
<?php
$topics = $_POST['topics'] ?? [];
// 清理:去除首尾空白、轉義 HTML
$cleanTopics = array_map(function($topic) {
return htmlspecialchars(trim($topic));
}, $topics);
// 驗證:只保留允許的值
$allowed = ['php', 'javascript', 'python', 'go', 'rust'];
$validTopics = array_intersect($cleanTopics, $allowed);
print_r($validTopics);
?>
<?php
$topics = $_POST['topics'] ?? [];
// 過濾空字串、null、false
$filtered = array_filter($topics, function($v) {
return $v !== '' && $v !== null && $v !== false;
});
// 重新索引 (可選)
$filtered = array_values($filtered);
?>
<?php
// 假設從資料庫讀取的用戶已選主題
$userTopics = ['php', 'go']; // 或從資料庫獲取
$allTopics = [
'php' => 'PHP',
'javascript' => 'JavaScript',
'python' => 'Python',
'go' => 'Go',
'rust' => 'Rust'
];
?>
<form method="post">
<?php foreach ($allTopics as $value => $label): ?>
<label>
<input type="checkbox"
name="topics[]"
value="<?= htmlspecialchars($value) ?>"
<?= in_array($value, $userTopics) ? 'checked' : '' ?>>
<?= htmlspecialchars($label) ?>
</label>
<?php endforeach; ?>
</form>
<script>
function toggleAll(source) {
const checkboxes = document.querySelectorAll('input[name="topics[]"]');
checkboxes.forEach(cb => cb.checked = source.checked);
}
function invertSelection() {
const checkboxes = document.querySelectorAll('input[name="topics[]"]');
checkboxes.forEach(cb => cb.checked = !cb.checked);
}
</script>
<button type="button" onclick="toggleAll(this)">全選</button>
<button type="button" onclick="invertSelection()">反選</button>
<form method="post">
<fieldset>
<legend>程式語言</legend>
<label><input type="checkbox" name="languages[]" value="php"> PHP</label>
<label><input type="checkbox" name="languages[]" value="js"> JavaScript</label>
</fieldset>
<fieldset>
<legend>框架</legend>
<label><input type="checkbox" name="frameworks[]" value="laravel"> Laravel</label>
<label><input type="checkbox" name="frameworks[]" value="vue"> Vue.js</label>
</fieldset>
</form>
<?php
// 後端分別處理
$languages = $_POST['languages'] ?? [];
$frameworks = $_POST['frameworks'] ?? [];
?>
<?php
function sanitizeCheckboxArray(array $input, array $allowedValues): array {
// 1. 確保輸入是陣列
if (!is_array($input)) return [];
// 2. 過濾非字串值
$strings = array_filter($input, 'is_string');
// 3. 長度限制 (防止超大值撐爆記憶體)
$lengthLimited = array_filter($strings, fn($v) => strlen($v) <= 50);
// 4. 白名單驗證
return array_intersect($lengthLimited, $allowedValues);
}
// 使用
$allowedTopics = ['php', 'javascript', 'python', 'go', 'rust'];
$safeTopics = sanitizeCheckboxArray($_POST['topics'] ?? [], $allowedTopics);
?>
<?php
// Controller
public function store(Request $request) {
// 自動驗證:必須是陣列,每個值在允許列表中
$validated = $request->validate([
'topics' => 'required|array|min:1',
'topics.*' => 'in:php,javascript,python,go,rust'
]);
$topics = $validated['topics'];
// 或使用 $request->input('topics', [])
}
// Blade 模板
@foreach($allTopics as $value => $label)
<label>
<input type="checkbox" name="topics[]" value="{{ $value }}"
{{ in_array($value, old('topics', $userTopics)) ? 'checked' : '' }}>
{{ $label }}
</label>
@endforeach
<?php
// Form Type
$builder->add('topics', ChoiceType::class, [
'choices' => [
'PHP' => 'php',
'JavaScript' => 'javascript',
// ...
],
'multiple' => true,
'expanded' => true, // 渲染為 checkbox
'constraints' => [
new Count(['min' => 1, 'minMessage' => '至少選擇一項'])
]
]);
<?php
// 控制器
$topics = $this->request->param('topics/a', []); // /a 強制返回陣列
// 或使用驗證器
validate([
'topics' => 'require|array',
'topics.*' => 'in:php,javascript,python,go,rust'
]);
| 錯誤現象 | 原因 | 解決方案 |
|---|---|---|
| 只能獲取到最後一個值 | name 屬性未加 `[]` | 改為 `name="topics[]"` |
| $_POST['topics'] 是字串而非陣列 | 只勾選了一個,但 PHP 仍返回陣列 | 正常現象,單選也是單元素陣列 |
| 未勾選時出現 Undefined array key | 直接訪問 $_POST['topics'] | 用 `$_POST['topics'] ?? []` |
| 值包含惡意腳本 | 未過濾輸出 | 輸出時用 htmlspecialchars() |
| 提交大量垃圾值導致記憶體耗盡 | 無長度/數量限制 | 前後端雙重限制數量與長度 |
<?php
function processCheckboxField(string $fieldName, array $allowedValues, int $maxItems = 20): array {
// 1. 獲取原始輸入
$raw = $_POST[$fieldName] ?? [];
// 2. 確保是陣列
if (!is_array($raw)) {
$raw = $raw !== '' ? [$raw] : [];
}
// 3. 數量限制
if (count($raw) > $maxItems) {
throw new InvalidArgumentException("選項過多,最多允許 $maxItems 個");
}
// 4. 清理每個值
$cleaned = array_map(function($v) {
return trim(strip_tags($v)); // 移除標籤、首尾空白
}, $raw);
// 5. 過濾空值
$nonEmpty = array_filter($cleaned, fn($v) => $v !== '');
// 6. 白名單驗證
$valid = array_intersect($nonEmpty, $allowedValues);
// 7. 重新索引並返回
return array_values($valid);
}
// 使用
try {
$topics = processCheckboxField('topics', ['php', 'js', 'py', 'go', 'rs']);
// 後續業務邏輯...
} catch (InvalidArgumentException $e) {
// 錯誤處理
echo "錯誤: " . $e->getMessage();
}
?>
掌握 Checkbox 多選值的正確處理,從前端命名規範到後端安全清洗,是表單開發的基本功。建立統一的處理函數/驗證器,能大幅減少 Bug 並提升代碼可維護性。