經驗交流

處理使用者上傳的檔案

問題說明
 PHP 可以處理使用者上傳的檔案。不過,檔案傳上來之後,要放在哪兒?我想不外乎這兩種方式:一是將它存入實體的目錄之中,一是把它存放於資料庫裡頭。
 這兩種方式分別要怎麼做?哪一種方式比較好呢?以下是我個人的做法與看法。

我的做法
 首先,我們先設計一個可以供使用者上傳檔案的介面。由於在存入資料庫時,我希望以 TEXT 這種欄位型態(能容納 64KB)來儲存檔案內容,所以檔案不能太大;此外,由於檔案內容必須以 MIME base64 的方式編碼才能順利存入資料庫,而編碼結果會使內容大小增加 30% 左右的空間,所以檔案本身需控制在 48KB 以內才行(如果您需要更大的儲存空間,請改用 MEDIUMTEXT 或 LONGTEXT 型態)。
 因此,在設計上傳介面時,除了以文字提醒使用者之外,我還在表單之中加入一個名為「MAX_FILE_SIZE」的參數,其值為 49152(48KB)。此外,表單的「method」與「enctype」之設定也要特別加以注意(請參閱「表單處理」中之「Form」一節之說明)
 以下這個範例是個 HTML 格式的檔案,您可以自行檢視其原始碼。

「上傳介面」範例
 由於在檔案上傳介面中,我以「MyFile」當做上傳元件的名稱,所以我們可以用 $_FILES['MyFile']['tmp_name']、$_FILES['MyFile']['name'] 與 $_FILES['MyFile']['size'],分別取得該檔案的暫存路徑、原始檔名與檔案大小。
 若使用者前後兩次不慎上傳同名的檔案,將會發生較早上傳之檔案被取代的現象。所以,我想以當時的「年月日時分秒」來當做新的主檔名,而原來的副檔名予以保留。
 接下來,我分別說明兩種儲存作業方式:
《存入實體目錄》
 我們先指定欲存入檔案的目錄為何,這個目錄的讀寫權限必須全開才行(設為 777),如此一來,PHP 在執行時,才有權限將檔案寫入其中。
 接下來,我們使用 copy( ) 將檔案寫入上述目錄中即可。在存檔成功之後,建議您利用資料庫記錄其檔名等資料,以便日後列出清單供使用者下載。
「存入實體目錄」之原始碼
《存入資料庫》
 檔案被上傳到伺服器上時,會先暫存在某個特定的目錄中,我們可以使用 fopen( ) 與 fread( ) 這兩個函數,開啟並取得檔案內容。由於檔案本身的內容會干擾 SQL 敘述的執行,所以我們必須將它先經過 MIME base64 的方式編碼處理,然後再存入資料庫中。
「存入資料庫」之原始碼
 將檔案存入資料庫中,無可避免地將使得資料表的「體積」大增,如果我們在編碼之前,先將檔案以 ZLIB 格式壓縮的話,多少會有一些「瘦身」的效果。倘若您的檔案是那種「虛胖」的 Word 格式,壓縮成效將更為可觀。以下這支程式與上一個類似,就多了一道壓縮的程序。
「存入資料庫(壓縮版)」之原始碼

 然後,怎麼開啟這個檔案呢?
《存入實體目錄》
 這個檔案要開放其他使用者下載時,請他們輸入「http://您的站名/存放的目錄/檔名」即可。
《存入資料庫》
 由於這個檔案並非以實體型態存在,所以必須透過程式將檔案內容自資料庫中取出,並合成為原來的實體型態,這樣才能開啟它。
 我在將檔案存入資料庫時,以當時的「年月日時分秒」賦予它一個識別的代號(file_id)。假設這個代號是「20021124145606」,配合下方這支程式(X_23_get_file.php),我們可以用「http://您的站名/X_23_get_file.php?file_id=20021124145606」這樣的方式取得檔案。
「取得檔案」之原始碼
 若檔案經過壓縮程序才存入資料庫的話,請改用以下這支程式來開啟它。
「取得檔案(壓縮版)」之原始碼

 您或許會對這兩種做法孰優孰劣感到好奇,其實它們各有優缺點,而且正好相反。
《存入實體目錄》
 因為可以使用 URL 的方式直接下載檔案,所以能夠在 proxy server 或個人電腦中產生 cache 的效果,同時也能減輕資料庫的負擔,這是它的優點。但是,只要有人猜中 URL 的話,檔案將無保密性可言。
《存入資料庫》
 因為檔案無法以 URL 的方式直接取得,所以只要在上述「取得檔案」的程式中加入安全性檢查的話,就能有效提升檔案的保密性。但是因為無法產生 cache 的效果,所以資料庫之負擔是需要考量的問題。
經驗交流