Skip to content

输入过滤与验证

PHP Filter 扩展提供了一组强大的函数,用于对外部输入进行验证和过滤。filter_var()filter_input()filter_has_var() 等函数支持多种数据类型的验证(邮箱、URL、IP、整数等)和过滤(去除标签、转义字符等),是构建安全 PHP 应用的基础工具。

前置知识

阅读本节前,建议先了解:安全总则GET 与 POST 请求

基础概念

验证与过滤的区别

操作目的失败时行为函数后缀
验证(Validation)检查数据是否符合格式返回 falseFILTER_VALIDATE_*
过滤(Sanitization)清理数据中的不安全内容返回清理后的字符串FILTER_SANITIZE_*

使用原则

先验证,后使用。优先使用验证过滤器确认数据格式正确,对于需要显示的内容再使用过滤函数清理。

语法与代码示例

filter_var() -- 验证变量

php
<?php

declare(strict_types=1);

// === FILTER_VALIDATE_INT ===
$int = '42';
$result = filter_var($int, FILTER_VALIDATE_INT);
// 返回: 42 (integer)

// 带范围验证
$result = filter_var($int, FILTER_VALIDATE_INT, [
    'options' => [
        'min_range' => 1,
        'max_range' => 100,
        'default' => 0, // 验证失败时的默认值
    ],
]);

// FILTER_FLAG_ALLOW_HEX(允许 0xFF 格式)
$result = filter_var('0xFF', FILTER_VALIDATE_INT, [
    'flags' => FILTER_FLAG_ALLOW_HEX,
]); // 返回: 255

// FILTER_FLAG_ALLOW_OCTAL(允许 0o77 格式,PHP 7.0+)
$result = filter_var('0o77', FILTER_VALIDATE_INT, [
    'flags' => FILTER_FLAG_ALLOW_OCTAL,
]); // 返回: 63

// === FILTER_VALIDATE_FLOAT ===
$result = filter_var('3.14', FILTER_VALIDATE_FLOAT);
$result = filter_var('1,000.5', FILTER_VALIDATE_FLOAT, [
    'flags' => FILTER_FLAG_ALLOW_THOUSAND,
]);
$result = filter_var('3.14e2', FILTER_VALIDATE_FLOAT, [
    'flags' => FILTER_FLAG_ALLOW_SCIENTIFIC,
]);

// === FILTER_VALIDATE_BOOLEAN ===
$result = filter_var('yes', FILTER_VALIDATE_BOOLEAN);      // true
$result = filter_var('true', FILTER_VALIDATE_BOOLEAN);     // true
$result = filter_var('on', FILTER_VALIDATE_BOOLEAN);       // true
$result = filter_var('1', FILTER_VALIDATE_BOOLEAN);        // true
$result = filter_var('no', FILTER_VALIDATE_BOOLEAN);       // false
$result = filter_var('false', FILTER_VALIDATE_BOOLEAN);     // false
$result = filter_var('0', FILTER_VALIDATE_BOOLEAN);        // false
$result = filter_var('', FILTER_VALIDATE_BOOLEAN);        // false
$result = filter_var(null, FILTER_VALIDATE_BOOLEAN);      // false

// FILTER_NULL_ON_FAILURE:验证失败返回 null 而非 false
$result = filter_var('invalid', FILTER_VALIDATE_BOOLEAN, [
    'flags' => FILTER_NULL_ON_FAILURE,
]); // null

// === FILTER_VALIDATE_EMAIL ===
$result = filter_var('user@example.com', FILTER_VALIDATE_EMAIL);    // 'user@example.com'
$result = filter_var('invalid-email', FILTER_VALIDATE_EMAIL);       // false
$result = filter_var('', FILTER_VALIDATE_EMAIL);                     // false(空字符串无效)
$result = filter_var('user@localhost', FILTER_VALIDATE_EMAIL);      // false(需要 FQDN)

// === FILTER_VALIDATE_URL ===
$result = filter_var('https://www.example.com', FILTER_VALIDATE_URL); // URL string
$result = filter_var('ftp://files.example.com', FILTER_VALIDATE_URL);
$result = filter_var('not-a-url', FILTER_VALIDATE_URL);              // false

// FILTER_FLAG_PATH_REQUIRED(必须有路径)
$result = filter_var('https://example.com', FILTER_VALIDATE_URL, [
    'flags' => FILTER_FLAG_PATH_REQUIRED,
]); // false

// FILTER_FLAG_QUERY_REQUIRED(必须有查询字符串)
$result = filter_var('https://example.com?page=1', FILTER_VALIDATE_URL, [
    'flags' => FILTER_FLAG_QUERY_REQUIRED,
]); // URL string

// === FILTER_VALIDATE_IP ===
$result = filter_var('192.168.1.1', FILTER_VALIDATE_IP);       // '192.168.1.1'
$result = filter_var('::1', FILTER_VALIDATE_IP);                // '::1'
$result = filter_var('not-an-ip', FILTER_VALIDATE_IP);          // false

