0%

PHP高级特性三之文件上传和下载

综述

上一节我们学习了文件的读写操作,这一节我们来看一下文件上传和下载的相关内容。

文件上传

1.PHP配置文件

首先,我们文件上传需要设定一下 php.ini 的配置文件。这是最基本的设置,如果这里设置不成功,那么代码写得再正确也没有用。基本的配置项目如下

file_uploads = on #文件上传开启 upload_max_filesize= 200M #文件上传的最大尺寸 upload_tmp_dir = c:/uploads/ #临时文件目录 post_max_size = 250M #POST时最大尺寸,必须要大于 upload_max_filesize

2.上传时注意事项

1) 文件上传操作表单提交方法必须为 post 2)文件上传时,input type 必须为 file 类型 3)文件上传的表单中,需要增加一个隐含内容,代码如下,value 的单位是 B

1
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">

4)enctype=”multipart/form-data” 只有文件上传时才使用这个值,用来指定表单编码的数据方式,让服务器知道我们要传递一个文件并带有一些常规的表单信息。如下

1
2
<form action="upload.php" method="post" enctype="multipart/form-data">
</form>

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title> File Uploads </title>
</head>
<body>
<form action="b.php" method="post" enctype="multipart/form-data">
shopname: <input type="text" name="shopname" > <br>
shopprice: <input type="text" name="price"> <br>
shopnum : <input type="text" name="num"> <br>
shoppic: <input type="file" name="pic"> <br>
<input type="submit" name="sub" value="添加商品">
</form>
</body>
</html>

文件 a.php 表单提交到了 b.php 文件,在文件 b.php 中如下

1
2
3
4
5
6
<?php 
echo "<pre>";
print_r($_POST);
print_r($_FILES);
echo "</pre>";
?>

一个是输出 POST得到的数据内容,另一个是输出获取到的文件信息。 运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Array
(
[shopname] => abc
[price] => abc
[num] => add
[sub] => 添加商品
)
Array
(
[pic] => Array
(
[name] => QPGF.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpC2C7.tmp
[error] => 0
[size] => 199224
)

)

如果不加 enctype=”multipart/form-data” 那么 print_r($_FILES) 不会有任何输出 又比如多文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<title> File Uploads </title>
</head>
<body>
<form action="b.php" method="post" enctype="multipart/form-data">
shopname: <input type="text" name="shopname" > <br>
shopprice: <input type="text" name="price"> <br>
shopnum : <input type="text" name="num"> <br>
shoppic: <input type="file" name="pic1"> <br>
shoppic: <input type="file" name="pic2"> <br>
shoppic: <input type="file" name="pic3"> <br>
<input type="submit" name="sub" value="添加商品">
</form>
</body>
</html>

file的name需要不同的名字,那么上面的代码输出结果为

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
Array
(
[pic1] => Array
(
[name] => libtcmalloc.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE51E.tmp
[error] => 0
[size] => 178232
)

[pic2] => Array
(
[name] => libexpatw.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE52E.tmp
[error] => 0
[size] => 130104
)

[pic3] => Array
(
[name] => AsyncTask.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE52F.tmp
[error] => 0
[size] => 84536
)

)

还可以将name设定为一个数组,如

1
2
3
shoppic: <input type="file" name="pic[]"> <br>
shoppic: <input type="file" name="pic[]"> <br>
shoppic: <input type="file" name="pic[]"> <br>

则输出会是一个三维数组

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
Array
(
[pic] => Array
(
[name] => Array
(
[0] => libtcmalloc.dll
[1] => libexpatw.dll
[2] => QQProtect.dll
)

[type] => Array
(
[0] => application/qscall-plugin
[1] => application/qscall-plugin
[2] => application/qscall-plugin
)

[tmp_name] => Array
(
[0] => D:\wamp\tmp\phpA17D.tmp
[1] => D:\wamp\tmp\phpA17E.tmp
[2] => D:\wamp\tmp\phpA17F.tmp
)

[error] => Array
(
[0] => 0
[1] => 0
[2] => 0
)

[size] => Array
(
[0] => 178232
[1] => 130104
[2] => 387128
)

)

)

3. 文件上传后的检查

加入上传的表单中文件的name是pic,那么检查的四个方法如下: 1)使用 $_FILES[‘file’][‘error’] 检查错误 2)使用 $_FILES[‘file’][‘size’] 限制大小,单位是字节 3)使用 $_FILES[‘pic’][‘type’] 获取文件或站名,限制文件的类型 4)将上传后的文件名改名

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
<?php
//step 1 使用$_FILES['pic']["error"] 检查错误

if($_FILES["pic"]["error"] > 0){
switch($_FILES["pic"]["error"]) {
case 1:
echo "上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值<br>";
break;
case 2:
echo "上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值";
break;

case 3:
echo "文件只有部分被上传";
break;

case 4:
echo "没有文件被上传";
break;

default:

echo "末知错误";

}
exit;
}

$maxsize=5000000; //50k

