Blogger Information
Blog 54
fans 6
comment 31
visits 106563
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
PHP(多)文件上传实现和函数封装
吾逍遥
Original
1001 people have browsed it

一、PHP 文件上传的相关知识

对 PHP 文件上传的相关知识总结主要是参考老师演示的代码和 drawer.php(某大神写的 PHP 单文件版的服务器文件管理端)

1. php 关于文件上传的配置

文件上传项目项在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 允许的尽可能最大的文件上传

2. 服务端超全局变量$_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.iniupload_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 文件写入失败

3、介绍一些常用的 PHP 函数

与 php.ini 配置相关的函数: 我们知道修改 php.ini 后要重启服务,但修改配置一般是某些页面需求,再者一般也不建议随意修改 php.ini 的配置文件。PHP 提供了 ini 为前缀的函数,修改配置仅对当前页面有效,页面释放后就无效了,非常适合我们平常使用。

  • ini_set(string $varname,string $newvalue):string 设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。简单的说就是可以临时修改 pnp.ini 配置文件中的值,页面结束时恢复 。这样就可以不用去修改 php.ini 的默认配置了,毕竟它是全局的,影响机器上所有 PHP 服务,而我们改变一般都是针对当前需求的,所以使用它修改比较合适。
  • ini_get(string $varname):string获取一个配置选项的值,ini_get_all([ string $extension[, bool $details = true]]):array获取所有已注册的配置选项,get_cfg_var(string $option):mixed获取 PHP 配置选项 option 的值,此函数不会返回 PHP 编译的配置信息,或从 Apache 配置文件读取。
  • ini_restore(string $varname):void 恢复指定的配置选项到它的原始值。
  1. //限制可访问目录,避免恶意修改
  2. ini_set('open_basedir',__DIR__);//仅在当前页面中应用该配置,不影响PHP.ini配置文件中设置,页面结束后就无效了。
  3. echo ini_get('open_basedir'),'<br>';
  4. ini_set('max_file_uploads','30');//设置无效
  5. echo ini_get('max_file_uploads'),'<br>';

补充: 本以为可以设置文件上传相关配置,经测试发现无效,查 PHP 官方只有可修改范围是 PHP_INI_ALL 才可以被 ini_set 修改。就当了解知道吧。

