PHP表单处理与验证完全指南
Orion K Lv6

PHP表单处理与验证完全指南

表单处理是Web开发中最常见的任务之一。作为PHP开发者,我们需要掌握如何安全地接收、验证和处理用户输入的数据。本文将分享一些实用的表单处理技巧和最佳实践。

HTML表单基础

1. 基本表单处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<?php
// 基本表单处理示例
echo "=== 基本表单处理 ===\n";

// 模拟POST数据
$_POST = [
'username' => 'testuser',
'email' => 'test@example.com',
'age' => '25',
'gender' => 'male',
'interests' => ['reading', 'coding'],
'message' => 'Hello World!'
];

// 检查表单是否提交
if ($_SERVER['REQUEST_METHOD'] === 'POST' || !empty($_POST)) {
echo "表单已提交\n";

// 获取表单数据
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$age = $_POST['age'] ?? '';
$gender = $_POST['gender'] ?? '';
$interests = $_POST['interests'] ?? [];
$message = $_POST['message'] ?? '';

echo "用户名: $username\n";
echo "邮箱: $email\n";
echo "年龄: $age\n";
echo "性别: $gender\n";
echo "兴趣: " . implode(', ', $interests) . "\n";
echo "留言: $message\n";
}

// 表单处理工具类
class FormHandler {
private $data = [];
private $errors = [];
private $rules = [];

public function __construct($data = null) {
$this->data = $data ?? $_POST;
}

// 获取表单字段值
public function get($field, $default = '') {
return $this->data[$field] ?? $default;
}

// 检查字段是否存在
public function has($field) {
return isset($this->data[$field]);
}

// 获取所有数据
public function all() {
return $this->data;
}

// 只获取指定字段
public function only($fields) {
$result = [];
foreach ($fields as $field) {
if ($this->has($field)) {
$result[$field] = $this->get($field);
}
}
return $result;
}

// 排除指定字段
public function except($fields) {
$result = $this->data;
foreach ($fields as $field) {
unset($result[$field]);
}
return $result;
}

// 清理输入数据
public function sanitize($field, $filter = FILTER_SANITIZE_STRING) {
$value = $this->get($field);

if (is_array($value)) {
return array_map(function($item) use ($filter) {
return filter_var($item, $filter);
}, $value);
}

return filter_var($value, $filter);
}

// 批量清理数据
public function sanitizeAll($filters = []) {
$sanitized = [];

foreach ($this->data as $field => $value) {
$filter = $filters[$field] ?? FILTER_SANITIZE_STRING;
$sanitized[$field] = $this->sanitize($field, $filter);
}

return $sanitized;
}

// 转义HTML特殊字符
public function escape($field) {
$value = $this->get($field);

if (is_array($value)) {
return array_map('htmlspecialchars', $value);
}

return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

// 获取文件上传信息
public function file($field) {
return $_FILES[$field] ?? null;
}

// 检查是否有文件上传
public function hasFile($field) {
return isset($_FILES[$field]) && $_FILES[$field]['error'] === UPLOAD_ERR_OK;
}
}

// 使用表单处理器
echo "\n=== 表单处理器示例 ===\n";

$form = new FormHandler();

// 获取字段值
echo "用户名: " . $form->get('username', '未提供') . "\n";
echo "邮箱: " . $form->get('email', '未提供') . "\n";

// 检查字段存在
echo "是否有年龄字段: " . ($form->has('age') ? '是' : '否') . "\n";
echo "是否有电话字段: " . ($form->has('phone') ? '是' : '否') . "\n";

// 只获取指定字段
$userInfo = $form->only(['username', 'email', 'age']);
echo "用户信息: " . json_encode($userInfo, JSON_UNESCAPED_UNICODE) . "\n";

// 排除敏感字段
$publicData = $form->except(['password', 'confirm_password']);
echo "公开数据字段数: " . count($publicData) . "\n";

// 清理数据
echo "清理后的用户名: " . $form->sanitize('username') . "\n";
echo "转义后的留言: " . $form->escape('message') . "\n";
?>

2. 数据验证基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
<?php
// 数据验证示例
echo "=== 数据验证基础 ===\n";

// 基本验证函数
class Validator {
private $data;
private $rules;
private $errors = [];
private $messages = [];

public function __construct($data, $rules, $messages = []) {
$this->data = $data;
$this->rules = $rules;
$this->messages = $messages;
}

// 执行验证
public function validate() {
$this->errors = [];

foreach ($this->rules as $field => $ruleString) {
$rules = explode('|', $ruleString);
$value = $this->data[$field] ?? null;

foreach ($rules as $rule) {
$this->applyRule($field, $value, $rule);
}
}

return empty($this->errors);
}

// 应用单个验证规则
private function applyRule($field, $value, $rule) {
$parts = explode(':', $rule);
$ruleName = $parts[0];
$parameter = $parts[1] ?? null;

switch ($ruleName) {
case 'required':
if (empty($value)) {
$this->addError($field, 'required', "$field 是必填字段");
}
break;

case 'email':
if (!empty($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->addError($field, 'email', "$field 必须是有效的邮箱地址");
}
break;

case 'min':
if (!empty($value) && strlen($value) < $parameter) {
$this->addError($field, 'min', "$field 最少需要 $parameter 个字符");
}
break;

case 'max':
if (!empty($value) && strlen($value) > $parameter) {
$this->addError($field, 'max', "$field 最多允许 $parameter 个字符");
}
break;

case 'numeric':
if (!empty($value) && !is_numeric($value)) {
$this->addError($field, 'numeric', "$field 必须是数字");
}
break;

case 'integer':
if (!empty($value) && !filter_var($value, FILTER_VALIDATE_INT)) {
$this->addError($field, 'integer', "$field 必须是整数");
}
break;

case 'between':
$range = explode(',', $parameter);
$min = $range[0];
$max = $range[1];
$len = strlen($value);
if (!empty($value) && ($len < $min || $len > $max)) {
$this->addError($field, 'between', "$field 长度必须在 $min$max 之间");
}
break;

case 'in':
$options = explode(',', $parameter);
if (!empty($value) && !in_array($value, $options)) {
$this->addError($field, 'in', "$field 必须是以下值之一: " . implode(', ', $options));
}
break;

case 'regex':
if (!empty($value) && !preg_match($parameter, $value)) {
$this->addError($field, 'regex', "$field 格式不正确");
}
break;

case 'confirmed':
$confirmField = $field . '_confirmation';
$confirmValue = $this->data[$confirmField] ?? null;
if ($value !== $confirmValue) {
$this->addError($field, 'confirmed', "$field 确认不匹配");
}
break;

case 'unique':
// 模拟数据库检查
$existingValues = ['admin', 'root', 'test']; // 模拟已存在的值
if (!empty($value) && in_array($value, $existingValues)) {
$this->addError($field, 'unique', "$field 已经存在");
}
break;
}
}

// 添加错误信息
private function addError($field, $rule, $message) {
$customMessage = $this->messages[$field . '.' . $rule] ??
$this->messages[$field] ??
$message;

if (!isset($this->errors[$field])) {
$this->errors[$field] = [];
}

$this->errors[$field][] = $customMessage;
}

// 获取所有错误
public function errors() {
return $this->errors;
}

// 获取第一个错误
public function firstError($field = null) {
if ($field) {
return $this->errors[$field][0] ?? null;
}

foreach ($this->errors as $fieldErrors) {
return $fieldErrors[0];
}

return null;
}

// 检查是否有错误
public function hasErrors($field = null) {
if ($field) {
return isset($this->errors[$field]);
}

return !empty($this->errors);
}

// 获取验证通过的数据
public function validated() {
if ($this->hasErrors()) {
throw new Exception('验证失败,无法获取验证数据');
}

$validated = [];
foreach ($this->rules as $field => $rules) {
if (isset($this->data[$field])) {
$validated[$field] = $this->data[$field];
}
}

return $validated;
}
}

// 使用验证器
echo "=== 验证器示例 ===\n";

// 模拟表单数据
$formData = [
'username' => 'john',
'email' => 'john@example.com',
'password' => '123456',
'password_confirmation' => '123456',
'age' => '25',
'gender' => 'male',
'bio' => 'Hello, I am John!'
];

// 定义验证规则
$rules = [
'username' => 'required|min:3|max:20|unique',
'email' => 'required|email',
'password' => 'required|min:6|confirmed',
'age' => 'required|integer|between:18,100',
'gender' => 'required|in:male,female,other',
'bio' => 'max:500'
];

// 自定义错误消息
$messages = [
'username.required' => '用户名不能为空',
'username.unique' => '用户名已被占用',
'email.required' => '邮箱地址不能为空',
'password.min' => '密码至少需要6位字符'
];

$validator = new Validator($formData, $rules, $messages);

if ($validator->validate()) {
echo "验证通过!\n";
$validatedData = $validator->validated();
echo "验证通过的数据: " . json_encode($validatedData, JSON_UNESCAPED_UNICODE) . "\n";
} else {
echo "验证失败!\n";
$errors = $validator->errors();
foreach ($errors as $field => $fieldErrors) {
echo "$field: " . implode(', ', $fieldErrors) . "\n";
}
}

// 测试验证失败的情况
echo "\n=== 验证失败示例 ===\n";

$invalidData = [
'username' => 'ad', // 太短
'email' => 'invalid-email', // 无效邮箱
'password' => '123', // 太短
'password_confirmation' => '456', // 不匹配
'age' => 'abc', // 非数字
'gender' => 'unknown' // 不在允许值中
];

$validator2 = new Validator($invalidData, $rules, $messages);

if (!$validator2->validate()) {
echo "发现以下验证错误:\n";
foreach ($validator2->errors() as $field => $fieldErrors) {
echo "- $field: " . implode(', ', $fieldErrors) . "\n";
}
}
?>

3. 高级验证功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<?php
// 高级验证功能
echo "=== 高级验证功能 ===\n";

class AdvancedValidator extends Validator {
private $customRules = [];

// 添加自定义验证规则
public function addRule($name, $callback, $message = null) {
$this->customRules[$name] = [
'callback' => $callback,
'message' => $message ?? "$name 验证失败"
];
}

// 重写规则应用方法以支持自定义规则
protected function applyRule($field, $value, $rule) {
$parts = explode(':', $rule);
$ruleName = $parts[0];
$parameter = $parts[1] ?? null;

// 检查是否是自定义规则
if (isset($this->customRules[$ruleName])) {
$customRule = $this->customRules[$ruleName];
$callback = $customRule['callback'];

if (!$callback($value, $parameter, $this->data)) {
$this->addError($field, $ruleName, $customRule['message']);
}
return;
}

// 调用父类方法处理标准规则
parent::applyRule($field, $value, $rule);
}

// 条件验证
public function sometimes($field, $rules, $condition) {
if (is_callable($condition)) {
$shouldValidate = $condition($this->data);
} else {
$shouldValidate = $condition;
}

if ($shouldValidate) {
$this->rules[$field] = $rules;
}

return $this;
}

// 验证数组
public function validateArray($field, $rules) {
$array = $this->data[$field] ?? [];

if (!is_array($array)) {
$this->addError($field, 'array', "$field 必须是数组");
return;
}

foreach ($array as $index => $item) {
$itemValidator = new self([$field => $item], [$field => $rules]);

if (!$itemValidator->validate()) {
$errors = $itemValidator->errors();
foreach ($errors[$field] as $error) {
$this->addError("$field.$index", 'validation', $error);
}
}
}
}

// 嵌套验证
public function validateNested($field, $rules) {
$data = $this->data[$field] ?? [];

if (!is_array($data)) {
$this->addError($field, 'array', "$field 必须是数组");
return;
}

$nestedValidator = new self($data, $rules);

if (!$nestedValidator->validate()) {
$errors = $nestedValidator->errors();
foreach ($errors as $nestedField => $nestedErrors) {
foreach ($nestedErrors as $error) {
$this->addError("$field.$nestedField", 'validation', $error);
}
}
}
}
}

// 创建高级验证器实例
$advancedValidator = new AdvancedValidator([], []);

// 添加自定义验证规则
$advancedValidator->addRule('phone', function($value) {
return preg_match('/^1[3-9]\d{9}$/', $value);
}, '手机号码格式不正确');

$advancedValidator->addRule('idcard', function($value) {
return preg_match('/^\d{17}[\dX]$/', $value);
}, '身份证号码格式不正确');

$advancedValidator->addRule('strong_password', function($value) {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $value);
}, '密码必须包含大小写字母、数字和特殊字符,且至少8位');

// 测试自定义规则
echo "=== 自定义验证规则测试 ===\n";

$testData = [
'phone' => '13812345678',
'idcard' => '12345678901234567X',
'password' => 'StrongPass123!'
];

$customRules = [
'phone' => 'required|phone',
'idcard' => 'required|idcard',
'password' => 'required|strong_password'
];

$customValidator = new AdvancedValidator($testData, $customRules);

// 添加自定义规则
$customValidator->addRule('phone', function($value) {
return preg_match('/^1[3-9]\d{9}$/', $value);
}, '手机号码格式不正确');

$customValidator->addRule('idcard', function($value) {
return preg_match('/^\d{17}[\dX]$/', $value);
}, '身份证号码格式不正确');

$customValidator->addRule('strong_password', function($value) {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $value);
}, '密码必须包含大小写字母、数字和特殊字符,且至少8位');

