上传文件。没错,除了上传表单的文本,我们还能用表单传文件。是直接将文件上传到服务器吗?当然不是,要认证的,“注册用户才能上传文件”是个不错的方法。另外,最好能建一个类来定义上传,面向对象嘛!
一、基本上传文件:(两步完成)
第一步:将enctype=" multipart/form-data"添加到开始标签<form>;
第二步:将<input>元素的type属性设置为file。
<form action=" " method="post" enctype=" multipart/form-data" id="uploadImage">
<p>
<label for="image">Upload image:</label>
<input type="file" name="image" id="image">
</p>
<p>
<input type="submit" name="upload" id="upload" value="Upload">
</p>
</form>
二、找到上传的东西:
1、这样就把文件上传了吗?肯定不是啦,文件被上传到一个临时文件夹中,文件过后将被删除,除非你在被删除之前将它们移到所需位置。
2、PHP在一个单独的超全局数组$_FILES中传输上传文件的详细内容。$_FILES是一个多维数组,对于图片文件,顶层数组只包含一个元素,例如image。
image元素包含另一个由下列5个元素构成的数组(或子数组 )
name:上传文件的初始名称。
type:上传文件的MIME类型。
tmp_name:上传文件的位置。
error:表示 上传状态的一个整数。
size:上传文件的大小(用字节表示)。
三、将临时文件移动到上传文件夹
1、临时版的上传文件。其实点击确认按扭,你只是上传到临时文件夹,所以要赶紧给它提供一个“地方”,不然它就像阵雨,来也匆 匆,去也冲冲。我们用move_uploaded_file()函数来处理。它有两个成员:
a、临时文件的位置。
b、文件的新位置的完全路径名,包括文件名本身。
2、我们先在HTML表单中指定上传文件的最大大小: <input type="hidden" name="MAX_FILE_SIZE" value="<? php echo $max; ?>">
3、定义$max的值。设定文件上传的最在数值。$max=51200;
4、将文件移动到上传文件夹并重命名。
<?php
$max=51200;
if(isset($_POST['upload'])){
$destination = 'C:/upload_test/'; //变量$destination用于定义上传文件夹的路径。
move_uploaded_file($_FILES['image</span>']['tmp_name'],$destination.$_FILES['image']['name']);
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>update files</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data" id="uploadImage">
<p>
<label for="image">Upload image:</label>
<input type="hidden" name="MAX_FILE_SIZE" value="<? php echo $max; ?>">
<input type="file" name="image" id="image">
</p>
<p>
<input type="submit" name="upload" id="upload" value="Upload"/>
</p>
</form>
<pre>
<?php
if(isset($_POST['upload'])){
print_r($_FILES);
}
?>
</pre>
</body>
</html>
四、创建基本的文件上传类
首先,我们为Ps2_Upload的类创建基本的定义,该类将$_FILES数组存储在一个准备处理文件上传的内部属性中。
1、在classes文件夹中创建一个名为Ps2的子文件夹。
2、在新的Ps2文件夹中,创建一个名为Upload.php的文件,并插入以下代码:
<?php
class Ps2_Upload{
}
3、为类声明各项使用受保护的变量。(每个受保护变量的名称以下划线开始,这是一个惯例。)
a、$_FILES数组
b、上传文件夹的路径
c、最大文件大小
d、报告上传状态的消息
e、支持的文件类型
f、记录文件名是否已被修改的布尔变量
4、到这步,类还不算做好。因为用类创建一个类(对象)的实例时,类定义文件自动调用类的构造方法,它初始化对象。所有类的构造方法的名称都是__construct()(两个下划线)。构造方法需要能够在类的外部访问,因此在其定义之前要使用public关键字。
构造函数内部的条件语句将$path传递给is_dir()和is_writable()函数,这两个函数可以检查所提交的值是否是一个有效的可写目录(文件夹)。如果其中的任何一个条件都失败,则构造函数抛出一个导常,并给出一条指示出问题的消息。
<?php
class Ps2_Upload{
protected $_uploaded = array(); //$_FILES数组
protected $_destination; //上传文件夹的路径
protected $_max = 51200; //最大文件大小
protected $_messages = array(); //报告上传状态的消息
protected $_permitted = array('image/gif','image/jpeg','image/pjpeg','image/png'); //支持的文件类型
protected $_renamed = false; //记录文件名是否已被修改的布尔变量
public function __construct($path){
if(!is_dir($path) || !is_writable($path)){
throw new Exception("$path must be a valid,writable directory.");
}
$this->_destination = $path;
$this->_uploaded = $_FILES;
}
public function move(){
$field = current($this->_uploaded);
$success = move_uploaded_file($field['tmp_name'],$this->_destination.$field['name']);
if($success){
$this->_messages[] = $field['name'].'uploaded successfully';
}else{
$this->_messages[] = 'Could not upload'.$field['name'];
}
}
public function getMessages(){
return $this->_messages;
}
}
?>
5、之前说过上传文件只会保存很短的时间,我们要将它及时移到文件夹中。我们直接在构造函数之后创建一个move()公共方法。第一行代码将$_uploaded属性传递给current()函数,此函数返回数组的当前元素,在这个例子中,返回元素是$_FILES数组的第一个元素。因此,$field保存了对第一次上传文件的引用,无论表单中使用的名称是什么。
6、接下来,创建一个公共方法来检索数组的内容:getMessages();
7、修改Uploaded.php,并修改file_upload.php顶部的代码。
<?php
// set the maximum upload size in bytes
$max = 51200;
if (isset($_POST['upload'])) {
// define the path to the upload folder
$destination = 'C:/upload_test/';
require_once('../classes/Ps2/Upload.php');
try {
$upload = new Ps2_Upload($destination);
$upload->move();
$result = $upload->getMessages();
} catch (Exception $e) {
echo $e->getMessage();
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8">
<title>Upload File</title>
</head>
<body>
<?php
if (isset($result)) {
echo '<ul>';
foreach ($result as $message) {
echo "<li>$message</li>";
}
echo '</ul>';
}
?>
<form action="" method="post" enctype="multipart/form-data" id="uploadImage">
<p>
<label for="image">Upload image:</label>
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $max; ?>">
<input type="file" name="image" id="image">
</p>
<p>
<input type="submit" name="upload" id="upload" value="Upload">
</p>
</form>
</body>
</html>
五、检查上传错误。
<?php
class Ps2_Upload {
protected $_uploaded = array();
protected $_destination;
protected $_max = 51200;
protected $_messages = array();
protected $_permitted = array('image/gif',
'image/jpeg',
'image/pjpeg',
'image/png');
protected $_renamed = false;
//初始化函数
public function __construct($path) {
if (!is_dir($path) || !is_writable($path)) {
throw new Exception("$path must be a valid, writable directory.");
}
$this->_destination = $path;
$this->_uploaded = $_FILES;
}
//将文件传递给move_uploaded_file()之前检查它的有效性
public function move() {
$field = current($this->_uploaded);
$OK = $this->checkError($field['name'], $field['error']);//将文件传递给move_uploaded_file()之前检查它的有效性。
if ($OK) {
$sizeOK = $this->checkSize($field['name'], $field['size']);
$typeOK = $this->checkType($field['name'], $field['type']);
if ($sizeOK && $typeOK) {
$success = move_uploaded_file($field['tmp_name'], $this->_destination . $field['name']);
if ($success) {
$this->_messages[] = $field['name'] . ' uploaded successfully';
} else {
$this->_messages[] = 'Could not upload ' . $field['name'];
}
}
}
}
public function getMessages() {
return $this->_messages;
}
//测试错误级别
protected function checkError($filename, $error) {
switch ($error) {
case 0:
return true;
case 1:
case 2:
$this->_messages[] = "$filename exceeds maximum size: " . $this->getMaxSize();
return true;
case 3:
$this->_messages[] = "Error uploading $filename. Please try again.";
return false;
case 4:
$this->_messages[] = 'No file selected.';
return false;
default:
$this->_messages[] = "System error uploading $filename. Contact webmaster.";
return false;
}
}
//使用number_format()函数,将数字格式化。它通常带有两个参数:想要格式化的值和你希望数值具有的小数位数。
public function getMaxSize() {
return number_format($this->_max/1024, 1) . 'kB';
}
//检测大小
protected function checkSize($filename, $size) {
if ($size == 0) {
return false;
} elseif ($size > $this->_max) {
$this->_messages[] = "$filename exceeds maximum size: " . $this->getMaxSize();
return false;
} else {
return true;
}
}
//检测文件类型
protected function checkType($filename, $type) {
if (!in_array($type, $this->_permitted)) {
$this->_messages[] = "$filename is not a permitted type of file.";
return false;
} else {
return true;
}
}
}
五、修改受保护的属性
1、之前已经定义了MIME的图像类型,其实添加额外的类型也是很容易的。(arrsy)是类型转换运算符,将它后面的变量转换为一种特定的类型。array_merge()函数要求两个参数都是数组。此函数合并数组并返回合并后的数组。
//添加其它上传文档的类型
public function addPermittedTypes($types){
$types = (array)$types; //(arrsy)是类型转换运算符,将它后面的变量转换为一种特定的类型。
$this->isValidMime($types); //自定义函数isValidMime将检测添加的文档类型是否符合要求。
$this->_permitted = array_merge($this->_permitted,$types); //array_merge()函数要求两个参数都是数组。此函数合并数组并返回合并后的数组。
}
2、有些情况是替换所支持的MIME类型的现有列表
//替换所支持的MIME类型的现有列表
public function setPermittedTypes($types){
$types = (array)$types;
$this->isValidMime($types);
$this->_permitted = $types;
}
3、前面两个函数都调用isValidMime()方法。自定义文档类型检测函数,每个添加的类型必须是库事先设定的一种。
//自定义文档类型检测函数,每个添加的类型必须是库事先设定的一种。
protected function isValidMime($types){
$alsoValid = array('image/tiff','application/pdf','text/plain','text/rtf');
$valid = array_merge($this->_permitted,$alsoValid);//valid存储着MIME的合法类型
foreach ($types as $type) {
if(!in_array($type, $valid)){
throw new Exception("$type is not a permitted MIME type");
}
}
}
4、测试addPermittedTypes()方法
<?php
// set the maximum upload size in bytes
$max = 51200;
if (isset($_POST['upload'])) {
// define the path to the upload folder
$destination = 'C:/upload_test/';
require_once('classes/Ps2/Upload.php');
try {
$upload = new Ps2_Upload($destination);
$upload->setMaxSize($max);
$upload->addPermittedTypes(array('application/pdf','text/plain'));
$upload->move();
$result = $upload->getMessages();
} catch (Exception $e) {
echo $e->getMessage();
}
}
?>
六、防止文件被覆盖。
PHP会自动覆盖现有的文件,不会给出警告。我们通过添加一个选项,在上传文件的文件扩展名前面插入一个数字来避免覆盖现有的同名文件。
因为空格绝不应该在Web服务器的文件名和文件夹名中使用,所以我们将文件名中的所有空格用下划线替换掉。我想应该URL中不允许有空格的原因吧。
1、自定义函数,包括两个参数:文件名和确定是否覆盖现有文件的Boolean变量。
//检测文件名
protected function checkName($name,$overwrite){
$nospaces = str_replace(' ', '_', $name); //函数含有三个参数,要替换的字符,用来替换的字符,想要更新的字符串
if($nospaces != $name){
$this->_renamed = true;
}
if(!$overwrite){
//如果文件已经存在,重命名它
}
2、如果另一个同名文件已经存在,则添加重命名文件的代码。
//检测文件名
protected function checkName($name,$overwrite){
$nospaces = str_replace(' ', '_', $name); //函数含有三个参数,要替换的字符,用来替换的字符,想要更新的字符串
if($nospaces != $name){
$this->_renamed = true;
}
if(!$overwrite){
//如果文件已经存在,重命名它
$existing = scandir($this->_destination); //scandir()函数,返回一个目录(文件夹)中的所有文件和文件夹构成的数组,并将其存储在$existing数组中。
if(in_array($nospaces, $existing)){
$dot = strrpos($nospaces,'.'); //strrpos()函数,通过从字符串的结尾开始搜索查找字符的位置。
if($dot){
$base = substr($nospaces, 0 ,$dot); //substr()函数带有三个或两个函数。如果三个参数都被使用,它返回一个子字符串,这个字符串从由第二个参数指定的位置开始,截取长度是由第三个参数确定的
$extension = substr($nospaces, $dot); //substr()函数使用两个参数,则返回从第二个参数指定的位置开始到字符串结尾的子字符串。
}else{
$base = $nospaces; //如果$dot为false,则全名被存储在$base中,并且$extension是一个空字符串。
$extension = '';
}
$i = 1;//首先将$1初始化为1,然后利用do...while循环从$base、下划线、$i和$extension构建一个新名称
do{
$nospaces = $base . '_' . $i++ . $extension;
}while (in_array($nospaces, $extension)); //直到新名称在数组中没有出现才退出循环。
$this->_renamed = true;
}
}
return $nospaces;
}
3、现在你需要将move()方法修改为调用checkName()。
//将文件传递给move_uploaded_file()之前检查它的有效性
public function move($overwrite = false) {
$field = current($this->_uploaded);
$OK = $this->checkError($field['name'], $field['error']);//将文件传递给move_uploaded_file()之前检查它的有效性。
if ($OK) {
$sizeOK = $this->checkSize($field['name'], $field['size']);
$typeOK = $this->checkType($field['name'], $field['type']);
if ($sizeOK && $typeOK) {
$name = $this->checkName($field['name'],$overwrite);
$success = move_uploaded_file($field['tmp_name'], $this->_destination . $name);
if ($success) {
$messages = $field['name'] . ' uploaded successfully';
if($this->_renamed){
$messages .= "and renamed $name";
}
$this->_messages[] = $messages;
} else {
$this->_messages[] = 'Could not upload ' . $field['name'];
}
}
}
}
//将文件传递给move_uploaded_file()之前检查它的有效性
public function move($overwrite = false) {
$field = current($this->_uploaded);
$OK = $this->checkError($field['name'], $field['error']);//将文件传递给move_uploaded_file()之前检查它的有效性。
if ($OK) {
$sizeOK = $this->checkSize($field['name'], $field['size']);
$typeOK = $this->checkType($field['name'], $field['type']);
if ($sizeOK && $typeOK) {
$name = $this->checkName($field['name'],$overwrite);
$success = move_uploaded_file($field['tmp_name'], $this->_destination . $name);
if ($success) {
$messages = $field['name'] . ' uploaded successfully';
if($this->_renamed){
$messages .= "and renamed $name";
}
$this->_messages[] = $messages;
} else {
$this->_messages[] = 'Could not upload ' . $field['name'];
}
}
}
}
七、上传多个文件
其实,将multiple属性添加到文件字段<input>标签中,就可以在HTML5兼容的浏览器中选择多个文件。但是在较老的不支持HTML5的浏览器中要支持多个文件 上传,就要在表单中添加额外的文件字段。
1、由于$_FILES是多维数组,因此它具有处理多个文件上传的能力。除了将multiple属性添到<input>标签之外,你还需要将一对空的方括号添加到name属性中。但是,这样只有部分浏览器支持(因为IE9以前的版本都不支持multiple属性)。如果你需要支持较老的浏览器,可以省略multiple属性,不管你你想同时上传多少个文件,都可以为它们创建单独的文件输入字段。为每个<input>标签提供相同的name属性后面紧跟方括号。
<input type="file" name="image[]" id="image" multiple>
2、通过检测name元素是否是数组,你就可以确定如何处理$_FILES数组。我们这里交move()拆成两部分。
3、新建一个受保护的方法processFile(),并把move()方法中的部分代码移到这里。
//将文件上传指定文件夹
protected function processFile($filename,$error,$size,$type,$tmp_name,$overwrite){
$OK = $this->checkError($filename, $error);//将文件传递给move_uploaded_file()之前检查它的有效性。
if ($OK) {
$sizeOK = $this->checkSize($filename,$size);
$typeOK = $this->checkType($filename,$type);
if ($sizeOK && $typeOK) {
$name = $this->checkName($filename,$overwrite);
$success = move_uploaded_file($tmp_name, $this->_destination . $name);
if ($success) {
$messages = "$filename uploaded successfully";
if($this->_renamed){
$messages .= "and renamed $name";
}
$this->_messages[] = $messages;
} else {
$this->_messages[] = "Could not upload $filename";
}
}
}
}
4、修改move()方法,并将参数传递给processFile方法。
//将文件传递给move_uploaded_file()之前检查它的有效性
public function move($overwrite = false) {
$field = current($this->_uploaded);
if(is_array($field['name'])){ //条件语句检查$field是否是数组。
foreach ($field['name'] as $number => $filename) {
//处理多个文件上传
$this->_renamed = false;
$this->processFile($filename,$field['error'][$number],$field['size'][$number],$field['type'][$number],$field['tmp_name'][$number],$overwrite);
}
}else{
$this->processFile($field['name'],$field['error'],$field['size'],$field['type'],$field['tmp_name'],$overwrite);
}
}
最终的PHP代码:
<?php
class Ps2_Upload {
protected $_uploaded = array();
protected $_destination;
protected $_max = 51200;
protected $_messages = array();
protected $_permitted = array('image/gif',
'image/jpeg',
'image/pjpeg',
'image/png');
protected $_renamed = false;
//初始化函数
public function __construct($path) {
if (!is_dir($path) || !is_writable($path)) {
throw new Exception("$path must be a valid, writable directory.");
}
$this->_destination = $path;
$this->_uploaded = $_FILES;
}
//将文件传递给move_uploaded_file()之前检查它的有效性
public function move($overwrite = false) {
$field = current($this->_uploaded);
if(is_array($field['name'])){ //条件语句检查$field是否是数组。
foreach ($field['name'] as $number => $filename) {
//处理多个文件上传
$this->_renamed = false;
$this->processFile($filename,$field['error'][$number],$field['size'][$number],$field['type'][$number],$field['tmp_name'][$number],$overwrite);
}
}else{
$this->processFile($field['name'],$field['error'],$field['size'],$field['type'],$field['tmp_name'],$overwrite);
}
}
public function getMessages() {
return $this->_messages;
}
//测试错误级别
protected function checkError($filename, $error) {
switch ($error) {
case 0:
return true;
case 1:
case 2:
$this->_messages[] = "$filename exceeds maximum size: " . $this->getMaxSize();
return true;
case 3:
$this->_messages[] = "Error uploading $filename. Please try again.";
return false;
case 4:
$this->_messages[] = 'No file selected.';
return false;
default:
$this->_messages[] = "System error uploading $filename. Contact webmaster.";
return false;
}
}
//使用number_format()函数,将数字格式化。它通常带有两个参数:想要格式化的值和你希望数值具有的小数位数。
public function getMaxSize() {
return number_format($this->_max/1024, 1) . 'kB';
}
//检测大小
protected function checkSize($filename, $size) {
if ($size == 0) {
return false;
} elseif ($size > $this->_max) {
$this->_messages[] = "$filename exceeds maximum size: " . $this->getMaxSize();
return false;
} else {
return true;
}
}
//检测文件类型
protected function checkType($filename, $type) {
if(empty($type)){
return false;
}else if (!in_array($type, $this->_permitted)) {
$this->_messages[] = "$filename is not a permitted type of file.";
return false;
} else {
return true;
}
}
//添加其它上传文档的类型
public function addPermittedTypes($types){
$types = (array)$types; //(arrsy)是类型转换运算符,将它后面的变量转换为一种特定的类型。
$this->isValidMime($types); //自定义函数isValidMime将检测添加的文档类型是否符合要求。
$this->_permitted = array_merge($this->_permitted,$types); //array_merge()函数要求两个参数都是数组。此函数合并数组并返回合并后的数组。
}
//替换所支持的MIME类型的现有列表
public function setPermittedTypes($types){
$types = (array)$types;
$this->isValidMime($types);
$this->_permitted = $types;
}
//自定义文档类型检测函数,每个添加的类型必须是库事先设定的一种。
protected function isValidMime($types){
$alsoValid = array('image/tiff','application/pdf','text/plain','text/rtf');
$valid = array_merge($this->_permitted,$alsoValid);//valid存储着MIME的合法类型
foreach ($types as $type) {
if(!in_array($type, $valid)){
throw new Exception("$type is not a permitted MIME type");
}
}
}
//修改支持大小最大值的公共方法
public function setMaxSize($num){
if(!is_numeric($num)){ //is_numeric()函数检查它是否是数字
throw new Exception("Maximum size must be a number.");
}
$this->_max = (int)$num; //转换为整型
}
//检测文件名
protected function checkName($name,$overwrite){
$nospaces = str_replace(' ', '_', $name); //函数含有三个参数,要替换的字符,用来替换的字符,想要更新的字符串
if($nospaces != $name){
$this->_renamed = true;
}
if(!$overwrite){
//如果文件已经存在,重命名它
$existing = scandir($this->_destination); //scandir()函数,返回一个目录(文件夹)中的所有文件和文件夹构成的数组,并将其存储在$existing数组中。
if(in_array($nospaces, $existing)){
$dot = strrpos($nospaces,'.'); //strrpos()函数,通过从字符串的结尾开始搜索查找字符的位置。
if($dot){
$base = substr($nospaces, 0 ,$dot); //substr()函数带有三个或两个函数。如果三个参数都被使用,它返回一个子字符串,这个字符串从由第二个参数指定的位置开始,截取长度是由第三个参数确定的
$extension = substr($nospaces, $dot); //substr()函数使用两个参数,则返回从第二个参数指定的位置开始到字符串结尾的子字符串。
}else{
$base = $nospaces; //如果$dot为false,则全名被存储在$base中,并且$extension是一个空字符串。
$extension = '';
}
$i = 1;//首先将$1初始化为1,然后利用do...while循环从$base、下划线、$i和$extension构建一个新名称
do{
$nospaces = $base . '_' . $i++ . $extension;
}while (in_array($nospaces, $existing)); //直到新名称在数组中没有出现才退出循环。
$this->_renamed = true;
}
}
return $nospaces;
}
//将文件上传指定文件夹
protected function processFile($filename,$error,$size,$type,$tmp_name,$overwrite){
$OK = $this->checkError($filename, $error);//将文件传递给move_uploaded_file()之前检查它的有效性。
if ($OK) {
$sizeOK = $this->checkSize($filename,$size);
$typeOK = $this->checkType($filename,$type);
if ($sizeOK && $typeOK) {
$name = $this->checkName($filename,$overwrite);
$success = move_uploaded_file($tmp_name, $this->_destination . $name);
if ($success) {
$messages = "$filename uploaded successfully";
if($this->_renamed){
$messages .= "and renamed $name";
}
$this->_messages[] = $messages;
} else {
$this->_messages[] = "Could not upload $filename";
}
}
}
}
}
最终的HTML代码:
<?php
// set the maximum upload size in bytes
$max = 51200;
if (isset($_POST['upload'])) {
// define the path to the upload folder
$destination = 'C:/upload_test/';
require_once('classes/Ps2/Upload.php');
try {
$upload = new Ps2_Upload($destination);
$upload->setMaxSize($max);
$upload->addPermittedTypes(array('application/pdf','text/plain'));
$upload->move();
$result = $upload->getMessages();
} catch (Exception $e) {
echo $e->getMessage();
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8"/>
<title>Upload File</title>
</head>
<body>
<?php
if (isset($result)) {
echo '<ul>';
foreach ($result as $message) {
echo "<li>$message</li>";
}
echo '</ul>';
}
?>
<form action="" method="post" enctype="multipart/form-data" id="uploadImage">
<p>
<label for="image">Upload image:</label>
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $max; ?>">
<input type="file" name="image[]" id="image" multiple>
</p>
<p>
<input type="submit" name="upload" id="upload" value="Upload">
</p>
</form>
</body>
</html>