与目录或文件相关的函数: PHP 内置大量的文件系统操作函数,在一篇类的自动加载时已经介绍过了 is_file 和 file_exists 函数,下面再介绍与上传文件相关的函数

  • 目录相关的函数:is_dir()判断给定文件名是否是一个目录,如果文件名存在,并且是个目录返回 true,否则返回 false。opendir()、 closedir()、readdir()和rewinddir()对目录进行遍历。rmdir()删除目录,mkdir新建目录。
  1. /**
  2. * 遍历出所有文件或文件夹
  3. * @access public
  4. * @param string $dira 要遍历的文件夹名
  5. * @return array
  6. */
  7. function traverseDir( $dira ) {
  8. $arr = array();
  9. if ( $dh = opendir( $dira ) ) {
  10. while ( ( $file = readdir( $dh ) ) !== false ) {
  11. if ( ( $file != '.' ) && ( $file != '..' ) && is_dir( $dira . '/' . $file ) ) {
  12. $arr[] = $dira . '/' . $file;
  13. foreach ( traverseDir( $dira . '/' . $file ) as $v ) {
  14. $arr[] = $v;
  15. }
  16. }
  17. clearstatcache();
  18. }
  19. }
  20. return $arr;
  21. }
  22. printf("<pre>%s<pre>",print_r(traverseDir('F:/120910'),true));
  • 文件相关的函数: 判断就是 is_file 和 file_exists 两个函数了。copy()复制文件,如果目标文件已存在,将会被覆盖。这里重点说下对上传临时文件的操作函数即tmp_name键名的临时文件
    • getimagesize(临时文件) 检验是否真实图片,可防止修改扩展名伪装图片
    • is_uploaded_file(临时文件) 判断文件是否是通过 HTTP POST 上传的,如果 filename 所给出的文件是通过 HTTP POST 上传的则返回 true。这可以用来确保恶意的用户无法欺骗脚本去访问本不能访问的文件,例如/etc/passwd
    • move_uploaded_file(临时文件,目标文件 将上传的文件移动到新位置。
  1. if (!getimagesize($fileInfo['tmp_name'])) die('不是真实图片,get out~');
  2. if (!is_uploaded_file($fileInfo['tmp_name'])) die('上传方式错误:请使用http post方式上传');
  3. if (!move_uploaded_file($fileInfo['tmp_name'], $fileRealPath)) die('文件上传失败');
  • 路径相关的函数:
    • basename(string $path[,string $suffix]):string给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。
    • dirname(string $path[,int $levels = 1]):string给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名,且目录深度为 levels 级。
    • pathinfo(string $path[,int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]):mixed返回一个关联数组包含有 path 的信息。返回关联数组还是字符串取决于 options。本函数检查并确保由 filename 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 destination 指定的文件。
  1. // 路径
  2. echo basename("/etc/sudoers.d").PHP_EOL;
  3. echo basename("/etc/sudoers.d", ".d").PHP_EOL;//如果文件名是以第二个参数结束的,那这一部分也会被去掉
  4. echo dirname("/etc/passwd") . PHP_EOL;
  5. echo dirname("/usr/local/lib", 2). PHP_EOL;//第二个参数指示深度(PHP7.0开始支持)
  6. $path_parts = pathinfo('/www/htdocs/inc/lib.inc.php');
  7. echo $path_parts['dirname'].PHP_EOL; //返回/www/htdocs/inc
  8. echo $path_parts['basename'].PHP_EOL; //返回lib.inc.php
  9. echo $path_parts['extension'].PHP_EOL; //返回php
  10. echo $path_parts['filename'].PHP_EOL; //返回lib.inc

二、PHP(多)文件上传前端的实现

1、功能和源码

功能描述:

  • 可选择一个文件,也可选择多个文件
  • 若选择文件是图片则提供预览功能
  • 对选择文件可进行上传服务端
  1. <style>
  2. .container {
  3. width: 70vw;
  4. min-width: 600px;
  5. margin: 3em auto;
  6. background-color: #007d20;
  7. color: white;
  8. font-size: 1.1em;
  9. padding: 0.5em 1em 3em;
  10. border-radius: 1em;
  11. }
  12. .container h2 {
  13. text-align: center;
  14. margin: 0;
  15. padding: 0;
  16. border: none;
  17. }
  18. form fieldset {
  19. display: flex;
  20. justify-content: space-between;
  21. }
  22. form fieldset#image {
  23. flex-flow: row wrap;
  24. justify-content: initial;
  25. /* flex其实也有弹性单元的概念,不过这不是官方的说法,它的提出是我学习Grid时发现的,只是它是交叉方向的,不设置默认是拉伸stretch */
  26. /* 在项目中若不设置,则图片宽度和高度都一样,这样有的图片就被拉伸了,不是按图片比例缩放 */
  27. align-items: center;
  28. }
  29. form fieldset#image img {
  30. margin: 5px 10px;
  31. }
  32. </style>
  33. <div class="container">
  34. <h2>PHP实现的(多)文件上传</h2>
  35. <form action="upload.php" method="POST" enctype="multipart/form-data">
  36. <fieldset>
  37. <legend>选择上传文件</legend>
  38. <input type="file" id="file" name="js_file[]" multiple />
  39. <button>上传服务器</button>
  40. </fieldset>
  41. <fieldset id="image" style="display: none;"></fieldset>
  42. </form>
  43. </div>
  44. <script>
  45. const file = document.querySelector('#file');
  46. const fdset = document.querySelector('#image');
  47. file.addEventListener('change', showImage, false);
  48. function showImage() {
  49. // 上传文件信息保存在input的DOM的files属性中
  50. let files = file.files;
  51. let imgArr = [];
  52. for (let item of Object.values(files)) {
  53. if (item.type.includes('image')) imgArr.push(item);
  54. }
  55. fdset.setAttribute('style', 'display:none;');
  56. if (imgArr.length > 0) {
  57. // 先清空内容再刷新
  58. fdset.setAttribute('style', 'display:block;');
  59. fdset.innerHTML = null;
  60. const legend = document.createElement('legend');
  61. legend.innerHTML = `准备上传${imgArr.length}张图片`;
  62. fdset.appendChild(legend);
  63. for (let [index, item] of imgArr.entries()) {
  64. const reader = new FileReader();
  65. reader.readAsDataURL(item);
  66. reader.onload = () => {
  67. const img = new Image();
  68. // const img=document.createElement('img');
  69. // reader.result为获取结果
  70. img.src = reader.result;
  71. img.width = '150';
  72. // 将用户选择的图片显示到指定元素中
  73. fdset.appendChild(img);
  74. };
  75. }
  76. }
  77. }
  78. </script>

