文件上传
1. 文件上传配置
文件上传项目项在
php.ini
中设置,常用的配置项有:
序号 | 配置项 | 默认值 | 描述 |
---|---|---|---|
1 | file_uploads |
On |
使 PHP 支持文件上传 |
2 | upload_tmp_dir |
/tmp |
指示应该临时把上传的文件存储在什么位置 |
3 | max_file_uploads |
20 |
单次请求时允许上传的最大文件数量 |
4 | max_execution_time |
30 |
设置 PHP 最长执行时间(秒) |
5 | max_input_time |
60 |
设置 PHP 通过 POST/GET/PUT 接收数据的时长(秒) |
6 | memory_limit |
128M |
系统分配给当前脚本执行可用的最大内存容量 |
7 | post_max_size |
8M |
允许的 POST 数据的总大小(以字节为单位) |
8 | upload_max_filesize |
32M |
允许的尽可能最大的文件上传(以字节为单位) |
$_FILES
- 上传文件的描述信息,全部保存在系统全局变量
$_FILES
中 $_FILES
以二维数组形式保存:$_FILES['form_file_name']['key']
'form_file_name'
: 对应着表单中<input type="file" name="my_pic">
中name
属性值'key'
: 共有 5 个键名, 描述如下:
序号 | 键名 | 描述 |
---|---|---|
1 | name |
文件在客户端的原始文件名(即存在用户电脑上的文件名) |
2 | type |
文件的 MIME 类型, 由浏览器提供, PHP 并不检查它 |
3 | tmp_name |
文件被上传到服务器上之后,在临时目录中临时文件名 |
4 | error |
和该文件上传相关的错误代码 |
5 | size |
已上传文件的大小(单位为字节) |
- 文件上传错误信息描述
序号 | 常量 | 值 | 描述 |
---|---|---|---|
1 | UPLOAD_ERR_OK |
0 |
没有错误发生,文件上传成功 |
2 | UPLOAD_ERR_INI_SIZE |
1 |
文件超过php.ini 中upload_max_filesize 值 |
3 | UPLOAD_ERR_FORM_SIZE |
2 |
文件大小超过表单中MAX_FILE_SIZE 指定的值 |
4 | UPLOAD_ERR_PARTIAL |
3 |
文件只有部分被上传 |
5 | UPLOAD_ERR_NO_FILE |
4 |
没有文件被上传 |
6 | UPLOAD_ERR_NO_TMP_DIR |
6 |
找不到临时文件夹 |
7 | UPLOAD_ERR_CANT_WRITE |
7 |
文件写入失败 |
- 支持文件上传的前端表单设置
序号 | 属性设置 | 描述 |
---|---|---|
1 | <form method="POST"> |
请求类型必须是POST |
2 | <form enctype="multipart/form-data"> |
设置表单提交数据的编码类型 |
3 | <input type="file" name="uploads"> |
设置表单控件类型与名称以支持上传 |
4 | <input type="hidden" name="MAX_FILE_SIZE" value="..."> |
设置隐藏域限制上传文件大小(可选) |
- [扩展]
enctype
属性说明
enctype
: 设置表单数据,在发送到服务器之前的编码规则
序号 | 属性值 | 描述 |
---|---|---|
1 | application/x-www-form-urlencoded |
默认值, 发送前进行编码,空格转+ ,非空字符转 16 进制 |
2 | multipart/form-data |
不对字符编码,以二进制发送,适合文件上传 |
3 | text/plain |
纯文本发送,仅对空格编码(转为+ ) |
4. MIME 类型
4.1 概述
- MIME: (Multipurpose Internet Mail Extensions)的简写,中文意思”多功能因特网邮件扩展”
- MIME: 最初用来表示
Email
附件格式的字符串, 后来演变成为网络文档或应用程序的文档格式规范 - MIME: 由一个媒体类型一个子类组成, 中间用斜线
/
分隔,例如text/css
4.2 类型
序号 | 类型 | 描述 | 示例 |
---|---|---|---|
1 | text |
文本 | text/plain ,text/html ,text/css ,text/javascript |
2 | image |
图像 1 | image/jpeg ,image/gif ,image/png ,image/bmp ,image/ webp , |
2 | image |
图像 2 | image/x-icon , image/vnd.microsoft.icon |
3 | audio |
音频 | audio/midi , audio/mpeg , audio/webm , audio/ogg , audio/wav |
4 | video |
视频 | video/mp4 ,video/mpeg ,video/webm , video/ogg ,video/x-msvideo |
5 | application |
二进制 1 | application/octet-stream , application/javascript , application/ecmascript |
5 | application |
二进制 2 | application/json ,application/pkcs12 , application/vnd.mspowerpoint |
5 | application |
二进制 3 | application/xhtml+xml , application/xml , application/pdf ,application/x-gzip |
- 表中,以
x-
为前缀的是还没有成为国际标准的格式 - 如果某个文档不在该列表中,会识别为:
applicaton/octet-stream
, 如.md
文档, 尽管它是纯文本
示例演示
前期准备基本代码:
<?php
// 查看与文件上传相关的配置项
phpinfo();
//了解各配置项的基本信息
-------------
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 二维数组: 只有一个元素
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>单文件上传</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="500000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
---------------
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
$errorCode = $_FILES['my_pic']['error'];
if (UPLOAD_ERR_NO_FILE === $errorCode) echo '<p>没有文件被上传</p>';
// if (empty($_FILES['my_pic']['name'])) echo '<p>没有文件被上传</p>'
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>单文件上传: 检查是否有文件被上传</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="500000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
-----------
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 检测当前文件是否是通过 HTTP POST 方式上传的
$tmpFileName = $_FILES['my_pic']['tmp_name'];
$originalFileName = $_FILES['my_pic']['name'];
if (is_uploaded_file($tmpFileName)) echo "{$orginalFileName}: 上传方式合法";
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>单文件上传: 检查是否是通过POST上传?</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="500000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
----------------------
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 获取文件类型
$fileType = $_FILES['my_pic']['type'];
// echo $fileType, '<br>';
// print_r(explode('/',$fileType)[0]);
// echo strstr($fileType, '/', true);
// $allow = ['jpg', 'png', 'gif'];
// $extension = pathinfo($_FILES['my_pic']['name'])['extension'];
// echo $extension;
// if (!in_array($extension, $allow)) echo '<p>文件类型错误</p>';
if (strstr($fileType, '/', true) !== 'image') echo '<p>文件类型错误</p>';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>单文件上传: 检测文件类型</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="500000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
------------
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// php.ini
echo ini_get('upload_max_filesize');
ini_set('upload_max_filesize', '10M');
$errorCode = $_FILES['my_pic']['error'] ?? 'null';
switch ($errorCode) {
case 1:
echo '文件超过`php.ini`中`upload_max_filesize`值';
break;
case 2:
echo '文件大小超过表单隐藏域中`MAX_FILE_SIZE`指定的值 ';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
>
<fieldset>
<legend>单文件上传: 检测文件大小</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="3000000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
单文件上传示例:
文件上传正常:
文件上传异常:
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 自定义上传异常类
class UploadException extends Exception
{
// 在异常子类中,可以访问并重写Exception中的四个属性,通过__toString()格式化异常输出信息
public function __toString()
{
return <<< UPLOAD
<style>
table {border-collapse: collapse;border:1px solid black;text-align: center;}
td {border:1px solid black;padding: 5px;}
tr:first-of-type {background-color:#eee;}
tr:last-of-type td {color: coral;}
</style>
<table>
<tr><td>代码</td><td>信息</td><td>文件</td><td>行号</td></tr>
<tr><td>$this->code</td><td>$this->message</td><td>$this->file</td><td>$this->line</td></tr>
</table>
UPLOAD;
}
}
///////////////////////////////////////////////////////////////////////
try {
// 上传出错的代码
$errorCode = $_FILES['my_pic']['error'];
if ($errorCode > UPLOAD_ERR_OK) {
switch ($errorCode) {
case UPLOAD_ERR_INI_SIZE:
throw new UploadException('上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值', 1);
break;
case UPLOAD_ERR_FORM_SIZE:
throw new UploadException('上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', 2);
break;
case UPLOAD_ERR_PARTIAL:
throw new UploadException('文件只有部分被上传', 3);
break;
case UPLOAD_ERR_NO_FILE:
throw new UploadException('没有文件被上传', 4);
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new UploadException('找不到临时文件夹', 6);
break;
case UPLOAD_ERR_CANT_WRITE:
throw new UploadException('文件写入失败', 7);
break;
default:
// 测试时建议关掉default: 避免误报影响
throw new UploadException('未知类型错误', 8);
}
}
// 判断文件类型
$fileType = $_FILES['my_pic']['type'];
if (strstr($fileType, '/', true) !== 'image') throw new UploadException('文件类型错误',9);
// 将文件从临时目录 移动到用户自定义的目标目录中
// 临时文件名
$tempFileName = $_FILES['my_pic']['tmp_name'];
if (is_uploaded_file($tempFileName)) {
// 原始文件名
$originalFileName = $_FILES['my_pic']['name'];
// 目录文件名
$destFileName = 'uploads/'.md5(time().mt_rand(1,1000)).strstr($originalFileName, '.');
// 移动文件到目标目录使用的函数
if (move_uploaded_file($tempFileName, $destFileName)) {
echo "<p>$originalFileName: 上传成功~~</p>";
// 预览
echo "<img src='{$destFileName}' width='200'>";
}
}
} catch (UploadException $e) {
echo $e;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>单文件上传: 异常处理</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="3000000">
<input type="file" name="my_pic">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
多文件上传示例一:
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 将异常处理先省略,只演示关键代码
foreach ($_FILES as $file) {
// 只要判断 error === 0
if ($file['error'] === 0) {
$destFile = 'uploads/' . $file['name'];
move_uploaded_file($file['tmp_name'], $destFile);
echo "<img src='{$destFile}' width='200'>";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>多文件上传: 逐个上传(一)</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="3000000">
<input type="file" name="my_pic1">
<input type="file" name="my_pic2">
<input type="file" name="my_pic3">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
多文件上传示例二:
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 将异常处理先省略,只演示关键代码
if ($_FILES['my_pic'])
foreach ($_FILES['my_pic']['error'] as $key => $error) {
// 只要判断 error === 0
if ($error === UPLOAD_ERR_OK) {
// 临时文件名
$tmpFileName = $_FILES['my_pic']['tmp_name'][$key];
// 原始文件名
$originalFileName = $_FILES['my_pic']['name'][$key];
// 目标文件名
$destFileName = 'uploads/'. $originalFileName;
// 移动文件
move_uploaded_file($tmpFileName, $destFileName);
// 预览
echo "<img src='{$destFileName}' width='200'>";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>多文件上传: 逐个上传(二)</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="3000000">
<!-- 将name属性值能数组的形式提供 -->
<input type="file" name="my_pic[]">
<input type="file" name="my_pic[]">
<input type="file" name="my_pic[]">
<button>上传</button>
</fieldset>
</form>
</body>
</html>
批量文件上传示例:
<?php
// $_FILES
printf('<pre>%s</pre>', print_r($_FILES, true));
// 将异常处理先省略,只演示关键代码
if ($_FILES['my_pic'])
foreach ($_FILES['my_pic']['error'] as $key => $error) {
// 只要判断 error === 0
if ($error === UPLOAD_ERR_OK) {
// 临时文件名
$tmpFileName = $_FILES['my_pic']['tmp_name'][$key];
// 原始文件名
$originalFileName = $_FILES['my_pic']['name'][$key];
// 目标文件名
$destFileName = 'uploads/'. $originalFileName;
// 移动文件
move_uploaded_file($tmpFileName, $destFileName);
// 预览
echo "<img src='{$destFileName}' width='200'>";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传变量$_FILES</title>
</head>
<body>
<hr>
<form action="" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>多文件上传: 批量上传</legend>
<input type="hidden" name="MAX_FILE_SIZE" value="3000000">
<!-- 将name属性值能数组的形式提供 -->
<input type="file" name="my_pic[]" multiple>
<button>上传</button>
</fieldset>
</form>
</body>
</html>
课程学习小结
本次课程老师讲解非常细致,在老师的直播讲解之后,又通过回看视频进行理解消化,在案例实操方面,老师由浅入深,从基本必备知识开始演示,通过单个文件上传、多个文件上传(列举了两种不同的实现方式)到批量文件上传,涵盖了几乎所有日常可能使用到的情况,并且融入了文件类型检查,异常处理等实现方式,使得代码更加健壮,通过对各个示例代码的认真理解及部分修改实践,加深对本次课程内容的理解。在实际应用系统中,文件上传是非常重要的功能,必须熟练掌握。