使用說明 PHP V5.2 為開發人員添加了 hook 以利用即時追蹤檔案上傳進度的功能。 如果安裝並配置了正確的庫,則 PHP V5.2 中的新 “hook” 實際上是在檔案傳輸過程中可獲得的資料點。這些新 hook 將使用一種稱為 Alternative PHP Cache 的功能。當 PHP 指令碼收到一個上傳檔案時,解釋程式將自動檢查
$_POST array
以尋找名為 APC_UPLOAD_PROGRESS 的隱藏欄位,它將成為緩衝變數,儲存關於上傳的資訊以便指令碼可以訪問上傳檔案。 預設情況下,PHP V5.2 中的 APC 不啟用。由於新 hook 是 APC 的一部分,因此需要確保安裝擴充並使其可用於 PHP 解釋程式。這將通過下載 php_apc 擴充檔案來完成。 apc組件可以從這裡取得:http://pecl.php.net/package/apc 開啟 <wamproot>/php/php.ini 並添加程式碼
apc.rfc1867 = on
(添加到任何位置都可以)。如果要嘗試在本地進行測試並計划上傳大型檔案以便可以實際看到進度,則還需要添加以下指令:
apc.max_file_size = 200M
、
upload_max_filesize = 200M
和
post_max_size = 200M
。
可接收檔案的帳戶
要接收檔案,必須先設定接收檔案的表單。很方便的是,HTML 附帶了檔案的標準欄位類型。同所有 HTML 表單欄位一樣,它在邏輯上被命名為類型 file
。預設情況下,附帶了顯示在塊右側的便捷 Browse 按鈕。
清單 1. upload.php 的 HTML 表單
<?php $id = $_GET['id'];?><form enctype="multipart/form-data" id="upload_form" action="target.php" method="POST"><input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $id?>"/><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 檔案
<?phpif(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=<?php echo($id)?>", 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=<?php echo($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 的輸出