if ($customValidator->validate()) {
echo "自定义规则验证通过!\n";
} else {
echo "自定义规则验证失败:\n";
foreach ($customValidator->errors() as $field => $errors) {
echo "- $field: " . implode(', ', $errors) . "\n";
}
}

// 条件验证示例
echo "\n=== 条件验证示例 ===\n";

$conditionalData = [
'user_type' => 'premium',
'credit_limit' => '50000'
];

$conditionalValidator = new AdvancedValidator($conditionalData, [
'user_type' => 'required|in:basic,premium,vip'
]);

// 只有当用户类型是premium或vip时才验证信用额度
$conditionalValidator->sometimes('credit_limit', 'required|numeric|min:10000', function($data) {
return in_array($data['user_type'] ?? '', ['premium', 'vip']);
});

if ($conditionalValidator->validate()) {
echo "条件验证通过!\n";
} else {
echo "条件验证失败:\n";
foreach ($conditionalValidator->errors() as $field => $errors) {
echo "- $field: " . implode(', ', $errors) . "\n";
}
}

// 数组验证示例
echo "\n=== 数组验证示例 ===\n";

$arrayData = [
'tags' => ['php', 'web', 'programming'],
'scores' => [85, 92, 78, 96]
];

$arrayValidator = new AdvancedValidator($arrayData, []);