// 限制 IPv4
$result = filter_var('192.168.1.1', FILTER_VALIDATE_IP, [
    'flags' => FILTER_FLAG_IPV4,
]);

// 限制 IPv6
$result = filter_var('::1', FILTER_VALIDATE_IP, [
    'flags' => FILTER_FLAG_IPV6,
]);

// 禁止私有/保留 IP(PHP 7.4+)
$result = filter_var('10.0.0.1', FILTER_VALIDATE_IP, [
    'flags' => FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
]); // false(10.x.x.x 是私有 IP)

// === FILTER_VALIDATE_DOMAIN ===(PHP 7.0+)
$result = filter_var('www.example.com', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
$result = filter_var('example.com', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
$result = filter_var('sub.domain.example.com', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);

// === FILTER_VALIDATE_MAC ===(PHP 5.5+)
$result = filter_var('00:1A:2B:3C:4D:5E', FILTER_VALIDATE_MAC);
$result = filter_var('00-1A-2B-3C-4D-5E', FILTER_VALIDATE_MAC);

// === FILTER_VALIDATE_REGEXP ===
$result = filter_var('13800138000', FILTER_VALIDATE_REGEXP, [
    'options' => ['regexp' => '/^1[3-9]\d{9}$/'],
]);

// === FILTER_VALIDATE_DATE ===(PHP 8.3+)
$result = filter_var('2024-01-15', FILTER_VALIDATE_DATE, [
    'options' => [
        'format' => 'Y-m-d',
    ],
]); // '2024-01-15' 或 DateTimeImmutable

$result = filter_var('2024/01/15', FILTER_VALIDATE_DATE, [
    'options' => ['format' => 'Y/m/d'],
]);

filter_var() -- 过滤变量

php
<?php

declare(strict_types=1);

// === FILTER_SANITIZE_STRING ===(PHP 8.1+ 已废弃,PHP 8.2 移除)
// 使用 htmlspecialchars 替代
$safe = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// === FILTER_SANITIZE_SPECIAL_CHARS ===
// HTML 转义 ' " < > & 及 ASCII 值小于 32 的字符
$input = '<b>Hello</b> "World" & \'PHP\'';
$safe = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS);
// &lt;b&gt;Hello&lt;/b&gt; &quot;World&quot; &amp; &#039;PHP&#039;

// FILTER_FLAG_STRIP_LOW(去除 ASCII < 32 的字符)
$safe = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW);

// FILTER_FLAG_STRIP_HIGH(去除 ASCII > 127 的字符)
$safe = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_HIGH);

// FILTER_FLAG_ENCODE_HIGH(将 ASCII > 127 编码为 HTML 实体)
$safe = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_ENCODE_HIGH);

// === FILTER_SANITIZE_ENCODED ===
// URL 编码特殊字符
$input = 'hello world & php<>';
$safe = filter_var($input, FILTER_SANITIZE_ENCODED);
// hello+world+%26+php%3C%3E

// === FILTER_SANITIZE_MAGIC_QUOTES ===(PHP 7.4+ 废弃,PHP 8.0 移除)
// 已移除,不要使用

// === FILTER_SANITIZE_NUMBER_INT ===
// 移除所有非数字和正负号
$input = 'abc123.45-67+89xyz';
$safe = filter_var($input, FILTER_SANITIZE_NUMBER_INT);
// 123-67+89

// === FILTER_SANITIZE_NUMBER_FLOAT ===
$input = 'abc123.45,67e8xyz';
$safe = filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT);
// 123.45,67e8

// FILTER_FLAG_ALLOW_FRACTION(允许小数点)
$safe = filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);

// FILTER_FLAG_ALLOW_THOUSAND(允许千位分隔符)
$safe = filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);

// FILTER_FLAG_ALLOW_SCIENTIFIC(允许科学计数法)
$safe = filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC);

// === FILTER_SANITIZE_EMAIL ===
// 移除除字母数字和 !#$%&'*+-/=?^_`{|}~@.[] 以外的字符
$input = 'user (name)@example.com!';
$safe = filter_var($input, FILTER_SANITIZE_EMAIL);
// username@example.com

// === FILTER_SANITIZE_URL ===
// 移除除字母数字和 $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&= 以外的字符
$input = 'https://example.com/path?q=hello world&a=1';
$safe = filter_var($input, FILTER_SANITIZE_URL);

// === FILTER_UNSAFE_RAW ===
// 不做任何过滤(或按标志处理)
$input = '<script>alert(1)</script>';
$safe = filter_var($input, FILTER_UNSAFE_RAW);
// <script>alert(1)</script>(原样返回)

// FILTER_FLAG_STRIP_LOW / STRIP_HIGH
// FILTER_FLAG_ENCODE_LOW / ENCODE_HIGH

filter_input() -- 验证/过滤输入

php
<?php

declare(strict_types=1);

// filter_input 直接从超全局变量获取并验证(推荐)
// 参数类型:
// INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, INPUT_ENV

