PHP 高级过滤器
Batch filtering with filter_var_array, FILTER_CALLBACK, and nested structures.
Batch filtering
<?php
$data = [
"name" => "Tom",
"age" => "25",
"email" => "tom@example.com",
"ip" => "192.168.1.1",
"site" => "not a url",
];
$rules = [
"name" => ["filter" => FILTER_SANITIZE_SPECIAL_CHARS],
"age" => ["filter" => FILTER_VALIDATE_INT, "options" => ["min_range" => 18]],
"email" => FILTER_VALIDATE_EMAIL,
"ip" => FILTER_VALIDATE_IP,
"site" => FILTER_VALIDATE_URL,
];
print_r(filter_var_array($data, $rules));Array
(
[name] => Tom
[age] => 25
[email] => tom@example.com
[ip] => 192.168.1.1
[site] =>
)site became an empty string (false cast to string). That is dangerous: failure is indistinguishable from empty. Add FILTER_NULL_ON_FAILURE to tell them apart.
<?php
$rules = [
"site" => [
"filter" => FILTER_VALIDATE_URL,
"flags" => FILTER_NULL_ON_FAILURE,
],
];
$r = filter_var_array(["site" => "not a url"], $rules);
var_dump($r["site"]);
var_dump(array_key_exists("site", $r));NULL
bool(true)Required and missing
Missing keys come back as null without error. To distinguish missing from invalid, check array_key_exists first.
<?php
$input = ["email" => "bad@"];
$rules = ["email" => FILTER_VALIDATE_EMAIL, "age" => FILTER_VALIDATE_INT];
$clean = filter_var_array($input, $rules);
$errors = [];
foreach ($rules as $field => $_) {
if (!array_key_exists($field, $input) || $input[$field] === "") {
$errors[$field] = "$field 必填";
} elseif ($clean[$field] === null || $clean[$field] === false) {
$errors[$field] = "$field 格式不正确";
}
}
print_r($errors);Array
(
[email] => email 格式不正确
[age] => age 必填
)FILTER_CALLBACK
When built-ins fall short, FILTER_CALLBACK attaches your own function — good for normalisation like lowercasing or whitespace collapsing.
<?php
function normalize(string $v): string {
return mb_strtolower(preg_replace("/\s+/u", " ", trim($v)));
}
$r = filter_var(" Hello World ", FILTER_CALLBACK, ["options" => "normalize"]);
echo $r, "\n";
// 用闭包
$slug = filter_var("Hello World 2023", FILTER_CALLBACK, [
"options" => fn($v) => strtolower(str_replace(" ", "-", $v)),
]);
echo $slug, "\n";hello world
hello-world-2023FILTER_CALLBACK never receives null — PHP returns null without calling. Handle null separately.Nested arrays
filter_var_array handles one level. For deeper structures recurse or filter level by level.
<?php
$input = [
"user" => ["name" => " Tom ", "email" => "tom@example.com"],
"tags" => [" php ", "web", ""],
];
function cleanValue(mixed $v): mixed {
if (is_array($v)) {
return array_map("cleanValue", $v);
}
return is_string($v) ? trim($v) : $v;
}
$clean = cleanValue($input);
print_r($clean);
$clean["tags"] = array_values(array_filter($clean["tags"], fn($t) => $t !== ""));
print_r($clean["tags"]);Array
(
[user] => Array
(
[name] => Tom
[email] => tom@example.com
)
[tags] => Array
(
[0] => php
[1] => web
[2] =>
)
)
Array
(
[0] => php
[1] => web
)A whole-form validator
<?php
function validateForm(array $in): array
{
$rules = [
"name" => ["required" => true, "filter" => FILTER_SANITIZE_SPECIAL_CHARS],
"email" => ["required" => true, "filter" => FILTER_VALIDATE_EMAIL],
"age" => ["required" => true, "filter" => FILTER_VALIDATE_INT,
"options" => ["min_range" => 18, "max_range" => 99]],
"site" => ["required" => false, "filter" => FILTER_VALIDATE_URL],
];
$clean = filter_var_array($in, $rules);
$errors = [];
foreach ($rules as $field => $r) {
$raw = trim((string)($in[$field] ?? ""));
if ($r["required"] && $raw === "") {
$errors[$field] = "$field 必填";
continue;
}
if ($raw === "") {
continue;
}
if ($clean[$field] === false || $clean[$field] === null) {
$errors[$field] = "$field 格式不正确";
}
}
return [$errors, $clean];
}
[$errors, $clean] = validateForm([
"name" => "Tom", "email" => "tom@example.com", "age" => "17", "site" => "",
]);
print_r($errors);
print_r($clean);Array
(
[age] => age 格式不正确
)
Array
(
[name] => Tom
[email] => tom@example.com
[age] =>
[site] =>
)The rule table declares required and format in one place, so adding a field touches only the array. This is the core idea behind Laravel's and Symfony's validators.
filter_var_array ignores extra keys like required and only reads filter / flags / options. Pass the whole rule table directly — extracting just filter would drop the options range checks.批量过滤
<?php
$data = [
"name" => "Tom",
"age" => "25",
"email" => "tom@example.com",
"ip" => "192.168.1.1",
"site" => "not a url",
];
$rules = [
"name" => ["filter" => FILTER_SANITIZE_SPECIAL_CHARS],
"age" => ["filter" => FILTER_VALIDATE_INT, "options" => ["min_range" => 18]],
"email" => FILTER_VALIDATE_EMAIL,
"ip" => FILTER_VALIDATE_IP,
"site" => FILTER_VALIDATE_URL,
];
print_r(filter_var_array($data, $rules));Array
(
[name] => Tom
[age] => 25
[email] => tom@example.com
[ip] => 192.168.1.1
[site] =>
)site 校验失败变成空字符串(false 转字符串的结果)。这个行为很危险——失败和「空值」分不清。要区分就加 FILTER_NULL_ON_FAILURE。
<?php
$rules = [
"site" => [
"filter" => FILTER_VALIDATE_URL,
"flags" => FILTER_NULL_ON_FAILURE,
],
];
$r = filter_var_array(["site" => "not a url"], $rules);
var_dump($r["site"]);
var_dump(array_key_exists("site", $r));NULL
bool(true)必填与缺失
filter_var_array 对缺失的键返回 null,且默认不会报错。要区分「缺失」和「非法」,先查 array_key_exists。
<?php
$input = ["email" => "bad@"];
$rules = ["email" => FILTER_VALIDATE_EMAIL, "age" => FILTER_VALIDATE_INT];
$clean = filter_var_array($input, $rules);
$errors = [];
foreach ($rules as $field => $_) {
if (!array_key_exists($field, $input) || $input[$field] === "") {
$errors[$field] = "$field 必填";
} elseif ($clean[$field] === null || $clean[$field] === false) {
$errors[$field] = "$field 格式不正确";
}
}
print_r($errors);Array
(
[email] => email 格式不正确
[age] => age 必填
)FILTER_CALLBACK
内置过滤器不够用时,用 FILTER_CALLBACK 挂自己的函数。适合做「转小写」「去掉多余空白」这类规范化。
<?php
function normalize(string $v): string {
return mb_strtolower(preg_replace("/\s+/u", " ", trim($v)));
}
$r = filter_var(" Hello World ", FILTER_CALLBACK, ["options" => "normalize"]);
echo $r, "\n";
// 用闭包
$slug = filter_var("Hello World 2023", FILTER_CALLBACK, [
"options" => fn($v) => strtolower(str_replace(" ", "-", $v)),
]);
echo $slug, "\n";hello world
hello-world-2023FILTER_CALLBACK 的回调**收不到 null**——PHP 会跳过 null 值直接返回 null。如果你的规范化逻辑要处理 null,得在外面另写判断。嵌套数组
filter_var_array 只处理一层。多层结构要自己递归,或者拆开逐层过滤。
<?php
$input = [
"user" => ["name" => " Tom ", "email" => "tom@example.com"],
"tags" => [" php ", "web", ""],
];
function cleanValue(mixed $v): mixed {
if (is_array($v)) {
return array_map("cleanValue", $v);
}
return is_string($v) ? trim($v) : $v;
}
$clean = cleanValue($input);
print_r($clean);
$clean["tags"] = array_values(array_filter($clean["tags"], fn($t) => $t !== ""));
print_r($clean["tags"]);Array
(
[user] => Array
(
[name] => Tom
[email] => tom@example.com
)
[tags] => Array
(
[0] => php
[1] => web
[2] =>
)
)
Array
(
[0] => php
[1] => web
)整张表单的校验器
<?php
function validateForm(array $in): array
{
$rules = [
"name" => ["required" => true, "filter" => FILTER_SANITIZE_SPECIAL_CHARS],
"email" => ["required" => true, "filter" => FILTER_VALIDATE_EMAIL],
"age" => ["required" => true, "filter" => FILTER_VALIDATE_INT,
"options" => ["min_range" => 18, "max_range" => 99]],
"site" => ["required" => false, "filter" => FILTER_VALIDATE_URL],
];
$clean = filter_var_array($in, $rules);
$errors = [];
foreach ($rules as $field => $r) {
$raw = trim((string)($in[$field] ?? ""));
if ($r["required"] && $raw === "") {
$errors[$field] = "$field 必填";
continue;
}
if ($raw === "") {
continue;
}
if ($clean[$field] === false || $clean[$field] === null) {
$errors[$field] = "$field 格式不正确";
}
}
return [$errors, $clean];
}
[$errors, $clean] = validateForm([
"name" => "Tom", "email" => "tom@example.com", "age" => "17", "site" => "",
]);
print_r($errors);
print_r($clean);Array
(
[age] => age 格式不正确
)
Array
(
[name] => Tom
[email] => tom@example.com
[age] =>
[site] =>
)规则表把「必填」和「格式」放在一处声明,新增字段只改数组不动逻辑。这是 Laravel、Symfony 校验器的核心思路——只是它们做得更细。
filter_var_array 会忽略规则里多出来的 required 键,只认 filter / flags / options。所以整张规则表可以直接丢进去,不用像早期写法那样 array_map 只抽 filter——那样会把 options 里的范围条件一起丢掉。