// 验证标签数组中的每个元素
$arrayValidator->validateArray('tags', 'required|min:2|max:20');

// 验证分数数组中的每个元素
$arrayValidator->validateArray('scores', 'required|integer|between:0,100');

if ($arrayValidator->validate()) {
echo "数组验证通过!\n";
} else {
echo "数组验证失败:\n";
foreach ($arrayValidator->errors() as $field => $errors) {
echo "- $field: " . implode(', ', $errors) . "\n";
}
}
?>

文件上传处理

1. 基本文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
<?php
// 文件上传处理
echo "=== 文件上传处理 ===\n";

class FileUploader {
private $uploadDir;
private $allowedTypes;
private $maxSize;
private $errors = [];

public function __construct($uploadDir = 'uploads/', $allowedTypes = [], $maxSize = 2097152) {
$this->uploadDir = rtrim($uploadDir, '/') . '/';
$this->allowedTypes = $allowedTypes ?: ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
$this->maxSize = $maxSize; // 默认2MB

// 创建上传目录
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}

// 上传单个文件
public function upload($fieldName, $customName = null) {
if (!isset($_FILES[$fieldName])) {
$this->errors[] = "文件字段 $fieldName 不存在";
return false;
}

$file = $_FILES[$fieldName];

// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
$this->errors[] = $this->getUploadErrorMessage($file['error']);
return false;
}

// 验证文件
if (!$this->validateFile($file)) {
return false;
}

// 生成文件名
$fileName = $this->generateFileName($file, $customName);
$filePath = $this->uploadDir . $fileName;

// 移动文件
if (move_uploaded_file($file['tmp_name'], $filePath)) {
return [
'success' => true,
'filename' => $fileName,
'path' => $filePath,
'size' => $file['size'],
'type' => $file['type'],
'original_name' => $file['name']
];
} else {
$this->errors[] = '文件上传失败';
return false;
}
}

