PHP 表单与请求处理

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

PHP 8.4

处理用户输入是 Web 开发里风险最高的环节:任何来自请求的数据都不可信。这一篇讲如何安全地接收、校验和输出表单数据。

超全局变量

PHP 把请求信息放进几个超全局数组,在函数内也能直接访问:

变量 内容
$_GET URL 查询参数(?id=1)
$_POST POST 请求体(表单 method="post")
$_SERVER 请求与服务器信息,如 REQUEST_METHOD、REQUEST_URI
$_FILES 上传的文件信息
$_COOKIE 请求的 Cookie
$_SESSION 会话数据(需先 session_start())

直接访问不存在的键会产生警告,取值一律用 ??:

$id = $_GET["id"] ?? null;

GET 与 POST 的分工

  • GET:查询、筛选、分页等不改变服务器状态的操作,参数出现在 URL 里,可被收藏和分享。
  • POST:提交表单、创建或修改数据,参数在请求体里,不在 URL 中。

判断当前请求方法:

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    // 处理提交
}

不要用 $_POST 是否为空来判断:一个没有字段的 POST 请求会让判断失效。REQUEST_METHOD 才是准确的。

一个最小的表单处理脚本

保存为 form.php,用 php -S localhost:8000 访问 http://localhost:8000/form.php:

<?php
declare(strict_types=1);

$errors = [];
$values = ["name" => "", "email" => ""];

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $values["name"] = trim($_POST["name"] ?? "");
    $values["email"] = trim($_POST["email"] ?? "");

    if (mb_strlen($values["name"], "UTF-8") < 2) {
        $errors["name"] = "请填写至少 2 个字符的名称";
    }

    if (!filter_var($values["email"], FILTER_VALIDATE_EMAIL)) {
        $errors["email"] = "邮箱格式不正确";
    }

    if ($errors === []) {
        // 校验通过:写入数据库或做其他处理,然后重定向
        header("Location: /thanks.php", true, 303);
        exit;
    }
}
?>
<!doctype html>
<meta charset="utf-8">
<title>注册</title>

<?php if ($errors !== []): ?>
    <p>请修正以下问题后重新提交。</p>
<?php endif; ?>

<form method="post" action="/form.php">
    <label>名称
        <input name="name" value="<?= htmlspecialchars($values["name"], ENT_QUOTES, "UTF-8") ?>">
    </label>
    <?php if (isset($errors["name"])): ?>
        <em><?= htmlspecialchars($errors["name"], ENT_QUOTES, "UTF-8") ?></em>
    <?php endif; ?>

    <label>邮箱
        <input name="email" type="email" value="<?= htmlspecialchars($values["email"], ENT_QUOTES, "UTF-8") ?>">
    </label>
    <?php if (isset($errors["email"])): ?>
        <em><?= htmlspecialchars($errors["email"], ENT_QUOTES, "UTF-8") ?></em>
    <?php endif; ?>

    <button type="submit">提交</button>
</form>

几个关键点:

  1. 成功后重定向(Post/Redirect/Get 模式):避免用户刷新浏览器导致重复提交。状态码用 303,header() 后必须 exit。
  2. 校验失败时不重定向,直接重新渲染表单,并把用户已填的值回填。
  3. 所有输出到 HTML 的值都要转义。

输出转义:防 XSS

echo htmlspecialchars($userInput, ENT_QUOTES, "UTF-8");

htmlspecialchars 把 <、>、&、"、' 转成 HTML 实体,使浏览器把它们当文本而不是标签。三个参数都要给:

  • ENT_QUOTES:同时转义单双引号(只转义双引号时,单引号包裹的属性仍可被突破)。
  • "UTF-8":明确字符集,避免编码猜测错误。

不转义的后果:

// 用户在名称里输入: <script>alert(1)</script>
echo $values["name"];   // 脚本会被浏览器执行(XSS)

输出时转义,而不是存入时转义。数据库里保存原始内容,在渲染到 HTML 时才转义,这样同一份数据用于 JSON、CSV 或纯文本时不会被二次污染。

输入过滤:filter_var

$email = filter_var($_POST["email"] ?? null, FILTER_VALIDATE_EMAIL);
$age = filter_var($_POST["age"] ?? null, FILTER_VALIDATE_INT, [
    "options" => ["min_range" => 0, "max_range" => 150],
]);
$url = filter_var($_POST["site"] ?? null, FILTER_VALIDATE_URL);

校验失败返回 false,返回 0 是合法值,所以判断必须写 === false:

if ($age === false) {
    $errors["age"] = "年龄必须是不大于 150 的整数";
}

也可以直接从超全局变量里取并过滤:

$page = filter_input(INPUT_GET, "page", FILTER_VALIDATE_INT, [
    "options" => ["default" => 1, "min_range" => 1],
]);

filter_input 的好处是键不存在时也不会产生警告。

常见错误与注意事项

  1. 直接 echo $_POST["name"]:XSS 漏洞,必须 htmlspecialchars。
  2. 用 if ($_POST) 判断是否为 POST 请求:空表单会误判,用 $_SERVER["REQUEST_METHOD"]。
  3. 提交成功后不重定向:刷新页面会重复提交。
  4. header() 后忘记 exit:后续代码仍会执行,可能泄露内容或覆盖跳转。
  5. filter_var 结果用 == false 判断:0、"0" 会被误判为失败,用 === false。
  6. 只做前端校验:浏览器校验可绕过,服务端校验才是安全边界。

小结

  • $_GET 用于查询,$_POST 用于提交;用 REQUEST_METHOD 判断方法。
  • 取值一律 ?? 兜底,避免未定义键警告。
  • 输出转义用 htmlspecialchars($v, ENT_QUOTES, "UTF-8"),在渲染时做,不在入库时做。
  • 校验用 filter_var,判断失败用 === false。
  • 提交成功后 303 重定向 + exit,防止重复提交。

下一步