PHP 表单验证

Client-side validation cannot be trusted; the server must re-validate.

Beginner
Key PointsClient validation is UX; server validation is the defence.

Why server-side validation

Browser required and type="email" are trivially bypassed: edit the HTML, disable JS, or just curl the endpoint. All client data is untrusted.

Bash
# 完全绕过页面,直接提交
curl -X POST http://localhost/welcome.php \
     -d "fname=<script>alert(1)</script>" \
     -d "age=abc"

The server receives exactly that string. Without validation and escaping it lands in the page verbatim — reflected XSS.

Validate and escape

Two separate jobs: validation decides whether data is usable, escaping decides how it is safely displayed. Do both.

PHP
<?php
$name = trim($_POST["fname"] ?? "");
$age  = trim($_POST["age"] ?? "");
$errors = [];

if ($name === "") {
    $errors[] = "名字不能为空";
} elseif (mb_strlen($name) > 20) {
    $errors[] = "名字不能超过 20 个字";
}

if ($age === "") {
    $errors[] = "年龄不能为空";
} elseif (!ctype_digit($age)) {
    $errors[] = "年龄必须是整数";
} elseif ((int)$age < 1 || (int)$age > 120) {
    $errors[] = "年龄必须在 1 到 120 之间";
}

if ($errors) {
    foreach ($errors as $e) {
        echo "错误: $e\n";
    }
} else {
    echo "通过: " . htmlspecialchars($name) . ", " . (int)$age . " 岁\n";
}
Output
通过: Tom, 18 岁

Feeding abc as the age instead yields:

Output
错误: 年龄必须是整数

Validation helpers

CheckFunctionNote
非空$v !== ''别用 empty(),"0" 会被判空
纯数字ctype_digit($v)不接受负号和小数点
整数filter_var($v, FILTER_VALIDATE_INT)支持范围选项
邮箱filter_var($v, FILTER_VALIDATE_EMAIL)标准做法
URLfilter_var($v, FILTER_VALIDATE_URL)要限定协议就再查前缀
IPfilter_var($v, FILTER_VALIDATE_IP)可加 FILTER_FLAG_IPV4
手机号(中国)preg_match('/^1[3-9]\d{9}$/', $v)按业务调整号段
empty() is true for "0", 0 and []. To test whether a field was filled use !== '' or isset().

Collecting errors

Collect all errors in an array and show them together instead of exiting on the first — far better UX.

PHP
<?php
function validate(array $in): array {
    $errors = [];

    $name = trim($in["name"] ?? "");
    if ($name === "") {
        $errors["name"] = "姓名必填";
    }

    $email = trim($in["email"] ?? "");
    if ($email === "") {
        $errors["email"] = "邮箱必填";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors["email"] = "邮箱格式不正确";
    }

    return [$errors, compact("name", "email")];
}

[$errors, $clean] = validate(["name" => "", "email" => "bad@"]);
print_r($errors);
Output
Array
(
    [name] => 姓名必填
    [email] => 邮箱格式不正确
)

Keying errors by field lets the form render each message under its input — the same shape Laravel's validator uses.

Length and encoding

中文的「长度」要用 mb_strlen。用 strlen 会把「张三」算成 6,限制 20 字时实际只能输入 6 个汉字。

PHP
<?php
$v = "张三丰";

echo strlen($v), "\n";
echo mb_strlen($v), "\n";
echo mb_strlen($v) <= 20 ? "长度合法" : "超长", "\n";
Output
9
3
长度合法
Pass "UTF-8" to mb_strlen or ensure default_charset is UTF-8. Mixing GBK and UTF-8 corrupts lengths and truncation.

Friends & Partners