PHP 表单 - 验证邮件和URL
Validating email, URL and name fields with regex and the filter family.
Key PointsPrefer `filter_var`; reach for regex only when it falls short.
Email validation
PHP
<?php
$tests = [
"tom@example.com",
"tom@example",
"tom.example.com",
"tom+tag@sub.example.co.uk",
];
foreach ($tests as $t) {
$ok = filter_var($t, FILTER_VALIDATE_EMAIL) !== false;
echo $t . " " . ($ok ? "合法" : "非法"), "\n";
}Output
tom@example.com 合法
tom@example 非法
tom.example.com 非法
tom+tag@sub.example.co.uk 合法FILTER_VALIDATE_EMAIL checks syntax only, not whether the domain exists. The only reliable confirmation is sending a verification mail.URL validation
PHP
<?php
$urls = [
"http://example.com",
"https://example.com/path?q=1",
"ftp://example.com",
"example.com",
"javascript:alert(1)",
];
foreach ($urls as $u) {
$ok = filter_var($u, FILTER_VALIDATE_URL) !== false;
echo $u . " " . ($ok ? "合法" : "非法"), "\n";
}Output
http://example.com 合法
https://example.com/path?q=1 合法
ftp://example.com 合法
example.com 非法
javascript:alert(1) 非法Note ftp:// passes — the filter does not restrict the scheme. To allow only http(s), check the prefix yourself.
PHP
<?php
function safeUrl(string $u): ?string {
if (filter_var($u, FILTER_VALIDATE_URL) === false) {
return null;
}
$scheme = strtolower((string)parse_url($u, PHP_URL_SCHEME));
if (!in_array($scheme, ["http", "https"], true)) {
return null;
}
return $u;
}
var_dump(safeUrl("https://example.com/a"));
var_dump(safeUrl("javascript:alert(1)"));
var_dump(safeUrl("ftp://example.com"));Output
string(23) "https://example.com/a"
NULL
NULLPseudo-protocols like
javascript: are a prime XSS vector. Run user links through a scheme allowlist before rendering <a href>.Name fields: letters and spaces
PHP
<?php
$names = ["Tom Smith", "张三", "Tom123", "Tom<script>", "O'Brien"];
foreach ($names as $n) {
// 允许中文、字母、空格、撇号、连字符
$ok = (bool)preg_match('/^[\p{Han}A-Za-z\x{20}\'\-]{1,50}$/u', $n);
echo $n . " " . ($ok ? "合法" : "非法"), "\n";
}Output
Tom Smith 合法
张三 合法
Tom123 非法
Tom<script> 非法
O'Brien 合法The /u modifier enables UTF-8 mode; \p{Han} matches Chinese ideographs. Without /u Chinese is rejected.
Regex snippets
| Pattern | Matches |
|---|---|
| /^1[3-9]\d{9}$/ | 中国大陆手机号 |
| /^\d{6}$/ | 6 位数字(验证码) |
| /^[A-Za-z]\w{5,17}$/ | 字母开头的用户名,6-18 位 |
| /^\d{17}[\dXx]$/ | 18 位身份证 |
| /^\d{4}-\d{2}-\d{2}$/ | 日期格式(不校验真实性) |
Regex checks shape, not truth.
2023-02-31 passes the pattern but is not a real date — use checkdate() or DateTime::createFromFormat().Validation vs sanitisation
| Goal | Function | Failure behaviour |
|---|---|---|
| 判断合法性 | FILTER_VALIDATE_* | 返回 false |
| 清洗掉非法字符 | FILTER_SANITIZE_* | 返回清理后的字符串 |
Do not use sanitise as a substitute for validate. FILTER_SANITIZE_EMAIL strips the space from tom@exa mple.com and returns something plausible, hiding the user's mistake.
邮箱验证
PHP
<?php
$tests = [
"tom@example.com",
"tom@example",
"tom.example.com",
"tom+tag@sub.example.co.uk",
];
foreach ($tests as $t) {
$ok = filter_var($t, FILTER_VALIDATE_EMAIL) !== false;
echo $t . " " . ($ok ? "合法" : "非法"), "\n";
}Output
tom@example.com 合法
tom@example 非法
tom.example.com 非法
tom+tag@sub.example.co.uk 合法FILTER_VALIDATE_EMAIL 只做语法校验,不验证域名是否真实存在。要确认邮箱可用,唯一可靠的办法是发一封验证邮件。URL 验证
PHP
<?php
$urls = [
"http://example.com",
"https://example.com/path?q=1",
"ftp://example.com",
"example.com",
"javascript:alert(1)",
];
foreach ($urls as $u) {
$ok = filter_var($u, FILTER_VALIDATE_URL) !== false;
echo $u . " " . ($ok ? "合法" : "非法"), "\n";
}Output
http://example.com 合法
https://example.com/path?q=1 合法
ftp://example.com 合法
example.com 非法
javascript:alert(1) 非法注意 ftp:// 也算合法——FILTER_VALIDATE_URL 不限制协议。用户提交的链接要限定 http / https,必须自己再查前缀。
PHP
<?php
function safeUrl(string $u): ?string {
if (filter_var($u, FILTER_VALIDATE_URL) === false) {
return null;
}
$scheme = strtolower((string)parse_url($u, PHP_URL_SCHEME));
if (!in_array($scheme, ["http", "https"], true)) {
return null;
}
return $u;
}
var_dump(safeUrl("https://example.com/a"));
var_dump(safeUrl("javascript:alert(1)"));
var_dump(safeUrl("ftp://example.com"));Output
string(23) "https://example.com/a"
NULL
NULLjavascript: 这种伪协议是 XSS 的主要入口之一。用户提交的链接在渲染成 <a href> 之前,必须过一遍协议白名单。名字字段:只允许字母和空格
PHP
<?php
$names = ["Tom Smith", "张三", "Tom123", "Tom<script>", "O'Brien"];
foreach ($names as $n) {
// 允许中文、字母、空格、撇号、连字符
$ok = (bool)preg_match('/^[\p{Han}A-Za-z\x{20}\'\-]{1,50}$/u', $n);
echo $n . " " . ($ok ? "合法" : "非法"), "\n";
}Output
Tom Smith 合法
张三 合法
Tom123 非法
Tom<script> 非法
O'Brien 合法/u 修饰符让 PCRE 按 UTF-8 处理,\p{Han} 匹配汉字。不加 /u 时 \p{Han} 不生效,中文会被判非法。
正则常用片段
| 正则 | 匹配 |
|---|---|
| /^1[3-9]\d{9}$/ | 中国大陆手机号 |
| /^\d{6}$/ | 6 位数字(验证码) |
| /^[A-Za-z]\w{5,17}$/ | 字母开头的用户名,6-18 位 |
| /^\d{17}[\dXx]$/ | 18 位身份证 |
| /^\d{4}-\d{2}-\d{2}$/ | 日期格式(不校验真实性) |
正则只适合「形状」,不适合「真假」。
2023-02-31 能过日期正则但不是真实日期——真实性交给 checkdate() 或 DateTime::createFromFormat()。验证与过滤的区别
| 目的 | 函数 | 失败行为 |
|---|---|---|
| 判断合法性 | FILTER_VALIDATE_* | 返回 false |
| 清洗掉非法字符 | FILTER_SANITIZE_* | 返回清理后的字符串 |
不要用 sanitize 代替 validate。FILTER_SANITIZE_EMAIL 会把 tom@exa mple.com 里的空格删掉然后返回一个「看起来能用」的地址,掩盖了用户输错的事实。