// 上传多个文件
public function uploadMultiple($fieldName) {
if (!isset($_FILES[$fieldName])) {
$this->errors[] = "文件字段 $fieldName 不存在";
return false;
}

$files = $_FILES[$fieldName];
$results = [];

// 处理多文件上传的数组结构
if (is_array($files['name'])) {
for ($i = 0; $i < count($files['name']); $i++) {
$file = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];

// 模拟单文件上传
$_FILES['temp_single'] = $file;
$result = $this->upload('temp_single');

if ($result) {
$results[] = $result;
}
}

unset($_FILES['temp_single']);
}

return $results;
}

// 验证文件
private function validateFile($file) {
// 检查文件大小
if ($file['size'] > $this->maxSize) {
$maxSizeMB = round($this->maxSize / 1024 / 1024, 2);
$this->errors[] = "文件大小超过限制 ({$maxSizeMB}MB)";
return false;
}

// 检查文件类型
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $this->allowedTypes)) {
$this->errors[] = "不允许的文件类型: $extension";
return false;
}

// 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

$allowedMimes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];

if (isset($allowedMimes[$extension]) && $mimeType !== $allowedMimes[$extension]) {
$this->errors[] = "文件MIME类型不匹配";
return false;
}

return true;
}

// 生成文件名
private function generateFileName($file, $customName = null) {
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));