2、前端图片的预览

MDN 官方介绍:FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。 重要提示: FileReader仅用于以安全的方式从用户(远程)系统读取文件内容 它不能用于从文件系统中按路径名简单地读取文件。 要在JavaScript中按路径名读取文件,应使用标准Ajax解决方案进行服务器端文件读取,如果读取跨域,则使用CORS权限。

input 控件的上传的 FileList 保存了文件名,没有保存路径,读取只能通过 FileReader,然后通过readAsDataURL读取指定的 Blob 或 File 对象,成功时返回结果中 result 属性将包含一个data:URL格式的字符串(base64 编码)以表示所读取文件的内容。然后加载给 img 的 src,在 js 中new Image()也可以创建 HTMLImageElement,或直接document.createElement('img')创建,上面代码中二种形式均支持,而且 img 元素比较特殊,既支持路径名的图片,也支持图上的 base64 编码。

上面图片预览代码是在老师演示代码基础上进行了改进:

  • 图片 HTMLImageElement 创建,老师演示是new Image(),我增加了document.createElement('img'),二者经测试是等效的
  • 多张图片是 Flex 布局,由于其默认等高,只设置了宽度,图片并不会等比例缩放,需要在 CSS 中设置align-items:center;。关于 Flex 弹性单元的概念,首先它不是官方的,但确实存在的,是我通过 Grid 相对提出的,具体可见https://www.php.cn/blog/detail/24670.html
  • 上面演示的是实时添加 DOM,本想使用文档片断(DocumentFragment)来优化加载渲染,但由于图片是异步加载的,目前没解决,以后再探讨吧

三、PHP(多)文件上传后端的实现

1、功能和源码

