Menu

PHP + Ajax 追蹤檔案上傳進度

PHP V5.2 為開發人員添加了 hook 以利用實時跟蹤文件上傳進度的功能。本文是  系列文章(共五部分)的第 5 部分,將向您展示如何監視文件上傳並相應地編寫代碼,以及如何創建 PHP 進度條。

Web 2.0 是 Internet 上最炙手可熱的時髦字眼,投資者紛紛把資金投入到涉及這項技術的投資項目中。數以百萬計的 Web 站點和應用程序覆蓋的描述性術語有很多。使用 Web 2.0,我們將描述一類 Web 站點,這些站點都提供了瞭解 Internet 上數以百萬計用戶心聲的途徑。與眾不同之處在於它們全都為用戶提供了交流和分享與共同利益相關的觀點和數據的場所,這些站點可以快速生成大量內容。

每個用戶都將提供某種內容 —— 評論咖啡店、上班路線等。YouTube 在這點上是一個優秀示例,為人們提供一個空間可以上傳視頻並使其他用戶可以觀看這些視頻並提供反饋。YouTube 是 Web 2.0 奉行者的新寵,值得關注的是到目前為止 YouTube 的流行度上升得比 Internet 中的任何一個站點都要快。這種流行度可以歸因於大量各式各樣的內容,以及能夠讓用戶以留言的形式發表自己對內容的看法。並且不僅可以留言,用戶甚至還可以 上傳與視頻相對應的視頻留言。

文本領域

許多接收文件的 Web 站點都會在文本框旁邊安置令人厭惡的 Browse 按鈕,強制要求用戶一次上傳一個文件。這可能要花費很長時間,尤其是在以小型文件組的形式提供視頻、甚至照片或其他項目的情況下。由於每個文件都必須單獨 上傳,因此可能會十分繁瑣。假定上傳超大型文件所花的時間會使耐心不足的用戶感到難以容忍,那麼給這些用戶提供積極反饋避免他們放棄並走開將十分重要。

幸運的是,PHP V5.2 引入到文件上傳過程中的新 hook 使我們可以向用戶實時顯示上傳的進展情況。在本文中,將使用 PHP V5.2 為用戶創建一個進度條。


完整說明

如果安裝並配置了正確的庫,則 PHP V5.2 中的新 「hook」 實際上是在文件傳輸過程中可獲得的數據點。這些新 hook 將使用一種稱為 Alternative PHP Cache 的功能。當 PHP 腳本收到一個上傳文件時,解釋程序將自動檢查 $_POST array 以查找名為 APC_UPLOAD_PROGRESS 的隱藏字段,它將成為緩存變量,存儲關於上傳的信息以便腳本可以訪問上傳文件。當此信息已被緩存並且隨時可以訪問後,可以給用戶提供可視化反饋,從而提高用戶體驗。

我們將介紹 HTML 表單中的 APC 代碼的實現,以及如何在 PHP 中識別該實現及如何訪問緩存的信息。表示此數據有很多方法:從 Ajax 到 FLEX,但是我們要關注的是準備這些前端技術所需的訪問數據的方法。

設置

默 認情況下,PHP V5.2 中的 APC 不啟用。由於新 hook 是 APC 的一部分,因此需要確保安裝擴展並使其可用於 PHP 解釋程序。這將通過下載 php_apc 擴展文件來完成。在我們的例子中,將使用 WAMP 安裝,這是包括 Apache 和 MySQL 的免費獲得打包的 PHP for Windows®。它提供了友好的用戶界面並且由於擁有支持配置選項的菜單而十分易於管理。

要在 WAMP 上設置 APC,請執行以下步驟:

  1. 要下載庫和 WAMP,請參閱。
  2. 安裝 WAMP。
  3. 把 php_apc.dll 文件放到 PHP 的擴展文件夾中。默認情況下,此文件夾為 <wamproot>/php/ext。
  4. 使用系統盤 WAMP 菜單來選擇 PHP settings>PHP Extensions>Add Extension
  5. 在彈出的命令行界面中,鍵入 php_apc.dll 並按 Enter
  6. 使用文本編輯器,打開 <wamproot>/php/php.ini 並添加代碼行 apc.rfc1867 = on(添加到任何位置都可以)。如果要嘗試在本地進行測試並計劃上傳大型文件以便可以實際看到進度,則還需要添加以下指令:apc.max_file_size = 200Mupload_max_filesize = 200Mpost_max_size = 200M。請不要在活動的生成服務器上執行此操作,不過,不這樣做很可能用盡帶寬和磁盤空間配額,更不必說會降低其他人的訪問速度。
  7. 重新啟動 PHP。

APC 現在應當已設置並被初始化。APC 的 RFC1867 特性 —— 使您可以跟蹤文件上傳的特性 —— 現在應當已被啟用為選項,並且應當準備好探究文件上傳以啟用實時狀態。


可接收文件的帳戶

要接收文件,必須先設置接收文件的表單。很方便的是,HTML 附帶了文件的標準字段類型。同所有 HTML 表單字段一樣,它在邏輯上被命名為類型 file。默認情況下,附帶了顯示在塊右側的便捷 Browse 按鈕。
清單 1. upload.php 的 HTML 表單