if ($customName) {
return $customName . '.' . $extension;
}

// 生成唯一文件名
return uniqid() . '_' . time() . '.' . $extension;
}

// 获取上传错误信息
private function getUploadErrorMessage($errorCode) {
$messages = [
UPLOAD_ERR_INI_SIZE => '文件大小超过php.ini中upload_max_filesize的限制',
UPLOAD_ERR_FORM_SIZE => '文件大小超过表单中MAX_FILE_SIZE的限制',
UPLOAD_ERR_PARTIAL => '文件只有部分被上传',
UPLOAD_ERR_NO_FILE => '没有文件被上传',
UPLOAD_ERR_NO_TMP_DIR => '找不到临时文件夹',
UPLOAD_ERR_CANT_WRITE => '文件写入失败',
UPLOAD_ERR_EXTENSION => '文件上传被扩展程序阻止'
];

return $messages[$errorCode] ?? '未知上传错误';
}

// 获取错误信息
public function getErrors() {
return $this->errors;
}

// 清除错误信息
public function clearErrors() {
$this->errors = [];
}

// 删除文件
public function delete($fileName) {
$filePath = $this->uploadDir . $fileName;

if (file_exists($filePath)) {
return unlink($filePath);
}

return false;
}

// 获取文件信息
public function getFileInfo($fileName) {
$filePath = $this->uploadDir . $fileName;

if (!file_exists($filePath)) {
return false;
}

return [
'name' => $fileName,
'path' => $filePath,
'size' => filesize($filePath),
'type' => mime_content_type($filePath),
'modified' => filemtime($filePath)
];
}
}

// 使用文件上传器
echo "=== 文件上传器示例 ===\n";

// 模拟文件上传数据
$_FILES['avatar'] = [
'name' => 'profile.jpg',
'type' => 'image/jpeg',
'tmp_name' => '/tmp/php_upload_temp',
'error' => UPLOAD_ERR_OK,
'size' => 1024000
];

// 创建临时文件模拟上传
$tempFile = '/tmp/php_upload_temp';
file_put_contents($tempFile, 'fake image content');

$uploader = new FileUploader('uploads/', ['jpg', 'jpeg', 'png', 'gif'], 2097152);

// 模拟上传(在实际环境中会自动处理)
echo "文件上传器配置完成\n";
echo "允许的文件类型: " . implode(', ', ['jpg', 'jpeg', 'png', 'gif']) . "\n";
echo "最大文件大小: 2MB\n";

// 清理临时文件
if (file_exists($tempFile)) {
unlink($tempFile);
}
?>

2. 图片处理和缩略图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<?php
// 图片处理和缩略图生成
echo "=== 图片处理功能 ===\n";