功能描述:

  • 可处理单文件或多文件上传
  • 对文件上传失败可提供详细信息
  • 若是图片则检查是否真实图片,并检查扩展名
  • 检查文件大小是否超过限制,默认是 2M
  • 检查文件上传是否是 POST
  • 可检查文件的扩展名
  • 对文件名进行散列 md5 处理
  • 结合数据库可解决重复文件上传问题,若是重复则直接返回已经上传的路径(这个当前没提供,可通过 Hash 判断文件是否上传)
  1. // upload.php
  2. require_once __DIR__.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'helper.php';
  3. $files=upload();
  4. $res=[];
  5. foreach($files as $file){
  6. array_push($res,uploadFile($file,false));
  7. }
  8. var_dump($res);
  1. // lib/helper.php
  2. // 对客户端上传的单文件或多文件进行分析处理
  3. // 返回的数组是上传文件信息,若只有一个则只有一个成员,若是多个则多个成员
  4. function upload()
  5. {
  6. $files = [];
  7. foreach ($_FILES as $file) {
  8. foreach ($file['name'] as $k => $v) {
  9. $name = $v;
  10. $type = $file['type'][$k];
  11. $tmp_name = $file['tmp_name'][$k];
  12. $error = $file['error'][$k];
  13. $size = $file['size'][$k];
  14. array_push($files, compact('name', 'type', 'tmp_name', 'error', 'size'));
  15. }
  16. }
  17. return $files;
  18. }
  19. // 完成文件上传
  20. function uploadFile($file, $flag = true, $path = './upload', $ext = [], $maxSize = 2 * 1024 * 1024)
  21. {
  22. // 先处理内置错误
  23. switch ($file['error']):
  24. case 0:
  25. break;
  26. case 1:
  27. $res['msg'] = '文件大小超过PHP.ini中upload_max_filesize指定的值';
  28. break;
  29. case 2:
  30. $res['msg'] = '文件大小超过表单中MAX_FILE_SIZE指定的值';
  31. break;
  32. case 3:
  33. $res['msg'] = '文件只有部分被上传';
  34. break;
  35. case 4:
  36. $res['msg'] = '没有文件被上传';
  37. break;
  38. case 6:
  39. $res['msg'] = '找不到临时文件夹';
  40. break;
  41. case 7:
  42. $res['msg'] = '文件写入失败';
  43. break;
  44. default:
  45. $res['msg'] = '系统错误';
  46. endswitch;
  47. // 处理后端自定义错误
  48. // 获取文件扩展名
  49. $extFile = substr($file['name'], strrpos($file['name'], '.') + 1);
  50. // 若是图片则判断是否真实图片和扩展名
  51. if ($flag) {
  52. $extImg = ['jpg', 'jpeg', 'png', 'wbmp', 'gif'];
  53. // 若是图片则返回数组,否则是false
  54. if (!getimagesize($file['tmp_name']))
  55. $res['msg'] = '不是合法的图片';
  56. if (!in_array($extFile, $extImg))
  57. $res['msg'] = '非法图片类型';
  58. }
  59. // 判断大小是否超过限制2M
  60. if ($file['size'] > $maxSize)
  61. $res['msg'] = '文件大小超过2M,请确保文件小于2M';
  62. // 判断是否POST上传
  63. if (!is_uploaded_file($file['tmp_name']))
  64. $res['msg'] = '文件不是通过HTTP POST上传';
  65. // 若设置扩展名,则检查文件类型
  66. if (count($ext) > 0) {
  67. if (!in_array($extFile, $ext))
  68. $res['msg'] = '非法文件类型';
  69. }
  70. if (!file_exists($path)) {
  71. if (!mkdir($path, 0777, true))
  72. $res['msg'] = '服务端禁止上传文件';
  73. chmod($path, 0777);
  74. }
  75. // 拦截用户定义的错误
  76. if ($res) return $res;
  77. // 成功则移动
  78. if ($file['error'] === 0) {
  79. $newPath = $path . DIRECTORY_SEPARATOR . md5(substr($file['name'], 0, strrpos($file['name'], '.'))) . '.' . $extFile;
  80. $res['msg'] = '上传文件失败';
  81. if (move_uploaded_file($file['tmp_name'], $newPath)) {
  82. $res['msg'] = '上传文件成功';
  83. $res['path'] = $newPath;
  84. }
  85. }
  86. return $res;
  87. }

2、多维数组的提取

在上例代码中对多文件上传时分析处理,我基本是按照老师方式遍历多维数组的方式来提取,在细节上改进了一些。其实对二维数组某列的获取,PHP 提供了array_column(),如对上传文件$_FILES,可以是如下形式,不过唯一不足就是每个上传文件信息变成索引数组了,不过和上面关联数组是一一对应的,不影响读取。

  1. function upload()
  2. {
  3. $files = [];
  4. foreach ($_FILES as $file) {
  5. foreach ($file['name'] as $k => $v) {
  6. array_push($files, array_column($file,$k));
  7. }
  8. }
  9. return $files;
  10. }

3、对上传文件状态检查

这点我和老师基本思路是一样的,就是先检查内部定义的错误,后处理用户自定义的检查(包括是否真实图片、大小、上传方式和扩展名等),若有错误则返回,没有则从临时文件夹移动到指定的位置,并返回所有上传文件的状态信息。在一些细节方面进行了改进。

  • 对文件名的获取,老师只考虑普通的情况,可能文件名是page_new_23.my.img.jpg时,有多个点时,老师使用array_shift()获取文件名就不完整,我的解决方案是(substr($file['name'], 0, strrpos($file['name'], '.')),可避免老师提取文件名不全的问题

  • 对内部定义的错误的处理,我是当error为0时,直接break跳出switch的分支语句。

  • 对扩展名的检查不再局限于图片,毕竟上传不仅仅是图片,其它文件也需要检查扩展名,通过判断是否定义扩展名来决定是否检查扩展名。

  • 我的现在项目中对上传文件还会检查是否已经上传,是通过Hash文件来判断,可在数据库中保存上传文件的Hash信息,每次上传会进行判断,若已经上传则直接返回已经上传地址。这里我就不实现了,思路已经提供了,这里只是演示文件上传功能。

Correcting teacher:灭绝师太灭绝师太

Correction status:qualified

Teacher's comments:很精彩, 基础知识扎实了, 相信后面的实战部分,你理解的会更透彻~
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!