// 获取并验证 GET 参数
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [
    'options' => ['default' => 1, 'min_range' => 1, 'max_range' => 1000],
]);

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === null) {
    echo "参数不存在";
} elseif ($id === false) {
    echo "参数无效";
} else {
    echo "ID: {$id}";
}

// 获取并验证 POST 数据
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$url = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);

// 获取并过滤输入
$search = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_SPECIAL_CHARS);

// 获取数组参数
$tags = filter_input(INPUT_POST, 'tags', FILTER_DEFAULT, [
    'flags' => FILTER_REQUIRE_ARRAY,
]);

// filter_has_var 检查变量是否存在
if (filter_has_var(INPUT_POST, 'submit')) {
    // 表单已提交
}

// filter_input_array 批量获取和验证
$inputs = filter_input_array(INPUT_POST, [
    'username' => FILTER_SANITIZE_SPECIAL_CHARS,
    'email' => FILTER_VALIDATE_EMAIL,
    'age' => [
        'filter' => FILTER_VALIDATE_INT,
        'options' => ['min_range' => 1, 'max_range' => 150],
    ],
    'website' => [
        'filter' => FILTER_VALIDATE_URL,
        'flags' => FILTER_NULL_ON_FAILURE,
    ],
]);

if ($inputs !== null) {
    foreach ($inputs as $field => $value) {
        if ($value === false) {
            echo "{$field} 无效\n";
        } elseif ($value === null) {
            echo "{$field} 未提供\n";
        }
    }
}

实战示例:输入验证封装类

php
<?php

declare(strict_types=1);

/**
 * 输入验证封装类
 */
class InputValidator
{
    /**
     * 获取整数
     */
    public static function getInt(string $key, int $default = 0, ?int $min = null, ?int $max = null): int
    {
        $options = ['default' => $default];
        if ($min !== null) $options['min_range'] = $min;
        if ($max !== null) $options['max_range'] = $max;

        $value = filter_input(INPUT_GET, $key, FILTER_VALIDATE_INT, ['options' => $options]);
        return $value === false || $value === null ? $default : $value;
    }

    /**
     * 获取字符串
     */
    public static function getString(string $key, string $default = '', int $maxLength = 255): string
    {
        $value = filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS);
        if ($value === null || $value === false) {
            return $default;
        }
        return mb_substr($value, 0, $maxLength);
    }

    /**
     * 获取邮箱
     */
    public static function getEmail(string $source = INPUT_POST): ?string
    {
        $value = filter_input($source, 'email', FILTER_VALIDATE_EMAIL);
        return $value ?: null;
    }

    /**
     * 获取 URL
     */
    public static function getUrl(string $source = INPUT_POST): ?string
    {
        $value = filter_input($source, 'url', FILTER_VALIDATE_URL);
        return $value ?: null;
    }

    /**
     * 获取 IP
     */
    public static function getIp(int $flags = 0): ?string
    {
        $value = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP, [
            'flags' => $flags,
        ]);
        return $value ?: null;
    }

    /**
     * 获取分页参数
     */
    public static function getPagination(int $defaultPage = 1, int $defaultPerPage = 20): array
    {
        return [
            'page' => self::getInt('page', $defaultPage, 1, 10000),
            'per_page' => self::getInt('per_page', $defaultPerPage, 1, 100),
        ];
    }
}

// 使用
$page = InputValidator::getInt('page', 1, 1, 100);
$search = InputValidator::getString('search');
$email = InputValidator::getEmail(INPUT_POST);
$ip = InputValidator::getIp(FILTER_FLAG_IPV4);
[$page, $perPage] = array_values(InputValidator::getPagination());

注意事项

1. filter_var 与类型转换

php
<?php

// filter_var 验证失败返回 false/null,不会抛出异常
$result = filter_var('abc', FILTER_VALIDATE_INT);
// $result === false

// 配合 null 合并运算符使用
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?? 1;

// filter_var 不抛出 TypeError,即使传入非字符串类型
$result = filter_var(42, FILTER_VALIDATE_INT); // 42
$result = filter_var(42, FILTER_VALIDATE_EMAIL); // false

2. FILTER_SANITIZE_STRING 已废弃

php
<?php

// PHP 8.1+ FILTER_SANITIZE_STRING 已废弃
// PHP 8.2+ 已移除
// 替代方案:
$safe = htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

// 或使用 strip_tags
$safe = strip_tags($input, '<p><br><strong><em><a>');

// 或使用 HTML Purifier
$purifier = new HTMLPurifier();
$safe = $purifier->purify($input);

最佳实践

1. 统一使用 filter_input

php
<?php

// 推荐:使用 filter_input(一步完成获取+验证)
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [
    'options' => ['default' => 1, 'min_range' => 1],
]);

// 不推荐:直接访问超全局变量
$page = (int) ($_GET['page'] ?? 1);
if ($page < 1) $page = 1;

下一节

继续学习:输出编码

参考链接