PHP 表单处理与数据验证:从入门到安全防范
表单处理流程
一个安全的表单处理流程:接收→验证→过滤→处理→响应。任何一个环节都不能省略。
一、接收表单数据
1. 超全局变量
$_GET['username'] // GET请求
$_POST['username'] // POST请求
$_REQUEST['username'] // 同时支持GET和POST
$_FILES['avatar'] // 文件上传
$_SERVER['REQUEST_METHOD'] // 'GET' 或 'POST'
2. 判断请求方式
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
} else {
$username = '';
}
二、基本验证
3. 必填项检查
$errors = [];
if (empty($_POST['username'])) $errors[] = '用户名不能为空';
if (empty($_POST['email'])) $errors[] = '邮箱不能为空';
if (!empty($errors)) { foreach ($errors as $e) echo "$e
"; exit; }
4. 邮箱格式验证
$email = trim($_POST['email']);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = '邮箱格式不正确';
}
5. 长度限制
if (mb_strlen($username) < 3) $errors[] = '用户名至少3个字符';
if (mb_strlen($username) > 20) $errors[] = '用户名最多20个字符';
6. 数字范围
$age = (int)($_POST['age'] ?? 0);
if ($age < 18 || $age > 100) $errors[] = '年龄必须在18-100之间';
三、安全防范
7. XSS 跨站脚本攻击防范
// 危险!直接输出用户输入
echo $_POST['comment'];
// 正确:转义后再输出
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
echo $comment;
8. SQL 注入防范
永远不要把用户输入直接拼进SQL:
// 错误:直接拼SQL(极危险!)
$sql = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "'";
// 正确:使用PDO预处理
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute(['name' => $_POST['name']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
9. CSRF 跨站请求伪造防范
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// 表单:echo '';
// 验证:if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) { die('CSRF验证失败'); }10. 密码安全存储
// 哈希存储(永远不要存明文!)
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 验证
if (password_verify($_POST['password'], $hash)) {
// 登录成功
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 更新数据库
}
}
四、文件上传处理
11. 安全文件上传
$file = $_FILES['avatar'];
// 1. 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) { die('文件上传失败'); }
// 2. 检查文件大小(最大2MB)
if ($file['size'] > 2 * 1024 * 1024) { die('文件超过2MB限制'); }
// 3. 检查MIME类型(不要信任扩展名)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mimeType, $allowed)) { die('只允许图片'); }
// 4. 生成随机文件名
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = bin2hex(random_bytes(16)) . '.' . $ext;
// 5. 移动文件
move_uploaded_file($file['tmp_name'], __DIR__ . '/uploads/' . $newName);
五、完整验证类示例
class FormValidator
{
private array $errors = [];
private array $data = [];
public function __construct(private array $rules) {}
public function validate(array $input): bool
{
foreach ($this->rules as $field => $ruleSet) {
$value = $input[$field] ?? null;
$rules = explode('|', $ruleSet);
foreach ($rules as $rule) {
[$name, $param] = array_pad(explode(':', $rule, 2), 2, null);
$this->applyRule($field, $value, $name, $param);
}
if ($value !== null) {
$this->data[$field] = is_string($value) ? trim($value) : $value;
}
}
return empty($this->errors);
}
private function applyRule(string $f, mixed $v, string $r, ?string $p): void
{
switch ($r) {
case 'required':
if (empty($v)) $this->errors[$f][] = "{$f}不能为空"; break;
case 'email':
if ($v && !filter_var($v, FILTER_VALIDATE_EMAIL))
$this->errors[$f][] = "{$f}邮箱格式不正确"; break;
case 'min':
if (mb_strlen($v) < (int)$p)
$this->errors[$f][] = "{$f}至少{$p}个字符"; break;
case 'max':
if (mb_strlen($v) > (int)$p)
$this->errors[$f][] = "{$f}最多{$p}个字符"; break;
}
}
public function getErrors(): array { return $this->errors; }
public function getData(): array { return $this->data; }
}
// 使用
$v = new FormValidator([
'username' => 'required|min:3|max:20',
'email' => 'required|email',
'password' => 'required|min:6',
]);
if ($v->validate($_POST)) {
$data = $v->getData();
// 入库...
} else {
print_r($v->getErrors());
}
六、常用辅助函数
$value = trim($_POST['value']); // 去首尾空格
$id = (int)($_GET['id'] ?? 0); // 整数化
$output = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); // 防XSS
$intro = mb_substr($_POST['intro'], 0, 200); // 限制长度
$content = strip_tags($_POST['content']); // 去除HTML标签总结
- 永远不相信用户输入——所有数据都要验证
- 输出时转义(htmlspecialchars)比输入时过滤更好
- SQL用预处理语句,彻底杜绝注入
- 密码用password_hash(),不要手写加密
- CSRF Token是表单安全的标配
- 文件上传要验证MIME类型、限制大小、随机命名