//step 2 使用$_FILES["pic"]["size"] 限制大小 单位字节 2M=2000000
if($_FILES["pic"]["size"] > $maxsize ) {
echo "上传的文件太大,不能超过{$maxsize}字节";
exit;
}

//step 3 使用$_FILES["pic"]["type"]或是文件的扩展名 限制类型 MIME image/gif image/png gif png jpg

/* list($dl, $xl) = explode("/", $_FILES["pic"]["type"]);

if($dl!="image"){
echo "请上传一个图片,不充许其它类型文件";
exit;
}
*/

$allowtype=array("png", "gif", "jpg", "jpeg");
$arr=explode(".", $_FILES["pic"]["name"]);
$hz=$arr[count($arr)-1];
if(!in_array($hz, $allowtype)){
echo "这是不充许的类型";
exit;
}

//step 4 将让传后的文件名改名


$filepath="./uploads/";
$randname=date("Y").date("m").date("d").date("H").date("i").date("s").rand(100, 999).".".$hz;
//将临时位置的文件移动到指定的目录上即可
if(is_uploaded_file($_FILES["pic"]["tmp_name"])){
if(move_uploaded_file($_FILES["pic"]["tmp_name"], $filepath.$randname)){
echo "上传成功";
}else{
echo "上传失败";
}
}else{
echo "不是一个上传文件";
}

以上便实现了文件上传的检测,包括错误检测,文件大小检测,文件类型检测以及文件更名等等。

文件上传类

在上面的介绍中,我们没有将文件的上传做一个封装,不过,将文件上传个功能封装成一个类的确是一个不错的选择。下面便是一个实例DEMO,让我们来感受一下吧!

1
FileUpload.class.php
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
<?php
class FileUpload {

private $filePath; //指定上传文件保存的路径
private $allowType=array('gif', 'jpg', 'png', 'jpeg'); //充许上传文件的类型
private $maxSize=1000000; //允上传文件的最大长度 1M
private $isRandName=true; //是否随机重命名, true false不随机,使用原文件名
private $originName; //源文件名称
private $tmpFileName; //临时文件名
private $fileType; //文件类型
private $fileSize; //文件大小
private $newFileName; //新文件名
private $errorNum=0; //错误号
private $errorMess=""; //用来提供错误报告



//用于对上传文件初使化
//1. 指定上传路径, 2,充许的类型, 3,限制大小, 4,是否使用随机文件名称
//让用户可以不用按位置传参数,后面参数给值不用将前几个参数也提供值
function __construct($options=array()){
foreach($options as $key=>$val){
//查看用户参数中数组的下标是否和成员属性名相同
if(!in_array($key,get_class_vars(get_class($this)))){
continue;
}
//设置成员变量
$this->setOption($key, $val);
}
}


//获得错误原因
private function getError(){
//获得错误原因
$str="上传文件<font color='red'>{$this->originName}</font>时出错:";
switch($this->errorNum){
case 4: $str .= "没有文件被上传"; break;
case 3: $str .= "文件只被部分上传"; break;
case 2: $str .= "上传文件超过了HTML表单中MAX_FILE_SIZE选项指定的值"; break;
case 1: $str .= "上传文件超过了php.ini 中upload_max_filesize选项的值"; break;
case -1: $str .= "不被充许的类型"; break;
case -2: $str .= "文件过大,上传文件不能超过{$this->maxSize}个字节"; break;
case -3: $str .= "上传失败"; break;
case -4: $str .= "建立存放上传文件目录失败,请重新指定上传目录"; break;
case -5: $str .= "必须指定上传文件的路径"; break;
default: $str .= "末知错误";
}
return $str.'<br>';
}


//用来检查文件上传路径
private function checkFilePath(){
if(empty($this->filePath)) {
$this->setOption('errorNum', -5);
return false;
}
if(!file_exists($this->filePath) || !is_writable($this->filePath)){
if(!@mkdir($this->filePath, 0755)){
$this->setOption('errorNum', -4);
return false;
}
}
return true;
}

//用来检查文件上传的大小
private function checkFileSize() {
if($this->fileSize > $this->maxSize){
$this->setOPtion('errorNum', '-2');
return false;
}else{
return true;
}
}

//用于检查文件上传类型
private function checkFileType() {
if(in_array(strtolower($this->fileType), $this->allowType)) {
return true;
}else{
$this->setOption('errorNum', -1);
return false;
}
}

//设置上传后的文件名称
private function setNewFileName(){
if($this->isRandName){
$this->setOption('newFileName', $this->proRandName());
} else {
$this->setOption('newFileName', $this->originName);
}
}


//设置随机文件名称
private function proRandName(){
$fileName=date("YmdHis").rand(100,999);
return $fileName.'.'.$this->fileType;
}

//设置成员变量
private function setOption($key, $val){
$this->$key=$val;
}

//用来上传一个文件
function uploadFile($fileField){

echo $fileField;
//默认返回值为True
$return=true;
//首先检查文件上传路径
if(!$this->checkFilePath()){
$this->errorMess=$this->getError();
return false;
}

//获得上传文件的名字
$name=$_FILES[$fileField]['name'];
//获得临时文件名
$tmp_name=$_FILES[$fileField]['tmp_name'];
//获得上传文件的大小
$size=$_FILES[$fileField]['size'];
//获得上传错误代号
$error=$_FILES[$fileField]['error'];

//如果上传的是多个文件
if(is_Array($name)){
//错误代号必须也是Array,因为一个文件对应一个错误代号
$errors=array();
//遍历检查文件
for($i=0; $i<count($name); $i++){
if($this->setFiles($name[$i], $tmp_name[$i], $size[$i], $error[$i])){
if(!$this->checkFileSize() || !$this->checkFileType()){
$errors[]=$this->getError();
$return=false;
}
}else{
$error[]=$this->getError();
$return=false;
}
if(!$return)
$this->setFiles();
}
if($return){
$fileNames=array();
for($i=0; $i<count($name); $i++){
if($this->setFiles($name[$i], $tmp_name[$i], $size[$i], $error[$i])){
$this->setNewFileName();
if(!$this->copyFile()){
$errors=$this->getError();
$return=false;
}else{
$fileNames[]=$this->newFileName;
}
}
}
//是一个数组
$this->newFileName=$fileNames;
}
//赋值错误信息
$this->errorMess=$errors;
return $return;
//如果是单个文件上传
} else {
if($this->setFiles($name, $tmp_name, $size, $error)){
if($this->checkFileSize() && $this->checkFileType()){
$this->setNewFileName();
if($this->copyFile()){
return true;
}else{
$return=false;
}
}else{
$return=false;
}
}else{
$return=false;
}

if(!$return)
$this->errorMess=$this->getError();


return $return;
}
}

//保存文件,将文件从临时路径移动到新路径
private function copyFile(){
if(!$this->errorNum){
$filePath=rtrim($this->filePath, '/').'/';
$filePath.=$this->newFileName;

if(@move_uploaded_file($this->tmpFileName, $filePath)) {
return true;
}else{
$this->setOption('errorNum', -3);
return false;
}

}else{
return false;
}
}

//设置和$_FILES有关的内容
private function setFiles($name="", $tmp_name='', $size=0, $error=0){

$this->setOption('errorNum', $error);
if($error){
return false;
}
$this->setOption('originName', $name);
$this->setOption('tmpFileName', $tmp_name);
//分割文件名,取最后一个后缀
$arrStr=explode('.', $name);
$this->setOption('fileType', strtolower($arrStr[count($arrStr)-1]));
$this->setOption('fileSize', $size);
return true;
}

//用于获取上传后文件的文件名
function getNewFileName(){
return $this->newFileName;
}

//上传如果失败,则调用这个方法,就可以查看错误报告
function getErrorMsg() {
return $this->errorMess;
}

}
1
 upload.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php
