PHP 文件上传
PHP 文件上传中文讲解
什么是 PHP 文件上传?
PHP 文件上传是指通过 HTML 表单允许用户上传文件(如图片、文档等)到服务器,并使用 PHP 脚本处理这些文件。PHP 通过超全局变量 $_FILES
获取上传文件的信息,适用于图片上传、文档管理、用户头像设置等场景。文件上传需要特别注意安全性和验证,以防止恶意文件或攻击。
为什么使用 PHP 文件上传?
- 用户交互:允许用户上传文件,如头像、附件或媒体文件。
- 数据存储:将文件保存到服务器,用于后续访问或处理。
- 动态应用:支持文件管理功能,如内容管理系统(CMS)或社交平台。
- 典型场景:用户注册时的头像上传、博客附件上传、文件分享系统。
核心概念
- HTML 表单:使用
<form>
标签,设置method="post"
和enctype="multipart/form-data"
。 - $_FILES 数组:包含上传文件的元数据,如文件名、类型、大小、临时路径等。
- 安全验证:检查文件类型、大小、扩展名,防止恶意文件上传。
- 文件移动:使用
move_uploaded_file()
将临时文件移到目标目录。
$_FILES 数组结构
当用户上传文件时,PHP 将信息存储在 $_FILES
数组中。例如,表单字段 <input type="file" name="upload">
:
$_FILES['upload']['name']
:原始文件名(如image.jpg
)。$_FILES['upload']['type']
:MIME 类型(如image/jpeg
)。$_FILES['upload']['size']
:文件大小(字节)。$_FILES['upload']['tmp_name']
:临时文件路径(如/tmp/php123
)。$_FILES['upload']['error']
:错误代码(如UPLOAD_ERR_OK
表示成功)。
常见错误代码
UPLOAD_ERR_OK
(0):上传成功。UPLOAD_ERR_INI_SIZE
(1):文件大小超过php.ini
的upload_max_filesize
。UPLOAD_ERR_FORM_SIZE
(2):文件大小超过表单的MAX_FILE_SIZE
。UPLOAD_ERR_NO_FILE
(4):未上传文件。- 更多错误代码见 PHP 官方手册。
基本示例:文件上传
以下是一个简单的文件上传示例,包含验证和错误处理。
- HTML 表单 (
upload.html
):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<style>
.error { color: red; }
label { display: inline-block; width: 100px; }
input { margin-bottom: 10px; }
</style>
</head>
<body>
<h2>上传文件</h2>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- 2MB -->
<label for="file">选择文件*:</label>
<input type="file" id="file" name="file">
<br>
<input type="submit" value="上传">
<p>* 表示必需字段</p>
</form>
</body>
</html>
- PHP 处理脚本 (
upload.php
):
<?php
$errors = [];
$uploadDir = 'uploads/'; // 目标目录
// 确保上传目录存在且可写
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 检查文件是否上传
if (!isset($_FILES['file']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) {
$errors[] = "请上传一个文件";
} else {
$file = $_FILES['file'];
$name = $file['name'];
$tmp_name = $file['tmp_name'];
$size = $file['size'];
$error = $file['error'];
// 处理上传错误
if ($error !== UPLOAD_ERR_OK) {
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$errors[] = "文件大小超过限制(最大 2MB)";
break;
default:
$errors[] = "上传失败,错误代码: $error";
}
} else {
// 验证文件大小(2MB = 2 * 1024 * 1024 字节)
if ($size > 2 * 1024 * 1024) {
$errors[] = "文件大小不能超过 2MB";
}
// 验证文件扩展名
$allowed = ['jpg', 'jpeg', 'png', 'pdf'];
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
$errors[] = "只允许上传 JPG、PNG 或 PDF 文件";
}
// 如果没有错误,移动文件
if (empty($errors)) {
$newPath = $uploadDir . uniqid() . '.' . $ext; // 使用唯一文件名
if (move_uploaded_file($tmp_name, $newPath)) {
echo "文件上传成功: <a href='$newPath'>$name</a>";
} else {
$errors[] = "无法保存文件到服务器";
}
}
}
}
}
// 显示错误并回显表单
if (!empty($errors)) {
echo "<div class='error'><ul>";
foreach ($errors as $error) {
echo "<li>$error</li>";
}
echo "</ul></div>";
}
include 'upload.html';
?>
关键点解析
- 表单设置:
enctype="multipart/form-data"
:必须设置以支持文件上传。MAX_FILE_SIZE
:隐藏字段限制文件大小(仅客户端提示,需服务器验证)。- $_FILES:处理上传文件的元数据,检查
error
代码判断上传状态。 - 验证:
- 检查文件是否存在(
UPLOAD_ERR_NO_FILE
)。 - 限制文件大小(2MB)。
- 限制文件类型(仅允许 JPG、PNG、PDF)。
- 文件移动:使用
move_uploaded_file()
将临时文件移到目标目录,防止覆盖使用uniqid()
生成唯一文件名。 - 错误处理:集中存储错误,统一显示,并回显表单。
安全性注意事项
- 防止恶意文件:
- 验证文件扩展名和 MIME 类型:
php $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; if (!in_array($file['type'], $allowedTypes)) { $errors[] = "无效的文件类型"; }
- 检查文件内容(高级验证):
php if (in_array($ext, ['jpg', 'jpeg', 'png']) && !getimagesize($tmp_name)) { $errors[] = "无效的图片文件"; }
- 防止路径穿越:
- 不要直接使用用户提供的文件名:
php $safeName = basename($name); // 提取文件名,防止路径攻击
- 目录权限:
- 确保上传目录可写(通常 755 或 775 权限)。
- 避免将上传目录放在 Web 可访问路径下,或使用
.htaccess
限制访问:<Directory "uploads"> Deny from all </Directory>
- CSRF 保护:
添加 CSRF 令牌:
session_start();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
?>
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
结合数据库存储
将文件信息存储到数据库,记录路径和元数据:
- 数据库表 (
files
):
CREATE TABLE files (
id INT AUTO_INCREMENT PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
path VARCHAR(255) NOT NULL,
size INT NOT NULL,
upload_time DATETIME NOT NULL
);
- 修改 PHP 脚本 (
upload.php
):
// 在成功移动文件后添加
if (move_uploaded_file($tmp_name, $newPath)) {
try {
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("INSERT INTO files (filename, path, size, upload_time) VALUES (:filename, :path, :size, :upload_time)");
$stmt->execute([
'filename' => $name,
'path' => $newPath,
'size' => $size,
'upload_time' => date('Y-m-d H:i:s')
]);
echo "文件上传并记录到数据库!";
} catch (PDOException $e) {
$errors[] = "数据库错误: " . $e->getMessage();
}
}
最佳实践
- 验证文件类型:结合扩展名和 MIME 类型验证,防止伪造。
- 限制大小:在
php.ini
设置upload_max_filesize
和post_max_size
:
upload_max_filesize = 2M
post_max_size = 8M
- 唯一文件名:使用
uniqid()
或哈希生成文件名,避免覆盖。 - 错误提示:提供具体错误信息,如“只允许 JPG 文件”。
- 安全存储:将文件存储在非 Web 可访问目录,数据库记录路径。
- 客户端验证:结合 HTML5 属性(如
accept
):
<input type="file" name="file" accept="image/jpeg,image/png,application/pdf">
参考资源
- PHP 官方手册 – 文件上传:详细说明。
- 菜鸟教程 – PHP 文件上传:初学者教程。
- OWASP 安全指南:Web 安全最佳实践。
总结
PHP 文件上传通过 $_FILES
和 move_uploaded_file()
实现,结合验证(类型、大小)和安全措施(如 CSRF、路径清理)确保安全性和可靠性。上述示例展示了从表单设计到文件存储的完整流程,适用于图片上传、文档管理等场景。通过结合数据库存储和错误处理,开发者可以构建用户友好的文件上传功能。