在架構盛行的今天,MVC 也不再是神話。 經常聽到很多程式員討論哪個架構好,哪個架構不好, 其實 架構只是工具,沒有好與不好,只有適合與不適合,適合自己的就是最好的。
每次我面試應屆生時都會問他使用過什麼架構,並談談對這些架構的理解。 當面試有經驗的程式員時,會讓他自己寫一個架構出來。 其實也不是讓他編碼,只要有思路就 OK 了。 我覺得,如果一個有一年經驗的程式員連一個 Framework v0.0.1 都開發不出來的話,肯定是沒有深入理解一個架構。
前幾天 @phoenixg 說要自己寫個 MVC 架構。 而且他也確實不僅僅是說說而已,短短一個周末,這個架構雛形就神奇的出現在了 github 上。
這篇博文的名字是『自己動手設計 PHP MVC架構』, 所以本文不會涉及太多的編碼,文中出現的任何程式碼片段都是我直接在 vim 裡面敲的, 沒做任何測試,如果想使用文中代碼需自行測試。
跟隨本教程,將從零開始設計一個屬於自己的 MVC 架構。
我使用過 ZendFramwork、CodeIgniter,每個架構都有自己的優點和不足。 在寫本文之前,我又看了 Symfony、cakephp、MooPHP、doitphp 等的核心源碼, 下面說說我將把我的架構設計成什麼樣子,這一章主要討論 URL 的設計。
1. REST
在這個 REST 橫行的時代,如果一個架構不支援 REST,肯定被前衛程式員所瞧不起,所以本架構也要支援 REST。
第一個設計準則: 所有東西都是資源,資源有多種表現形式。
不管實際上存在的,還是抽象上的, 所有資源都會有一個不變的標識(ID),對資源的任何 API 操作都不應該改變資源的標識。
事實上,上面的這些完完全全是按照互連網的特性提出來的。
- 互連網中,一個 URL 就是一個資源;
- 資源的內容就是 HTML 頁面;
- 不管怎麼改 HTML 內容,URL 都不會改變;
- 資源之間通過 HTML 裡的串連聯絡起來;
- 每次擷取的時候,擷取到的都是完整的 HTML 內容。
比如
GET http://justjavac.com/users // 所有使用者GET http://justjavac.com/users/phper // 標識為phper的使用者
2. 副檔名
在此我不討論副檔名和檔案類型之間的關係,以及“副檔名只是約定,而檔案類型記錄在檔案頭”。
我通常把副檔名理解為“約定”,而不是檔案類型。 當我們請求一個 news.html 時,我們並不能確信它就是一個存在於伺服器上的news.html檔案, 它也可能是php檔案,也可能是jsp檔案,在nodejs流行的今天,它也可能是一個js檔案。 但不管頁面是如何產生的,有一點是明確的——最終我們得到了一個html文檔。
雖然rest不要求使用副檔名,但有人告訴我,如果在一個女生名字後面加一個.rmvb 的副檔名,將變得非常……因此本架構將支援副檔名,但是副檔名並是資源的一部分。
什麼意思呢?
還是前面的例子,所有使用者這個資源該如何表示呢? 用 url http://justjavac.com/users 就可以唯一標識,
而 副檔名可以用來標識資源的不同表現形式。
a、當我們請求 http://justjavac.com/users 時,架構將返回一個html文檔, 資料可能在表格中,也可能在
form 中,也可能在 div 中(如)。
b、當我們請求 http://justjavac.com/user.json 時,將返回 json 格式資料。
[ { "firstName" : "just", "lastName" : "javac", "userName" : "@justjavac" }, { "firstName" : "Tom", "lastName" : "Cat", "userName" : "@tomcat" }, ……]
c、當我們請求 http://justjavac.com/user.xml 時, 將返回 xml 格式的資料,xml 文檔可由
DTD 或者 XSD 定義。
d、如果我們想把所有使用者的列表發給管理員,或者列印出來呢?
可以直接存取 http://justjavac.com/user.xls,架構將會返回 Excel 試算表。 當我們高高興興把檔案下載下來,卻發現電腦沒有安裝
Excel,怎麼辦? 沒關係,我們還可以訪問http://justjavac.com/user.jpg,畢竟看圖工具我們還是有的。
用過 Google 短網址服務的同學都知道,比如我的網站 http://justjavac.com 的短網址是 http://goo.gl/JMQJ8,Google
還提供了二維碼錶示法,只需要在後面添加 .qr 例如 http://goo.gl/JMQJ8.qr。
taourl 也提供了一個很方便的功能,例如 我們想查看網址 http://taourl.com/7c1ug 的訪問情況,那麼只需要在網址最後面添加一個+號就可以了。
總之,不管用了什麼副檔名,將返回同一個資源,只是表現形式不同罷了。 這也就是經常所說的 資料 + 模板 = 輸出。
如果沒有副檔名呢?返回 HTML 文檔?
別忘了 http 請求的 Accept。 佈建要求頭的 Accept: application/x-excel 我們依然可以得到一個試算表。
甚至當我們訪問某個使用者時, http://justjavac.com/user/justjavac,我們可以使用 Accept:
text/x-vcard,如果不知道嘛意思,自己Google去。
下面說說設計模式,在這個功能上,可以用一個適配器模式,根據不同的副檔名選擇不同的適配器,執行不同的功能,最後提供相同的介面,具體實現就不多說了。
3. 多語言支援
@TODO 多語言支援的 url 結構設計
4. 充分利用 HTTP
和請求有關的錯誤和其他重要的狀態資訊怎麼辦呢?
簡單,使用 HTTP 的狀態代碼! 通過使用 HTTP 狀態代碼,你不需要為你的介面想出 error/success 規則,它已經為你做好。
比如:假如一個消費者提交資料(POST)到 /api/users,
- 你需要返回一個成功建立的訊息,此時你可以簡單的發送一個 201 狀態代碼(201=Created)。
- 如果失敗了,伺服器端失敗就發送一個 500(500=內部伺服器錯誤),
- 如果請求中斷就發送一個 400(400=錯誤請求)。
- 也許他們會嘗試向一個不接受 POST 請求的介面提交資料,你就可以發送一個 501 錯誤(未執行)。
- 又或者你的 MySQL 伺服器掛了,介面也會臨時性的中斷,發送一個503錯誤(服務不可用)。
幸運的是,你已經知道了這些,假如你想要瞭解更多關於狀態代碼的資料,可以在維基百科上尋找。
HTTP 支援用戶端緩衝,在HTTP響應裡利用 Cache-Control,Expires,Last-Modified 三個頭欄位, 我們可以讓瀏覽器緩衝資源一段時間。
REST 也可以利用這些頭,告訴用戶端在一定時間內不需要再次請求資源。 這對提高效能有很大好處。Expires、Last-Modified 以及 ETag 可以通過資源的屬性提供,這個在有關 Model 層的設計中再詳細介紹。
5. 測試與調試
PHP 的靈活使得自動化測試或者 TDD 變得困難,至少和 Java 比就差了好大一截。 在架構中,將很自由的開啟調試,比如我的設計是通過添加 url 參數:
http://justjavac.com/user/justjavac?DEBUG=2
通過添加 DEBUG 參數告訴架構開啟偵錯模式,後面的參數值是調試的層級 level。 類似的,你也可以加入 LOG 參數來開機記錄。
這樣設計還有一個好處就是,不需要修改設定檔,而且還可以 針對某一個頁面來開啟或者關閉。 當我用 CI 時,每次我發現程式中的問題,都在設定檔中將 log 層級設定為 all, 再重新開啟頁面,當我再看 log 檔案時,居然已經幾百行了,因為我訪問的每個頁面都被記錄到了日誌裡面。
測試和 url 好像沒有多大關係,測試放在單獨的章節討論。 我為測試約定的 url 是添加 test,比如為控制器 justjavac.controller.php 寫的測試案例(Test Case)可以通過http://justjavac.com/test/user/justjavac 訪問。
但我還是比較喜歡在命令列測試,畢竟當你手動點擊瀏覽器,並手動輸入 url, 手動敲斷行符號鍵時,已經違背了自動化測試。
6. Ajax
@TODO 應用於單頁 Ajax 的 url 結構設計