上傳文件。沒錯,除了上傳表單的文本,我們還能用表單傳文件。是直接將文件上傳到服務器嗎?當然不是,要認證的,“註冊用戶才能上傳文件”是個不錯的方法。另外,最好能建一個類來定義上傳,面向對象嘛!
一、基本上傳文件:(兩步完成)
第一步:將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>