require "FileUpload.class.php";
//实例化这个对象
$up=new FileUpload(array('isRandName'=>true,'allowType'=>array('txt', 'doc', 'php', 'gif'),'filePath'=>'./uploads/', 'maxSize'=>200000));
echo '<pre>';
//调用上传文件的方法
if($up->uploadFile('upload')){
print_r($up->getNewFileName());
}else{
print_r($up->getErrorMsg());
}
echo '</pre>';
1
 form.html
1
2
3
4
5
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">
<input type="file" name="upload"> <br>
<input type="submit" name="sub" value="upload file"><br>
</form>
1
 多文件上传的 form.html
1
2
3
4
5
6
7
8
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="submit" name="sub" value="upload file"><br>
</form>

利用上面的这个文件上传类,我们便可以轻松地实现文件上传,非常之便捷。

文件下载

对于浏览器无法直接打开的文件,我们一般只需要设置一下超链接就好了。比如

1
<a href="a.rar">a.rar</a>

点击超链接之后,便会弹出下载的提示框。 可是对于浏览器可以直接打开的文件,例如 1.html,2.php,3.gif 等等文件,如果仍然用这种超链接形式,那就行不通了,浏览器会直接跳转到这个页面。 我们怎样解决这个问题呢?很简单 我们首先要将超链接的文件名改为一个 php 文件,比如上面的链接就可以改为

1
<a href="a.php">logo.gif</a>

这样浏览器会去访问 a.php 文件,那么我们只需要在 a.php 文件中作相应处理即可,例如我们要下载 logo.gif 文件 我们就需要在 a.php 文件最开始设定头部信息,如下

1
2
3
4
5
6
<?php 
header("Content-Type:image/gif");
header('Content-Disposition: attachment; filename="logo.gif"');
header('Content-Length:300');
readfile("logo.gif");
?>

一般设置三个头部信息就好了 第一个是设置文件传输的类型,第二个是设置传送的内容为附件形式,文件名是 logo.gif,这里的filename 即为我们下载文件时命名的名字,而不是文件名本身。第三个是设置文件传输大小。 最后设置一下下载的是哪个文件就好了。利用 readfile 方法。 以上便是文件下载所需要的方法。 这样,文件上传和文件下载的方法就全部介绍完啦!