当值是有效的邮箱地址时,filter_var($value, FILTER_VALIDATE_EMAIL) 返回该值,否则返回 false。换一个过滤器就能检查其他格式:FILTER_VALIDATE_INT、FILTER_VALIDATE_FLOAT、FILTER_VALIDATE_URL、FILTER_VALIDATE_BOOL、FILTER_VALIDATE_IP。结果始终要用 === false 比较。
有两个结果常让人意外。ada@example 被拒绝,是因为域名里没有点;' ada@example.com' 被拒绝,是因为开头有空格,所以验证之前要先用 trim() 处理输入。
验证表单中的邮箱
把同样的检查放到表单后面。运行它,输入一个地址并点击 Check;这段代码会带着你在 $_POST 中的输入再运行一次。
FILTER_VALIDATE_EMAIL 只检查格式。nobody@example.com 能通过,域名里有拼写错误的地址也能通过。非 ASCII 字符会被拒绝:adä@example.com 只有加上 FILTER_FLAG_EMAIL_UNICODE 才能通过,这个标志允许 @ 前面出现这类字符;而 exämple.com 这样的域名即使加了标志也会失败,除非你先把它转换成 xn-- 形式。要知道一个地址能不能用,唯一的办法是给它发送确认链接。
验证整数,以及 min_range 和 max_range
FILTER_VALIDATE_INT 把有效的字符串转换成 int,拒绝其他所有值。选项数组可以加上范围和默认值:
首尾的空白和开头的 + 是允许的,开头的零('042')则不允许;'42' 的结果是整数 42,而不是字符串。超出范围得到 false;设置了 default 选项时,你会得到默认值而不是 false,这对 ?page= 这类参数很方便(见 _POST)。加上 FILTER_FLAG_ALLOW_HEX 或 FILTER_FLAG_ALLOW_OCTAL 就能接受 0x1A 或 0755。
0 与 false 的陷阱
有效的 '0' 返回整数 0,而 0 是假值。普通的 if 会把它当成失败:
FILTER_VALIDATE_FLOAT('0.0')和 FILTER_VALIDATE_BOOL 也一样,对后者来说 false 本身就是有效的答案。FILTER_NULL_ON_FAILURE 正是为此而设的。
布尔值、浮点数、URL 和 IP 地址
FILTER_VALIDATE_BOOL 是 PHP 8 中的名称;FILTER_VALIDATE_BOOLEAN 是同一个过滤器。不加 FILTER_NULL_ON_FAILURE 的话,'maybe' 会返回 false,与 'off' 无法区分。
有效的 URL 不等于安全的链接
FILTER_VALIDATE_URL 只检查字符串的形式是否像 URL,任何协议都可以。这其中包括你绝不希望出现在 href 中的协议:
javascript://comment%0Aalert(1) 能通过 FILTER_VALIDATE_URL,而点击这个链接时浏览器会把它当作 JavaScript 运行,因为 %0A 结束了 //comment。把用户提供的 URL 输出到页面之前,先用 parse_url() 检查协议。你还可以用 FILTER_FLAG_PATH_REQUIRED 和 FILTER_FLAG_QUERY_REQUIRED 要求 URL 必须包含某些部分。
净化过滤器
净化过滤器不回答“是”或“否”,而是移除字符。它们适合规范化输入,但不能让输入变得可以安全输出:
看第一行:Order #12-345 (x2) 变成了 12-3452,一个输入中从未出现过的数字;第三行则是一个没有人输入过的地址。如果做了净化,之后还要验证结果。对于输出到 HTML 中的任何内容,正确的工具是在输出时使用 htmlspecialchars()(FILTER_SANITIZE_FULL_SPECIAL_CHARS 做的是同样的转义)。FILTER_SANITIZE_STRING 从 PHP 8.1 起已被弃用;替代方案见 htmlspecialchars。
用 filter_var_array 验证整个表单
filter_var_array() 对每个键应用一个过滤器,返回的数组包含你要求的每个键:过滤后的值、验证失败时的 false,或者键不存在时的 null。
role 不见了:没有列出的键会被丢弃,这样访问者就无法往表单里加上 role=admin 并让它被保存下来。country 是 null,因为它没有被提交。在 Web 服务器上,filter_input_array(INPUT_POST, $rules) 做同样的事,只是直接从请求中读取。
常见问题
PHP 中怎么验证邮箱地址?
用 filter_var($email, FILTER_VALIDATE_EMAIL),邮箱有效时返回邮箱字符串,无效时返回 false:if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { /* invalid */ }。它只检查格式;要知道地址是否真实存在,得发一封确认邮件。
PHP 中怎么判断一个字符串是不是整数?
filter_var($s, FILTER_VALIDATE_INT) 对 "42"、"-7" 和 " 42 " 返回整数,对 "4.2"、"42abc" 或 "042" 返回 false。要用 !== false 比较,因为有效的 "0" 会返回 0,而它是假值。
为什么 filter_var 对 0 返回 false?
其实没有:filter_var('0', FILTER_VALIDATE_INT) 返回的是整数 0。bug 出在判断上,if (filter_var(...)) 把 0 当成了 false。应该写 if (filter_var($v, FILTER_VALIDATE_INT) !== false)。
PHP 8.1 中用什么替代 FILTER_SANITIZE_STRING?
没有直接的替代品;它被弃用,是因为它的名字承诺了它做不到的安全性。验证你期望的输入,按原样存储,输出时再用 htmlspecialchars() 转义。如果需要去掉标签,用 strip_tags()。
filter_var 和 filter_input 有什么区别?
filter_var() 过滤你传入的任何值。filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) 直接从原始请求中读取,所以参数不存在时返回 null,并且会忽略你的代码对 $_GET 做的任何修改。两者接受相同的过滤器和选项。