最近我完成了一個用PHP做的檔案上傳專案「PHP File Host」,順便學習了PHP框架Fat-Free Framework、PHP資料庫函式庫RedBeanPHP、前端檔案上傳工具jQuery File Upload跟前端界面Bootstrap等技術。以下說明這個專案內容。
I wrote a PHP project “PHP File Host” for cross origin file uploading. In this project, I try some new technology include Fat-Free Framework, RedBeanPHP, Bootstrap and jQuery File Upload. Following is introduction of this project.
專案由來 / Project Introduction
原本我的KALS專案並不具備檔案上傳功能(其實一開始規劃時有啦,但是一直沒有實作),但最近開始有了這方面的需求。然而檔案上傳乍聽之下很簡單,但是在跨網域(Cross Origin)的情境中,卻不太容易實作。
另一方面,簡單的檔案上傳應用實作常常會有幾個問題:
- 實體檔案管理的問題:檔案存放在哪哩?伺服器空間足夠嗎?
- 檔案重複問題:如何有效率地降低檔案的使用空間?
- 檔案名稱問題:遇到不支援的檔案名稱編碼,存到伺服器的檔案系統時會造成亂碼的問題。
因此我想要做一個簡單的檔案上傳應用網站。這個只做一件事情:支援跨網域的檔案上傳、然後的到一個下載網址。這樣就夠了。這就是PHP File Host的由來。
專案內容 / Project
我把最近對PHP File Host的報告彙整成為一個投影片,裡面有簡單的功能介紹:
特色 / Features
PHP File Host的特色在於:
- 運作環境:以PHP架設,資料庫預設使用SQLite,但是不需要額外配置資料庫。
- 跨網域檔案上傳:支援以JSONP上傳與jQuery File Upload上傳
- 避免儲存重複檔案:以MD5特徵碼來辨識檔案內容,避免儲存相同檔案。
- 完整保留檔案名稱:以資料庫儲存檔案名稱,並由程式負責從header指定下載檔案名稱,因此不會下載到亂碼的檔名。
相關技術 / Technology
裡面主要用到幾種技術,在此我也聊一下使用這些技術的心得。
Fat-Free Framework (F3)
Fat-Free Framework (F3)是一個PHP框架。不過在講F3之前,我想先聊一下CodeIgniter。
在開發KALS時,我主要使用的PHP框架是CodeIgniter (CI)。CI大量參考Ruby on Rails的理念,大量遵守「約定優於配置」(convention over configuration)的準則。特別是對於routing功能來說,要連到指定網址就得在特定的檔案結構中撰寫相對應的PHP類別。
一開始我覺得這也不錯,大家的都能遵守約定的話,開發就能夠維持一致性。但事實上是為了這這個約定,CI限制了大量的靈活性。常常會發現要接手專案的新手要花很多時間來瞭解routing的邏輯,而且無法自由指定routing中的變數與類別也很令人覺得限制很大。最致命的就是不能支援JSONP的呼叫模式,難以跟jQuery.getJSON()搭配運用。
雖然KALS的CI被我大改之後變得可以支援JSONP,但我不覺得這是一種理想的做法。所以當我這次要開發這個專門支援跨網域檔案上傳的PHP File Host時,我就毅然決然換了另一個PHP框架。
我花了一點時間嘗試不同的PHP框架,不過後來找到了F3。這個專案特色是檔案看起來不會太複雜,特別是與龐大的CodeIgniter相比。
F3一些零星的功能不多,但是主要功能卻比CI好用很多。
F3的routing是由設定檔控制,寫法跟Node.js的express框架很像。這符合我們一般使用的概念:從URI追溯檔案位置。而不是像CI那樣,得先瞭解約定才能知道檔案的位置。使用配置設定來規範routing這點看起來像是違反了「約定優於配置」,但是從另一個角度來看,這也是讓「使用者」(利用URI使用系統)跟「開發者」(使用伺服器上的檔案配置)脫鉤的一種好方法。CI那種routing規範實在是太過糾結,用起來綁手綁腳的。
而F3的routing也支援分辨GET (查詢)、POST (新增)、PUT (更新)、DELETE (刪除)等REST API會使用的四種方法。不過要注意到,若針對同一URI使用GET跟POST等多種方法,最後變數只會取得使用GET這個而已。這是比較令人困擾的地方,我得再研究看看。
此外,我喜歡F3用擴增HTML標籤的方式來建立樣板,輸出的樣板能夠直接指定MIME Type為JavaScript這點也很不錯,這對JSONP支援良好,也可以輕易使用現在流行的Markdown程式語言。相較之下,CI的樣板只能說是原始人。不過F3預設限制「同源使用」(same-origin),為此得額外宣告以下header才行讓其他網站跨網域開啟F3專案:
header('X-Frame-Options: ');
CI提供了大量零星的函式(helper),讓我們能夠簡單地處理很多小東西。F3並沒有這麼多helper,但是它把很多常用的系統與環境資訊都寫在框架的系統變數裡面。習慣之後也還算好用,但我比較喜歡helper的函式形式。
在資料庫的使用上,F3的資料庫也跟CodeIgniter的Active Record一樣,都是使用ORM (Object-Relation Mapping)的方式操作資料庫。而F3多了一些NoSQL資料庫的支援,像是MongoDB跟Jig。
雖然這些ORM用起來不錯,但是我這次更想使用另一種資料庫函式庫更感興趣,那就是RedBeanPHP。
RedBeanPHP
RedBeanPHP是一個PHP資料庫函式庫,使用時只要導入它一個PHP主要檔案即可。
他在使用上跟很多ORM函式庫一樣,可以把資料表當作一個類別,裡面的一列當作是一個物件來使用。但是最大的差別在於,RedBeanPHP是不需要預先設定資料表(schemaless)的架構。
舉例來說,今天我們有一種類別叫做Book,那我們就用RedBeanPHP建立一個類別叫做「Book」的物件,然後設定其中的屬性「title」跟「author」後儲存,這樣子資料庫中就會自動幫我們把相關的table與field都設定好,連field的資料形態都會與物件屬性的形態直接相對應。
未來如果這個Book想要增加第三種屬性「price」,那就在程式中加入「price」,儲存,這樣子資料表就會多一個price的欄位。
這樣子的好處在於,我們不需要在配置程式碼之餘還要煩惱如何配置資料庫。RedBeanPHP預設採用SQLite,但也可以支援主流的關聯式資料庫,如PostgreSQL跟MySQL。
我使用RedBeanPHP在PHP File Host中儲存檔案資料,操作起來非常容易上手,而且令人驚訝地好用。
有人抱怨RedBeanPHP的資料庫查詢速度過慢,這點可能要謹慎評估。但PHP File Host少量應用看來是沒有這個問題。
Bootstrap
這次我也一併改進了前端的界面。跟以往一樣,比起重頭開始設計網頁界面,我比較偏好從既有的網頁範本開始修改。難得這次機會,我也就從知名的客戶端技術Bootstrap的範本Landing Page開始改起。
使用Bootstrap的目的包括:
- 支援RWD (Responsive Web Design):不論電腦、平板、手機等不同螢幕大小的裝置,網頁都應該自動最佳化調整版面。這點可以靠Bootstrap的Grid system來調整。
- 一致且美觀的元件:Bootstrap的選單、按鈕、讀取條非常好看。我還蠻喜歡它的Default、Primary、Success、Info、Warning、Danger、Link的通用分類與對應顏色。
- 豐富的圖示:不光是Bootstrap本身提供了大量的圖示(Glyphicons),Landing Page範本還用了更多元的Font Awesome圖示,基本功能操作真的是不需要額外在準備其他圖片了。
最後完成的結果在Am I Responsive?上看起來就是這樣,開頭的圖片會按照視窗畫面去做調整,頂端選單列也會在小螢幕中自動縮成一個按鈕,成果很不錯。
雖然我老是做一些伺服器端的專案,但是作為網頁相關的程式設計師,前端界面技術自然也不能生疏。PHP File Host雖然只是一個小小的網站,但是做起來還是令我挺開心的。
jQuery File Upload
我這次也加入了jQuery File Upload來改善檔案上傳的介面。這個套件可以用簡單的方式來設定檔案上傳的功能,讓我們輕易加入以下功能:
- 點選按鈕、選擇檔案、馬上上傳
- 上傳進度條
- 拖曳至指定區域上傳
- 剪貼上傳
我很喜歡這些方便的操作,傳統點選檔案按鈕的方式實在是太過麻煩。
另一方面,我本來還蠻煩惱怎麼用jQuery File Upload來進行跨網域傳送檔案,卻意外發現jQuery File Upload使用了HTML5元件postMessage來傳輸檔案的方式,比我以前提出的JSONP跨網域檔案上傳還要好用很多,真讓我驚豔。
不過最後我要在其他專案使用jQuery File Upload設定上傳功能時,卻發現它使用的是jQuery 1.11,與KALS專案的jQuery 1.4有很大的差別。加上jQuery File Upload也要使用jQuery UI等工具,導致它與其他功能互相衝突,最後我還是放棄在KALS專案使用jQuery File Upload而使用原本的JSONP跨網域檔案上傳方式。
向DSpace致敬 / Salute to DSpace
最後也提一下PHP File Host內部檔案儲存的方式,這部分我大量地參考了DSpace保存檔案的方式。
DSpace依照檔案的MD5特徵碼作為檔案名稱與實體位置的設定,因此每個檔案都長得像是「01f7b24e629cc23e369983994d0b8fbe」。檔案的名稱、MIME Type等相關資訊則是寫在資料庫中,連同上傳者、上傳時間等操作記錄也一併分開儲存。
這樣做可以改善檔案重複儲存、避免檔案名稱編碼不支援等問題,但是檔案管理 (特別是刪除)跟下載就成為另一個技術上的重點。目前PHP File Host還沒有做的刪除功能,就只是一直擺放資料,然後以供人下載而已。
利用Base56面碼縮短網址 / Shorten URL by Base56 Encoding
喔對了,為了避免網址過長,我還特別用了Base56將數字ID以「0123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ_-.~」等網址允許的字元進行編碼。即使檔案的編號很大,網址長度仍然不會很誇張地漲大。這種做法我也挺喜歡的。
這些大致上就是PHP File Host使用到的技術。
結語:過時的技術? / Conclusion: Outdated Technology?
剛好最近也看到一篇分析報導:「AngelList 分析:越好的公司越喜歡用 Python,越差的公司越愛用 PHP」。該分析將公司分成Okay、Good、Great三級,然後看公司使用的程式語言。
其中伺服器端PHP排行第三、客戶端Bootstrap排行第五(但是RoR我不認為是客戶端技術)、資料庫SQLite榜上無名。雖然直接看圖表起來也沒啥不好,但我現在才學這些技術,的確是比很多人慢了許多。
之後有機會的話,我想要研究伺服器端的Node.js,然後在單一頁面應用上繼續精進AngularJS (之前寫了一個批次開啟網頁的小應用,蠻好上手的)。久違地學習新的技術,真的很令人開心呢。
(more...)
Comments