PHP 过滤器
filter_var's validate and sanitise filters, and why it should be the default.
Key Points`filter_var` is a built-in, audited implementation — more reliable than hand-rolled regex.
Validate vs sanitise
Two different goals: validate answers "is this legal", sanitise answers "what remains after stripping".
PHP
<?php
// 验证:合法返回值,非法返回 false
var_dump(filter_var("tom@example.com", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("not-an-email", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("42", FILTER_VALIDATE_INT));
var_dump(filter_var("4.2", FILTER_VALIDATE_INT));
var_dump(filter_var("http://example.com", FILTER_VALIDATE_URL));
// 清洗:总是返回字符串
echo filter_var("<b>hi</b>", FILTER_SANITIZE_SPECIAL_CHARS), "\n";Output
string(15) "tom@example.com"
bool(false)
int(42)
bool(false)
string(18) "http://example.com"
<b>hi</b>Ranges and options
PHP
<?php
$opt = ["options" => ["min_range" => 1, "max_range" => 255]];
var_dump(filter_var("300", FILTER_VALIDATE_INT, $opt));
var_dump(filter_var("80", FILTER_VALIDATE_INT, $opt));
// 允许前导零的写法
var_dump(filter_var("007", FILTER_VALIDATE_INT));
// 允许八进制 / 十六进制
var_dump(filter_var("0x1A", FILTER_VALIDATE_INT, ["flags" => FILTER_FLAG_ALLOW_HEX]));
// 校验失败时返回默认值
var_dump(filter_var("abc", FILTER_VALIDATE_INT, ["options" => ["default" => 0]]));Output
bool(false)
int(80)
bool(false)
int(26)
int(0)"007" fails because leading zeros are not standard decimal. FILTER_FLAG_ALLOW_OCTAL accepts it but reads it as octal.
Common filters
| Filter | Accepts |
|---|---|
| FILTER_VALIDATE_EMAIL | 符合 RFC 的邮箱 |
| FILTER_VALIDATE_URL | 带协议的 URL |
| FILTER_VALIDATE_IP | IPv4 / IPv6 |
| FILTER_VALIDATE_MAC | MAC 地址 |
| FILTER_VALIDATE_DOMAIN | 域名 |
| FILTER_VALIDATE_BOOLEAN | 1/0/true/false/on/off/yes/no |
| FILTER_VALIDATE_REGEXP | 自定义正则 |
| FILTER_SANITIZE_EMAIL | 删掉邮箱里不允许的字符 |
| FILTER_SANITIZE_NUMBER_INT | 只留数字和正负号 |
| FILTER_SANITIZE_URL | 删掉 URL 里不允许的字符 |
| FILTER_SANITIZE_SPECIAL_CHARS | HTML 转义 |
PHP
<?php
var_dump(filter_var("192.168.1.1", FILTER_VALIDATE_IP));
var_dump(filter_var("::1", FILTER_VALIDATE_IP));
var_dump(filter_var("192.168.1.1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
var_dump(filter_var("yes", FILTER_VALIDATE_BOOLEAN));
var_dump(filter_var("no", FILTER_VALIDATE_BOOLEAN));
var_dump(filter_var("maybe", FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE));
echo filter_var("abc123-45", FILTER_SANITIZE_NUMBER_INT), "\n";
echo filter_var("a b@c d.com", FILTER_SANITIZE_EMAIL), "\n";Output
string(11) "192.168.1.1"
string(3) "::1"
string(11) "192.168.1.1"
bool(true)
bool(false)
NULL
123-45
ab@cd.comWith FILTER_NULL_ON_FAILURE, unrecognised values return null instead of false — distinguishing "explicitly false" from "not a boolean".
Custom regex filter
PHP
<?php
$filter = [
"filter" => FILTER_VALIDATE_REGEXP,
"options" => ["regexp" => "/^[A-Za-z][A-Za-z0-9_]{5,17}$/"],
];
foreach (["Tom_2023", "1abc", "ab"] as $v) {
$ok = filter_var($v, $filter["filter"], $filter["options"]) !== false;
echo $v . " " . ($ok ? "合法" : "非法"), "\n";
}Output
Tom_2023 合法
1abc 非法
ab 非法FILTER_VALIDATE_REGEXP uses preg_match semantics, so anchor with ^ and $ yourself — otherwise partial matches pass.Why prefer filter
- Audited —— edge cases like IPv6, IDN and timezones are handled
- Stable —— a missing regex character does not silently pass bad input
- Readable —— clearer than a long regex
- But not strict enough —— business rules like phone prefixes or ID checksums are yours to write
FILTER_VALIDATE_EMAIL accepts a@b (RFC allows it). If a TLD is required, add your own check.验证 vs 清洗
两类过滤器目的不同:验证回答「这个值合法吗」,清洗回答「去掉非法部分后是什么」。
PHP
<?php
// 验证:合法返回值,非法返回 false
var_dump(filter_var("tom@example.com", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("not-an-email", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("42", FILTER_VALIDATE_INT));
var_dump(filter_var("4.2", FILTER_VALIDATE_INT));
var_dump(filter_var("http://example.com", FILTER_VALIDATE_URL));
// 清洗:总是返回字符串
echo filter_var("<b>hi</b>", FILTER_SANITIZE_SPECIAL_CHARS), "\n";Output
string(15) "tom@example.com"
bool(false)
int(42)
bool(false)
string(18) "http://example.com"
<b>hi</b>范围与选项
PHP
<?php
$opt = ["options" => ["min_range" => 1, "max_range" => 255]];
var_dump(filter_var("300", FILTER_VALIDATE_INT, $opt));
var_dump(filter_var("80", FILTER_VALIDATE_INT, $opt));
// 允许前导零的写法
var_dump(filter_var("007", FILTER_VALIDATE_INT));
// 允许八进制 / 十六进制
var_dump(filter_var("0x1A", FILTER_VALIDATE_INT, ["flags" => FILTER_FLAG_ALLOW_HEX]));
// 校验失败时返回默认值
var_dump(filter_var("abc", FILTER_VALIDATE_INT, ["options" => ["default" => 0]]));Output
bool(false)
int(80)
bool(false)
int(26)
int(0)"007" 被判非法是因为前导零不是标准十进制写法。要接受它得加 FILTER_FLAG_ALLOW_OCTAL,但那会把 007 当成八进制 7。
常用过滤器
| 过滤器 | 接受 |
|---|---|
| FILTER_VALIDATE_EMAIL | 符合 RFC 的邮箱 |
| FILTER_VALIDATE_URL | 带协议的 URL |
| FILTER_VALIDATE_IP | IPv4 / IPv6 |
| FILTER_VALIDATE_MAC | MAC 地址 |
| FILTER_VALIDATE_DOMAIN | 域名 |
| FILTER_VALIDATE_BOOLEAN | 1/0/true/false/on/off/yes/no |
| FILTER_VALIDATE_REGEXP | 自定义正则 |
| FILTER_SANITIZE_EMAIL | 删掉邮箱里不允许的字符 |
| FILTER_SANITIZE_NUMBER_INT | 只留数字和正负号 |
| FILTER_SANITIZE_URL | 删掉 URL 里不允许的字符 |
| FILTER_SANITIZE_SPECIAL_CHARS | HTML 转义 |
PHP
<?php
var_dump(filter_var("192.168.1.1", FILTER_VALIDATE_IP));
var_dump(filter_var("::1", FILTER_VALIDATE_IP));
var_dump(filter_var("192.168.1.1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
var_dump(filter_var("yes", FILTER_VALIDATE_BOOLEAN));
var_dump(filter_var("no", FILTER_VALIDATE_BOOLEAN));
var_dump(filter_var("maybe", FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE));
echo filter_var("abc123-45", FILTER_SANITIZE_NUMBER_INT), "\n";
echo filter_var("a b@c d.com", FILTER_SANITIZE_EMAIL), "\n";Output
string(11) "192.168.1.1"
string(3) "::1"
string(11) "192.168.1.1"
bool(true)
bool(false)
NULL
123-45
ab@cd.comFILTER_VALIDATE_BOOLEAN 加 FILTER_NULL_ON_FAILURE 后,无法识别的值返回 null 而不是 false——这样能区分「明确是假」和「根本不是布尔值」。
自定义正则过滤
PHP
<?php
$filter = [
"filter" => FILTER_VALIDATE_REGEXP,
"options" => ["regexp" => "/^[A-Za-z][A-Za-z0-9_]{5,17}$/"],
];
foreach (["Tom_2023", "1abc", "ab"] as $v) {
$ok = filter_var($v, $filter["filter"], $filter["options"]) !== false;
echo $v . " " . ($ok ? "合法" : "非法"), "\n";
}Output
Tom_2023 合法
1abc 非法
ab 非法FILTER_VALIDATE_REGEXP 的正则必须完整匹配吗?不是——它用 preg_match 语义,所以要自己加 ^ 和 $。忘了锚点会变成「包含即通过」。为什么优先用 filter
- 经过审计 —— 边界情况(IPv6、国际化域名、时区)都处理过
- 行为稳定 —— 不会因为某个正则少写一个字符就放过非法值
- 可读 ——
FILTER_VALIDATE_EMAIL比一长串正则清楚 - 但不够严格 —— 业务规则(手机号号段、身份证校验位)还得自己写
FILTER_VALIDATE_EMAIL 会接受 a@b 这种没有点的域名(RFC 允许)。如果业务要求必须有顶级域,得再补一道 str_contains($email, ".") 检查。