class ImageProcessor {
private $allowedTypes = ['jpg', 'jpeg', 'png', 'gif'];
private $quality = 85;

// 生成缩略图
public function createThumbnail($sourcePath, $destPath, $width, $height, $crop = false) {
if (!file_exists($sourcePath)) {
throw new Exception('源文件不存在');
}

$imageInfo = getimagesize($sourcePath);
if (!$imageInfo) {
throw new Exception('无效的图片文件');
}

list($originalWidth, $originalHeight, $type) = $imageInfo;

// 创建源图像资源
$sourceImage = $this->createImageFromFile($sourcePath, $type);

if ($crop) {
// 裁剪模式:保持比例,裁剪多余部分
$sourceRatio = $originalWidth / $originalHeight;
$targetRatio = $width / $height;

if ($sourceRatio > $targetRatio) {
// 源图像更宽,裁剪宽度
$newWidth = $originalHeight * $targetRatio;
$newHeight = $originalHeight;
$srcX = ($originalWidth - $newWidth) / 2;
$srcY = 0;
} else {
// 源图像更高,裁剪高度
$newWidth = $originalWidth;
$newHeight = $originalWidth / $targetRatio;
$srcX = 0;
$srcY = ($originalHeight - $newHeight) / 2;
}
} else {
// 缩放模式:保持比例,不裁剪
$ratio = min($width / $originalWidth, $height / $originalHeight);
$width = $originalWidth * $ratio;
$height = $originalHeight * $ratio;

$srcX = 0;
$srcY = 0;
$newWidth = $originalWidth;
$newHeight = $originalHeight;
}

// 创建目标图像
$targetImage = imagecreatetruecolor($width, $height);

// 处理透明背景
if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) {
imagealphablending($targetImage, false);
imagesavealpha($targetImage, true);
$transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
imagefill($targetImage, 0, 0, $transparent);
}

// 重新采样
imagecopyresampled(
$targetImage, $sourceImage,
0, 0, $srcX, $srcY,
$width, $height, $newWidth, $newHeight
);

// 保存图像
$this->saveImage($targetImage, $destPath, $type);

// 清理资源
imagedestroy($sourceImage);
imagedestroy($targetImage);

return true;
}

// 从文件创建图像资源
private function createImageFromFile($path, $type) {
switch ($type) {
case IMAGETYPE_JPEG:
return imagecreatefromjpeg($path);
case IMAGETYPE_PNG:
return imagecreatefrompng($path);
case IMAGETYPE_GIF:
return imagecreatefromgif($path);
default:
throw new Exception('不支持的图像类型');
}
}

// 保存图像
private function saveImage($image, $path, $type) {
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}

switch ($type) {
case IMAGETYPE_JPEG:
return imagejpeg($image, $path, $this->quality);
case IMAGETYPE_PNG:
return imagepng($image, $path);
case IMAGETYPE_GIF:
return imagegif($image, $path);
default:
throw new Exception('不支持的图像类型');
}
}

// 添加水印
public function addWatermark($sourcePath, $watermarkPath, $destPath, $position = 'bottom-right', $opacity = 50) {
$sourceImage = imagecreatefromjpeg($sourcePath);
$watermarkImage = imagecreatefrompng($watermarkPath);

$sourceWidth = imagesx($sourceImage);
$sourceHeight = imagesy($sourceImage);
$watermarkWidth = imagesx($watermarkImage);
$watermarkHeight = imagesy($watermarkImage);

// 计算水印位置
switch ($position) {
case 'top-left':
$destX = 10;
$destY = 10;
break;
case 'top-right':
$destX = $sourceWidth - $watermarkWidth - 10;
$destY = 10;
break;
case 'bottom-left':
$destX = 10;
$destY = $sourceHeight - $watermarkHeight - 10;
break;
case 'bottom-right':
default:
$destX = $sourceWidth - $watermarkWidth - 10;
$destY = $sourceHeight - $watermarkHeight - 10;
break;
case 'center':
$destX = ($sourceWidth - $watermarkWidth) / 2;
$destY = ($sourceHeight - $watermarkHeight) / 2;
break;
}

// 添加水印
imagecopymerge($sourceImage, $watermarkImage, $destX, $destY, 0, 0, $watermarkWidth, $watermarkHeight, $opacity);

// 保存图像
imagejpeg($sourceImage, $destPath, $this->quality);

// 清理资源
imagedestroy($sourceImage);
imagedestroy($watermarkImage);

return true;
}

// 图像格式转换
public function convertFormat($sourcePath, $destPath, $targetFormat) {
$imageInfo = getimagesize($sourcePath);
if (!$imageInfo) {
throw new Exception('无效的图片文件');
}

list($width, $height, $sourceType) = $imageInfo;

$sourceImage = $this->createImageFromFile($sourcePath, $sourceType);

// 确定目标类型
$targetType = $this->getImageTypeFromExtension($targetFormat);

// 保存为新格式
$this->saveImage($sourceImage, $destPath, $targetType);

imagedestroy($sourceImage);

return true;
}

