PHP 表单 - 验证邮件和URL

Validating email, URL and name fields with regex and the filter family.

Beginner
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
NULL
Pseudo-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

PatternMatches
/^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

GoalFunctionFailure 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.

Friends & Partners