action="target.php" method="POST">

<input type="hidden" name="APC_UPLOAD_PROGRESS"
id="progress_key" value=""/>

<input type="file" id="test_file" name="test_file"/><br/>

<input onclick="window.parent.startProgress(); return true;"
type="submit" value="Upload!"/>

</form>

需要為此表單創建一個 PHP 頁面,因為需要使用惟一密鑰來跟蹤上傳。最後,它將是用於調用此頁面作為 GET 值的 URL 的一部分。此數字將是稍後將檢索的 APC 緩存條目密鑰的值。要傳遞該值,表單字段需要有一個擁有特殊名稱的隱藏字段,使 APC 知道它需要保存文件上傳狀態。此字段被稱為 APC_UPLOAD_PROGRESS。這是前述的啟動緩存過程的 hook。為確保 PHP 可以訪問緩存中的正確條目,我們使用檢索到的惟一 ID 作為隱藏字段的值,從而創建該值的密鑰。用戶提交表單後 —— 我們將簡短地處理提交按鈕 —— 瀏覽器將把文件和密鑰作為發送給服務器的 POST 數據的一部分進行發送。

裝入到瀏覽器中後,此頁面應當提供一個非常簡單的表單,如圖 1 所示:
圖 1. 上傳表單
上傳表單

要在不重新裝入整個頁面的情況下使用戶可以提交文件,需要把此表單嵌入到另一個文件的 iframe 中。如果嘗試僅使用表單操作頁面 (target.php) 來檢索數據,則無法看到任何緩存信息,因為在上傳完成之前頁面不會返回任何信息。鑑於這個原因,使用此新 hook 的最常見示例都是用 Ajax 編寫的。通過該方法,您可以提交表單並且還可以在同一個窗口中繼續檢查上傳的狀態而無需刷新。

要使腳本運行,需要繼續轉到一個包含頁面,該頁面將設置 iframe 並接收已上傳文件的信息。還需要使用一組 JavaScript 函數來為進度指示器獲得數據以及顯示進度指示器。
捕獲已接收的文件

提交表單中包括文件時,該文件將發送到服務器的臨時位置中,直至它被保存到永久位置。當它在臨時存儲設備中時,可以通過 $_FILES 關聯數組獲得它。使用 PHP 附帶的標準版本文件上傳函數,可以選擇路徑並將這些函數保存到服務器上,或者按自己的需要處理這些函數。
清單 2. target.php 文件

<?php  

if($_SERVER['REQUEST_METHOD']=='POST') {
move_uploaded_file($_FILES["test_file"]["tmp_name"],
"c:\\sw\\wamp\\www\\" . $_FILES["test_file"]["name"]);
echo "<p>File uploaded. Thank you!</p>";
}

?>

首先,查看來自表單的 POST 變量是否已被設定並表示我們已經收到了表單數據。如果收到表單數據,並且但願包括文件,則還應當有一個全局數組 $_FILES。把已上傳的文件移到安全位置,這取決於需要對其採取的操作。在本例中,只需把文件移到 \sw\wamp\www(當然,這是完全任意的位置。請隨意選擇一個所需位置)。完成該操作後,我們將感謝用戶。

在這裡包括實際文件處理主要是為了實現完整性。由於本文講述的是進度條,因此收到實際文件後如何處理它無關緊要。
製作進度條

還將需要一個返回實際上傳進度的腳本。清單 3 顯示了一個非常簡單的版本。
清單 3. getprogress.php 文件

<?php
if(isset($_GET['progress_key'])) {

$status = apc_fetch('upload_'.$_GET['progress_key']);
echo $status['current']/$status['total']*100;

}
?>

此腳本首先將查找 progress_key,它是先前討論的 $id 值(不必擔心,您馬上就將看到它的來源)。這將導致調用從 APC 緩存返回數據的 apc_fetch()。我們需要正確的文件信息,因此需要惟一 ID,在本文中表示為 $_GET['progress_key']。調用帶有 upload_xxxxxx 參數的 apc_fetch(),其中 xxxxxx 是惟一 ID;PHP 將自動預先追加 upload_ part。

獲得數據後,可以使用 JSON 擴展給信息設定一種更便於在 JavaScript 中使用的格式並返回整個對象(如果需要)。$status 對象是擁有以下字段的數組:

total
文件的總大小
current
到目前為止收到的文件數
rate
上傳速度(以字節每秒為單位)
filename
文件名
name
變量名
temp_filename
PHP 保存文件的臨時副本的位置
cancel_upload
上傳是已取消 (1),還是未取消 (0)
done
上傳是已完成 (1),還是尚未完成 (0)

在本例中,只需要完成百分比。您可以在自己的應用程序中選擇使用更多信息。
顯示進度條的 JavaScript

現在已經準備好開始構建實際的進度條。為了簡單起見,腳本將使用 CSS 創建一個用於模擬進度條並可以使用 JavaScript 進行控制的 div,如清單 4 所示:
清單 4. 主文件 progress.php

<html>
<head><title>Upload Example</title></head>
<body>