// 从扩展名获取图像类型
private function getImageTypeFromExtension($extension) {
$extension = strtolower($extension);

switch ($extension) {
case 'jpg':
case 'jpeg':
return IMAGETYPE_JPEG;
case 'png':
return IMAGETYPE_PNG;
case 'gif':
return IMAGETYPE_GIF;
default:
throw new Exception('不支持的目标格式');
}
}
}

// 使用图片处理器
echo "=== 图片处理器示例 ===\n";

$processor = new ImageProcessor();

echo "图片处理器功能:\n";
echo "- 生成缩略图(缩放/裁剪模式)\n";
echo "- 添加水印\n";
echo "- 格式转换\n";
echo "- 透明背景处理\n";

// 模拟处理结果
echo "\n处理示例:\n";
echo "原图: 1920x1080 -> 缩略图: 300x200 (裁剪模式)\n";
echo "水印位置: 右下角,透明度: 50%\n";
echo "格式转换: JPG -> PNG\n";
?>

安全防护

1. CSRF防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php
// CSRF防护
echo "=== CSRF防护 ===\n";

class CSRFProtection {
private $tokenName = '_csrf_token';
private $sessionKey = 'csrf_tokens';

public function __construct() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}

if (!isset($_SESSION[$this->sessionKey])) {
$_SESSION[$this->sessionKey] = [];
}
}

// 生成CSRF令牌
public function generateToken($formName = 'default') {
$token = bin2hex(random_bytes(32));
$_SESSION[$this->sessionKey][$formName] = [
'token' => $token,
'time' => time()
];

return $token;
}

// 验证CSRF令牌
public function validateToken($token, $formName = 'default') {
if (!isset($_SESSION[$this->sessionKey][$formName])) {
return false;
}

$storedData = $_SESSION[$this->sessionKey][$formName];

// 检查令牌是否匹配
if (!hash_equals($storedData['token'], $token)) {
return false;
}

// 检查令牌是否过期(1小时)
if (time() - $storedData['time'] > 3600) {
unset($_SESSION[$this->sessionKey][$formName]);
return false;
}

return true;
}

// 生成隐藏字段HTML
public function getHiddenField($formName = 'default') {
$token = $this->generateToken($formName);
return "<input type=\"hidden\" name=\"{$this->tokenName}\" value=\"$token\">";
}

// 验证请求中的令牌
public function validateRequest($formName = 'default') {
$token = $_POST[$this->tokenName] ?? $_GET[$this->tokenName] ?? '';

if (empty($token)) {
return false;
}

$isValid = $this->validateToken($token, $formName);

// 验证后删除令牌(一次性使用)
if ($isValid) {
unset($_SESSION[$this->sessionKey][$formName]);
}

return $isValid;
}

// 清理过期令牌
public function cleanExpiredTokens() {
$currentTime = time();

foreach ($_SESSION[$this->sessionKey] as $formName => $data) {
if ($currentTime - $data['time'] > 3600) {
unset($_SESSION[$this->sessionKey][$formName]);
}
}
}
}

// 使用CSRF防护
echo "=== CSRF防护示例 ===\n";

$csrf = new CSRFProtection();

// 生成表单令牌
$token = $csrf->generateToken('contact_form');
echo "生成的CSRF令牌: " . substr($token, 0, 16) . "...\n";

// 模拟表单提交
$_POST['_csrf_token'] = $token;

// 验证令牌
if ($csrf->validateRequest('contact_form')) {
echo "CSRF验证通过,表单处理安全\n";
} else {
echo "CSRF验证失败,可能存在攻击\n";
}

// 生成HTML隐藏字段
$hiddenField = $csrf->getHiddenField('login_form');
echo "HTML隐藏字段: " . htmlspecialchars($hiddenField) . "\n";
?>

2. XSS防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
// XSS防护
echo "=== XSS防护 ===\n";

class XSSProtection {

// 基本HTML转义
public static function escape($string, $encoding = 'UTF-8') {
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $encoding);
}

// 清理HTML标签
public static function stripTags($string, $allowedTags = '') {
return strip_tags($string, $allowedTags);
}

// 过滤危险的HTML属性
public static function filterAttributes($html) {
// 移除危险的事件属性
$dangerousAttributes = [
'onload', 'onerror', 'onclick', 'onmouseover', 'onmouseout',
'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset',
'onselect', 'onkeydown', 'onkeypress', 'onkeyup'
];

foreach ($dangerousAttributes as $attr) {
$html = preg_replace('/' . $attr . '\s*=\s*["\'][^"\']*["\']/i', '', $html);
}

// 移除javascript:协议
$html = preg_replace('/javascript\s*:/i', '', $html);

return $html;
}

