驗證層級與選擇
| 層級 | 方法 | 準確度 | 效能 | 適用場景 |
| 1. 基礎格式 | filter_var($email, FILTER_VALIDATE_EMAIL) | 高(符合 RFC 5322 子集) | 極快 | 大多數表單驗證、註冊、登入 |
| 2. 嚴格 RFC 5322 | 完整正則(超過 6000 字元) | 最高 | 慢 | 郵件系統核心、標準相容性測試 |
| 3. DNS MX 記錄 | checkdnsrr($domain, 'MX') | 驗證域名可收信 | 網路延遲 | 減少垃圾帳號、API 整合前檢查 |
| 4. SMTP 連線驗證 | RCPT TO 指令 | 驗證信箱真實存在 | 極慢、易被封鎖 | 高價值線索清洗、專業郵件服務 |
推薦:PHP 內建 filter_var(PHP 5.2+)
function isValidEmail(string $email): bool {
// 長度限制(RFC 5321: local-part <= 64, domain <= 255, total <= 254)
if (strlen($email) > 254) return false;
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
// 測試
var_dump(isValidEmail('user@example.com')); // true
var_dump(isValidEmail('user.name+tag@example.co.uk')); // true
var_dump(isValidEmail('user@126,com')); // false (逗號非法)
var_dump(isValidEmail('user@[IPv6:2001:db8::1]')); // true (IP 字面域名)
進階:自訂正則(支援國際化域名 IDN、嚴格長度)
function isValidEmailStrict(string $email): bool {
// 1. 基礎長度
if (strlen($email) > 254) return false;
// 2. 分離 local-part @ domain
$parts = explode('@', $email, 2);
if (count($parts) !== 2) return false;
[$local, $domain] = $parts;
// 3. local-part 長度 (RFC 5321)
if (strlen($local) > 64) return false;
// 4. domain 長度
if (strlen($domain) > 255) return false;
// 5. domain 格式:支援 IDN (xn--...)、IP 字面、標準域名
// 簡化版:標準域名 + IDN 已轉換為 ASCII
$domainRegex = '/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i';
if (!preg_match($domainRegex, $domain)) {
// 嘗試 IDN 轉換
if (function_exists('idn_to_ascii')) {
$domainAscii = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
if ($domainAscii && preg_match($domainRegex, $domainAscii)) return true;
}
return false;
}
// 6. local-part 格式(簡化:允許字母數字、點、底線、減號、加號,不以點開頭結尾、無連續點)
$localRegex = '/^[a-z0-9_+-]+(?:\.[a-z0-9_+-]+)*$/i';
return preg_match($localRegex, $local) === 1;
}
DNS MX 記錄檢查(非同步化建議)
function hasMxRecord(string $domain): bool {
// 移除可選的 [IPv6:...] 包裝
$domain = trim($domain, '[]');
if (str_starts_with($domain, 'IPv6:')) $domain = substr($domain, 5);
return checkdnsrr($domain, 'MX');
}
// 非同步批次檢查(Swoole/RoadRunner/ReactPHP)
// 生產環境請勿在 HTTP 請求中同步呼叫 checkdnsrr,會阻塞 1-5 秒
常見誤區與避坑
| 誤區 | 正確認知 |
用簡單正則 /^.+@.+\..+$/ | 通過 a@b.c 但擋不住 user@domain (無 TLD)、user@-domain.com、user@domain..com |
擋住 + 標籤(user+tag@gmail.com) | 合法且實用(Gmail、Outlook 支援),請勿過濾 |
| 要求 TLD 至少 2 字元 | 新頂級域可為 1 字元(理論),但實務極少見,{2,} 通常可接受 |
| 不支援國際化域名(中文域名) | 用 idn_to_ascii() 轉換後驗證 |
同步 checkdnsrr 在高併發 API | 會耗盡 PHP-FPM 進程,改用非同步佇列或快取結果 |
結語
**95% 場景直接用 filter_var($email, FILTER_VALIDATE_EMAIL) 即可**,內建處理 RFC 5322 複雜規則、效能最佳。需嚴格域名可收信驗證時,加上非同步 checkdnsrr。切勿自寫簡單正則,邊界情況極多且難以維護。