表單驗證是 Web 應用資料品質的第一道防線。完整的驗證流程包含:伺服器端驗證(必要)、用戶端驗證(體驗優化)、資料保持(避免重輸入)、錯誤顯示(引導修正)、安全防護(XSS/CSRF)。本文實作一個完整範例。
<?php
// 初始化變數
$name = $email = $website = $comment = $gender = '';
$nameErr = $emailErr = $websiteErr = $genderErr = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. 統一輸入過濾函數
function cleanInput($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
// 2. 姓名驗證
if (empty($_POST['name'])) {
$nameErr = '姓名為必填';
} else {
$name = cleanInput($_POST['name']);
if (!preg_match('/^[a-zA-Z\u4e00-\u9fa5\s]+$/u', $name)) {
$nameErr = '姓名僅允許中英文字母與空格';
}
}
// 3. Email 驗證
if (empty($_POST['email'])) {
$emailErr = 'Email 為必填';
} else {
$email = cleanInput($_POST['email']);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$emailErr = 'Email 格式不正確';
}
}
// 4. 網址驗證(選填)
if (!empty($_POST['website'])) {
$website = cleanInput($_POST['website']);
if (!filter_var($website, FILTER_VALIDATE_URL)) {
$websiteErr = '網址格式不正確(需含 https:// 或 https://)';
}
}
// 5. 備註(選填)
if (!empty($_POST['comment'])) {
$comment = cleanInput($_POST['comment']);
}
// 6. 性別驗證
if (empty($_POST['gender'])) {
$genderErr = '請選擇性別';
} else {
$gender = cleanInput($_POST['gender']);
if (!in_array($gender, ['male', 'female', 'other'])) {
$genderErr = '無效的性別選項';
}
}
// 7. 無錯誤時處理業務邏輯
if (empty($nameErr) && empty($emailErr) && empty($websiteErr) && empty($genderErr)) {
// 這裡處理:存資料庫、發送郵件、API 呼叫等
$success = true;
// 可選:PRG 模式重導向避免重複提交
// header('Location: thank_you.php');
// exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>會員註冊</title>
<style>
.error { color: #dc3545; font-size: 0.875rem; margin-top: 0.25rem; display: block; }
.form-group { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.5rem; font-weight: 500; }
input, textarea, select { width: 100%; max-width: 400px; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 0.25rem; }
input.error-input, textarea.error-input, select.error-input { border-color: #dc3545; }
.btn { padding: 0.5rem 1.5rem; background: #0d6efd; color: white; border: none; border-radius: 0.25rem; cursor: pointer; }
.success-msg { color: #198754; padding: 1rem; background: #d1e7dd; border: 1px solid #badbcc; border-radius: 0.25rem; margin-bottom: 1rem; }
</style>
</head>
<body>
<h1>會員註冊</h1>
<?php if (!empty($success)): ?>
<div class="success-msg">註冊成功!感謝您的加入。</div>
<?php endif; ?>
<form method="post" action="">
<!-- CSRF Token -->
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?? ''; ?>">
<div class="form-group">
<label>姓名 <span style="color:red">*</span></label>
<input type="text" name="name" value="<?php echo $name; ?>"
class="<?php echo $nameErr ? 'error-input' : ''; ?>">
<?php if ($nameErr): ?><span class="error"><?php echo $nameErr; ?></span><?php endif; ?>
</div>
<div class="form-group">
<label>Email <span style="color:red">*</span></label>
<input type="email" name="email" value="<?php echo $email; ?>"
class="<?php echo $emailErr ? 'error-input' : ''; ?>">
<?php if ($emailErr): ?><span class="error"><?php echo $emailErr; ?></span><?php endif; ?>
</div>
<div class="form-group">
<label>個人網址</label>
<input type="url" name="website" value="<?php echo $website; ?>"
class="<?php echo $websiteErr ? 'error-input' : ''; ?>" placeholder="https://example.com">
<?php if ($websiteErr): ?><span class="error"><?php echo $websiteErr; ?></span><?php endif; ?>
</div>
<div class="form-group">
<label>備註</label>
<textarea name="comment" rows="4" class="<?php echo $commentErr ? 'error-input' : ''; ?>"><?php echo $comment; ?></textarea>
</div>
<div class="form-group">
<label>性別 <span style="color:red">*</span></label>
<div>
<label><input type="radio" name="gender" value="male" <?php echo $gender === 'male' ? 'checked' : ''; ?>> 男生</label>
<label><input type="radio" name="gender" value="female" <?php echo $gender === 'female' ? 'checked' : ''; ?>> 女生</label>
<label><input type="radio" name="gender" value="other" <?php echo $gender === 'other' ? 'checked' : ''; ?>> 其他</label>
</div>
<?php if ($genderErr): ?><span class="error"><?php echo $genderErr; ?></span><?php endif; ?>
</div>
<button type="submit" class="btn">送出註冊</button>
</form>
</body>
</html>
value="<?php echo $name; ?>" 將驗證後/原始輸入回填,避免用戶重複輸入。checked 屬性控制單選/核取方塊狀態。
每個欄位下方顯示對應錯誤,error-input 樣式標紅邊框,視覺引導明確。
function cleanInput($data) {
$data = trim($data); // 移除首尾空白
$data = stripslashes($data); // 移除反斜槓(魔術引號遺留)
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8'); // 編碼 HTML 實體
return $data;
}
/^[a-zA-Z\u4e00-\u9fa5\s]+$/u(中英文+空格,u 修飾符支援 Unicode)filter_var($email, FILTER_VALIDATE_EMAIL)(內建 RFC 5322 相容)filter_var($url, FILTER_VALIDATE_URL)(需含 scheme)表單隱藏 csrf_token,伺服器驗證 hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])。
成功處理後 header('Location: ...') 重導向,防止重新整理重複提交。
<?php
class FormValidator {
private $data = [];
private $errors = [];
private $rules = [];
public function __construct(array $data) {
$this->data = $data;
}
public function rule(string $field, callable $validator, string $message): self {
$this->rules[$field][] = [$validator, $message];
return $this;
}
public function required(string $field, string $message = '此欄位為必填'): self {
return $this->rule($field, fn($v) => !empty($v), $message);
}
public function email(string $field, string $message = 'Email 格式錯誤'): self {
return $this->rule($field, fn($v) => filter_var($v, FILTER_VALIDATE_EMAIL), $message);
}
public function regex(string $field, string $pattern, string $message): self {
return $this->rule($field, fn($v) => (bool)preg_match($pattern, $v), $message);
}
public function inArray(string $field, array $allowed, string $message = '無效選項'): self {
return $this->rule($field, fn($v) => in_array($v, $allowed, true), $message);
}
public function validate(): bool {
foreach ($this->rules as $field => $rules) {
$value = $this->data[$field] ?? '';
foreach ($rules as [$validator, $message]) {
if (!$validator($value)) {
$this->errors[$field][] = $message;
}
}
}
return empty($this->errors);
}
public function getErrors(): array { return $this->errors; }
public function getError(string $field): string { return $this->errors[$field][0] ?? ''; }
public function hasError(string $field): bool { return isset($this->errors[$field]); }
}
// 使用範例
$validator = new FormValidator($_POST);
$validator->required('name', '姓名必填')
->regex('name', '/^[a-zA-Z\s]+$/', '僅允許字母空格')
->required('email', 'Email必填')
->email('email')
->required('gender', '請選擇性別')
->inArray('gender', ['male','female','other']);
if ($validator->validate()) {
// 成功
} else {
$nameErr = $validator->getError('name');
// ...
}
?>
伺服器驗證不可省略,前端驗證提升體驗:
<input type="text" name="name" required pattern="[A-Za-z\s]+" title="僅允許字母空格">
<input type="email" name="email" required>
<input type="url" name="website" pattern="https?://.+">
<script>
// 表單送出前攔截
document.querySelector('form').addEventListener('submit', function(e) {
if (!this.checkValidity()) {
e.preventDefault();
this.reportValidity(); // 顯示瀏覽器內建錯誤訊息
}
});
</script>
完整表單驗證不僅是「檢查資料」,更是「引導用戶成功完成任務」的介面設計。結合伺服器嚴格驗證、前端即時回饋、友善錯誤提示、資料保持機制,能大幅提升轉換率與資料品質。