PHP: 表单验证
上节课你学会了接收表单数据。这节课是更关键的一步——验证。永远不要信任用户的输入。用户可能手滑、可能是恶意攻击、可能压根没填。表单验证是你的第一道防火墙。
1. 验证架构
所有验证在服务器端做(PHP),前端验证(HTML5/JS)只是锦上添花:
TEXT
📖 仅展示
用户浏览器(JS 验证,可被绕过)
↓
PHP 服务器(必须验证!不可跳过)
↓
存入数据库
PHP
<?php
$errors = [];
// 1. 获取并清理输入
$name = trim($_POST['name'] ?? '');
$age = $_POST['age'] ?? '';
$email = trim($_POST['email'] ?? '');
// 2. 验证规则
if ($name === '') {
$errors['name'] = '姓名不能为空';
} elseif (strlen($name) > 50) {
$errors['name'] = '姓名不能超过50个字符';
}
if ($age === '') {
$errors['age'] = '年龄不能为空';
} elseif (!is_numeric($age)) {
$errors['age'] = '年龄必须是数字';
} elseif ((int)$age < 1 || (int)$age > 150) {
$errors['age'] = '年龄必须在1-150之间';
}
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = '邮箱格式不正确';
} // email 不是必填项,但填了就要正确
// 3. 判断结果
if (empty($errors)) {
echo "验证通过!";
} else {
foreach ($errors as $field => $msg) {
echo "<p style='color:red'>{$msg}</p>";
}
}
?>
💡 提示: 用关联数组
$errors 来收集错误信息。$errors['field_name'] 的结构便于在对应字段旁边显示错误提示。
2. filter_var() 验证
PHP 内置的 filter_var() 是最简洁的验证方式:
PHP
<?php
// 邮箱验证
$email = "test@example.com";
var_dump(filter_var($email, FILTER_VALIDATE_EMAIL));
// "test@example.com"(验证通过返回原值)
$badEmail = "not-an-email";
var_dump(filter_var($badEmail, FILTER_VALIDATE_EMAIL));
// bool(false)(验证失败)
// URL 验证
$url = "https://www.example.com";
var_dump(filter_var($url, FILTER_VALIDATE_URL));
// IP 验证
$ip = "192.168.1.1";
var_dump(filter_var($ip, FILTER_VALIDATE_IP));
// 整数范围验证
$age = 25;
var_dump(filter_var($age, FILTER_VALIDATE_INT, [
"options" => ["min_range" => 1, "max_range" => 150]
]));
?>
▶ 示例:用 filter_var 验证整个表单
PHP
<?php
$errors = [];
// 验证邮箱
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
if ($email === false) {
$errors['email'] = '请输入有效的邮箱地址';
}
// 验证 URL
$website = filter_var($_POST['website'] ?? '', FILTER_VALIDATE_URL);
if ($website === false && ($_POST['website'] ?? '') !== '') {
$errors['website'] = '请输入有效的网址';
}
// 验证整数范围
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT, [
"options" => ["min_range" => 1, "max_range" => 120]
]);
if ($age === false) {
$errors['age'] = '年龄必须在1-120之间';
}
?>
3. 正则验证(preg_match)
当标准验证不够用时,正则表达式上场:
PHP
<?php
// 手机号验证(中国11位)
$phone = "13800138000";
if (preg_match('/^1[3-9]\d{9}$/', $phone)) {
echo "手机号格式正确";
}
// 用户名验证(字母数字下划线,3-20位)
$username = "user_2024";
if (preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
echo "用户名格式正确";
}
// 强密码验证(至少8位,含大小写字母和数字)
$password = "MyPass123";
if (preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/', $password)) {
echo "密码强度合格";
}
?>
4. 安全防护
(1) htmlspecialchars() — 防 XSS 攻击
用户输入中的 <script> 标签如果不处理,会直接在浏览器执行:
PHP
<?php
$userInput = '<script>alert("被黑了!")</script>';
// ❌ 危险:直接输出
echo $userInput; // 脚本会执行!
// ✅ 安全:转义特殊字符
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// 输出:<script>alert("被黑了!")</script>
// 浏览器显示原文,但不执行脚本
?>
(2) strip_tags() — 去除 HTML 标签
PHP
<?php
$content = "<p>这是<b>重要</b>内容</p>";
echo strip_tags($content); // 这是重要内容
// 允许保留某些标签(第二个参数白名单)
echo strip_tags($content, '<b><i>'); // 这是<b>重要</b>内容
?>
(3) trim() — 去除头尾空白
PHP
<?php
$input = " 小明 ";
echo trim($input); // 小明
?>
▶ 示例:完整的输入清理函数
PHP
<?php
/**
* 清理用户文本输入
*/
function cleanInput(string $data): string {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
$name = cleanInput($_POST['name'] ?? '');
$comment = cleanInput($_POST['comment'] ?? '');
?>
5. 完整验证实战
▶ 示例:注册表单完整验证
PHP
📖 仅展示
<?php
$errors = [];
$username = $email = $password = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
// 用户名验证
if ($username === '') {
$errors['username'] = '用户名不能为空';
} elseif (strlen($username) < 3 || strlen($username) > 20) {
$errors['username'] = '用户名需要3-20个字符';
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
$errors['username'] = '用户名只能包含字母、数字、下划线';
}
// 邮箱验证
$emailClean = filter_var($email, FILTER_VALIDATE_EMAIL);
if ($email === '') {
$errors['email'] = '邮箱不能为空';
} elseif ($emailClean === false) {
$errors['email'] = '邮箱格式不正确';
}
// 密码验证
if ($password === '') {
$errors['password'] = '密码不能为空';
} elseif (strlen($password) < 6) {
$errors['password'] = '密码至少需要6个字符';
}
if (empty($errors)) {
$usernameSafe = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
echo "<h3 style='color:green'>注册成功!欢迎,{$usernameSafe}!</h3>";
$username = $email = $password = '';
}
}
?>
<form method="POST" action="">
<div>
<label>用户名:</label>
<input type="text" name="username"
value="<?= htmlspecialchars($username) ?>">
<span style="color:red"><?= $errors['username'] ?? '' ?></span>
</div>
<div>
<label>邮箱:</label>
<input type="email" name="email"
value="<?= htmlspecialchars($email) ?>">
<span style="color:red"><?= $errors['email'] ?? '' ?></span>
</div>
<div>
<label>密码:</label>
<input type="password" name="password">
<span style="color:red"><?= $errors['password'] ?? '' ?></span>
</div>
<button type="submit">注册</button>
</form>
❓ 常见问题
Q 前端已经有 required 和 pattern 验证了,还需要 PHP 验证吗?
A 必须需要。前端验证可以被轻易绕过(禁用 JS、用 curl 模拟请求)。PHP 端验证是不可跳过的最后防线。
Q
htmlspecialchars 和 strip_tags 什么时候用哪个?A 保留显示用户输入的内容用
htmlspecialchars(转义但不删除 HTML 标签,保留原外观)。完全去除 HTML 标签用 strip_tags。大多数场景用 htmlspecialchars 就够。Q 正则表达式看起来很恐怖,我要全记住吗?
A 不用。常用正则(邮箱、手机号、URL)有现成的,记住
preg_match() 的用法和几个基础符号(^开头、$结尾、\d数字、[] 字符集、{}数量)就够了。📖 小节
- 验证顺序:必填检查 → 类型检查 → 范围检查 → 格式检查
filter_var($val, FILTER_VALIDATE_EMAIL/URL/INT)最简洁的验证preg_match('/正则/', $val)复杂格式验证htmlspecialchars()防 XSS(输出到 HTML 前转义)strip_tags()彻底去标签,trim()去两端空白- 前端验证是体验层,PHP 验证是安全底线
📝 作业
- 写一个用户资料编辑表单,包含昵称、年龄、邮箱、个人主页URL,对每个字段做对应的验证。
- 写一个密码修改表单,要求:原密码不为空、新密码至少8位且含数字和字母、确认密码与新密码一致。
- 测试 XSS 漏洞:创建一个留言板,故意不写
htmlspecialchars,尝试在留言中输入<script>alert(1)</script>,观察结果。然后加上htmlspecialchars再测试。