// 安全的富文本过滤
public static function filterRichText($html) {
// 允许的标签
$allowedTags = '<p><br><strong><em><u><h1><h2><h3><h4><h5><h6><ul><ol><li><a><img>';

// 首先移除不允许的标签
$html = strip_tags($html, $allowedTags);

// 过滤危险属性
$html = self::filterAttributes($html);

// 验证链接
$html = preg_replace_callback('/<a\s+[^>]*href\s*=\s*["\']([^"\']*)["\'][^>]*>/i', function($matches) {
$url = $matches[1];

// 只允许http、https和mailto协议
if (preg_match('/^(https?:\/\/|mailto:)/i', $url)) {
return $matches[0];
}

return '<a>';
}, $html);

return $html;
}

// URL编码
public static function encodeUrl($url) {
return urlencode($url);
}

// JavaScript字符串转义
public static function escapeJs($string) {
return json_encode($string, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
}

// CSS值转义
public static function escapeCss($string) {
return preg_replace('/[^a-zA-Z0-9\-_]/', '', $string);
}

// 检测XSS攻击模式
public static function detectXSS($input) {
$xssPatterns = [
'/<script[^>]*>.*?<\/script>/is',
'/javascript\s*:/i',
'/on\w+\s*=/i',
'/<iframe[^>]*>.*?<\/iframe>/is',
'/<object[^>]*>.*?<\/object>/is',
'/<embed[^>]*>/i',
'/expression\s*\(/i',
'/vbscript\s*:/i'
];

foreach ($xssPatterns as $pattern) {
if (preg_match($pattern, $input)) {
return true;
}
}

return false;
}
}

// 使用XSS防护
echo "=== XSS防护示例 ===\n";

// 测试数据
$userInput = '<script>alert("XSS")</script><p>正常内容</p>';
$maliciousLink = '<a href="javascript:alert(\'XSS\')">点击</a>';
$richText = '<p>这是<strong>粗体</strong>文本</p><script>alert("恶意代码")</script>';

echo "原始输入: " . $userInput . "\n";
echo "转义后: " . XSSProtection::escape($userInput) . "\n";
echo "移除标签: " . XSSProtection::stripTags($userInput) . "\n";

echo "\n恶意链接: " . $maliciousLink . "\n";
echo "过滤后: " . XSSProtection::filterRichText($maliciousLink) . "\n";

echo "\n富文本: " . $richText . "\n";
echo "安全过滤: " . XSSProtection::filterRichText($richText) . "\n";

// XSS检测
echo "\nXSS检测结果:\n";
echo "输入1检测: " . (XSSProtection::detectXSS($userInput) ? '发现XSS' : '安全') . "\n";
echo "输入2检测: " . (XSSProtection::detectXSS('正常文本') ? '发现XSS' : '安全') . "\n";

// JavaScript转义
$jsString = "用户输入: 'Hello \"World\"'";
echo "\nJS转义: " . XSSProtection::escapeJs($jsString) . "\n";
?>

总结

通过本文的学习,我们掌握了PHP表单处理的核心技能:

关键要点

  1. 表单数据处理: 学会了安全地接收和处理用户输入
  2. 数据验证: 实现了完整的验证系统,包括自定义规则和条件验证
  3. 文件上传: 掌握了文件上传的安全处理和图片处理技巧
  4. 安全防护: 了解了CSRF和XSS攻击的防护方法

最佳实践

  • 始终验证和清理用户输入
  • 使用白名单而不是黑名单进行验证
  • 实施CSRF防护保护表单安全
  • 对输出进行适当的转义防止XSS
  • 限制文件上传的类型和大小
  • 使用参数化查询防止SQL注入

安全建议

  • 永远不要信任用户输入
  • 在服务器端进行所有重要验证
  • 使用HTTPS传输敏感数据
  • 定期更新安全防护措施
  • 记录和监控异常活动

掌握这些表单处理技巧,将帮助你构建更安全、更可靠的Web应用程序。记住,安全是一个持续的过程,需要不断学习和改进。

本站由 提供部署服务