PHP 表单与请求处理
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
处理用户输入是 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>
几个关键点:
- 成功后重定向(Post/Redirect/Get 模式):避免用户刷新浏览器导致重复提交。状态码用
303,header()后必须exit。 - 校验失败时不重定向,直接重新渲染表单,并把用户已填的值回填。
- 所有输出到 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 的好处是键不存在时也不会产生警告。
常见错误与注意事项
- 直接
echo $_POST["name"]:XSS 漏洞,必须htmlspecialchars。 - 用
if ($_POST)判断是否为 POST 请求:空表单会误判,用$_SERVER["REQUEST_METHOD"]。 - 提交成功后不重定向:刷新页面会重复提交。
header()后忘记exit:后续代码仍会执行,可能泄露内容或覆盖跳转。filter_var结果用== false判断:0、"0"会被误判为失败,用=== false。- 只做前端校验:浏览器校验可绕过,服务端校验才是安全边界。
小结
$_GET用于查询,$_POST用于提交;用REQUEST_METHOD判断方法。- 取值一律
??兜底,避免未定义键警告。 - 输出转义用
htmlspecialchars($v, ENT_QUOTES, "UTF-8"),在渲染时做,不在入库时做。 - 校验用
filter_var,判断失败用=== false。 - 提交成功后 303 重定向 +
exit,防止重复提交。