<script type="text/javascript">

var counter = 0;

function startProgress(){
document.getElementById("progressouter").style.display="block";
fire();
}

function fire(){
if (counter < 101){
document.getElementById("progressinner").style.width =
counter+"%";
counter++;
setTimeout("fire()",100);
}
}

</script>

<div id="progressouter" style=
"width: 500px; height: 20px; border: 6px solid red; display:none;">
<div id="progressinner" style=
"position: relative; height: 20px; background-color: purple; width: 0%; ">
</div>
</div>

<span onclick="startProgress()">Start me up!</span>

</body>
</html>

此頁面包含了兩個嵌套的 div 元素,外面的那個用作邊框。腳本將調整內部 div 相對於邊框的大小以顯示進度。當用戶單擊 Start me up! 文本時,startProgress() 腳本將調用 fire() 函數。該函數將檢查計數器的值,並且如果該值尚未超過 100,就把內部 div 設為外部 div 寬度的該百分比值。然後它將增加計數器的值並告訴瀏覽器每十分之一秒就執行一次全部上述過程。

結果將與圖 2 非常相似:
圖 2. 進度條腳本
進度條腳本

現在只需要有一種獲得腳本以更新寬度的方法,此寬度不是任意的數字而是完成百分比。


   

整合

現在剩下的只是要把所有內容 hook 到一起。您可以通過 progress.php 頁面來完成此操作。
清單 5. 最終的 progress.php 頁面

<?php
$id = uniqid("");
?>
<html>
<head><title>Upload Example</title></head>
<body>

<script src="http://maps.google.com/maps?file=api&v=2&key=<yourkeyhere>"
type="text/javascript"></script>

<script type="text/javascript">

function getProgress(){
GDownloadUrl("getprogress.php?progress_key=",
function(percent, responseCode) {
document.getElementById("progressinner").style.width = percent+"%";
if (percent < 100){
setTimeout("getProgress()", 100);
}
});

}

function startProgress(){
document.getElementById("progressouter").style.display="block";
setTimeout("getProgress()", 1000);
}

</script>

<iframe id="theframe" name="theframe"
src="upload.php?id="
style="border: none; height: 100px; width: 400px;" >
</iframe>
<br/><br/>

<div id="progressouter" style=
"width: 500px; height: 20px; border: 6px solid red; display:none;">
<div id="progressinner" style=
"position: relative; height: 20px; background-color: purple; width: 0%; ">
</div>
</div>

</body>
</html>

從底層開始向上層工作,我們已經添加了嵌入清單 1 中的 upload.php 腳本的 iframe,給它提供了在頁面頂部生成的惟一 ID。

現在,是否還記得該表單中的 Submit 按鈕?

<input onclick="window.parent.startProgress(); return true;"
type="submit" value="Upload!"/>

該按鈕將完成兩項工作。提交表單,像普通的 Submit 按鈕一樣;但在執行該操作之前,它將在主窗口中調用 startProgress() 腳本。startProgress() 腳本將告訴進度條顯示自身 —— 開始時無顯示屬性,然後告訴瀏覽器等待一秒,然後再執行 getProgress() 腳本。

現在,getProgress() 腳本將使事情變得有趣。記不記得在前面我說過將需要使用 Ajax 或某種類似的方法來檢查文件的進度?對,在本例中,表單將採用捷徑,調用來自 Google Maps API 庫的 GdownloadUrl() 函數(注意,表單將導入位於頁面頂部的庫。您將需要獲得自己的訪問此庫的密鑰,但是它是從 Google 免費獲取的)。

此 函數將下載 URL 的內容 —— 本例中為 getprogress.php 腳本 —— 並執行在其中定義的匿名函數。函數所接受的第一個參數是從 URL 返回的數據,本例中為百分比,以便使用它更新進度條。最後,如果文件尚未完成下載,則告訴瀏覽器每十分之一秒重試一次(在實際情況中,可能無法那麼快地執 行這些調用,但是瀏覽器將盡其所能進行操作)。

最終結果是頁面使用戶可以查看文件正被上傳的進度。
圖 3. Progress.php 的輸出
Progress.php 的輸出


 

結束語

在 Web 2.0 的世界裡,我們鼓勵用戶在 Web 站點中彼此提供信息和內容。作為開發人員,我們為人與人之間的這種開放、自由的數據交換創建了一個框架。雖然能實現這種功能的工具很早以前就有了,但是用 戶體驗還沒有達到所能具有的最佳程度。在本文中,您已經看到了向用戶提供實時反饋(尤其是為用戶上傳到站點的信息提供進度條)來提高用戶體驗和應用程序質 量的一些方法。

 關於APC 套件安裝說明

cd /usr/ports/www/pecl-APC
make install clean

安裝之後

************************************************************************
You may edit /usr/local/etc/php.ini to change this variables:

apc.enabled="1"
^^^ -> Default value

apc.shm_size="30"
^^^^ -> Default value

* More information on /usr/local/share/doc/APC/INSTALL

Then restart your web server and consult the output of phpinfo().
If there is an informational section for APC, the installation was
successful.
************************************************************************
遵照說明 編輯PHP.INI