MVC模式、類封裝還是黑客代碼

合理設計PHP項目

編碼對於合格的PHP程序員來說並不是什麼難事(也許只是花費時間長短的問題),因此系統分析和設計這一階段就顯得尤爲重要。不過本文並不打算討論和需求分析、獲取商業邏輯相關的話題,而是針對系統設計方面進行探討。

面臨難題
編碼對於合格的PHP程序員來說並不是什麼難事(也許只是花費時間長短的問題),因此系統分析和設計這一階段就顯得尤爲重要。對於一個擔任PHP項目的系統分析員來說,面臨着兩個難題:

  1. PHP語言本身的限制。
    這一點在複雜系統的面向對象設計中尤其顯著。PHP的面向對象特性在現有版本中雖然得到了改善,但是還不甚健全,根本不足以擔任面向對象設計的實現語言;即使眼光長遠一些,在即將釋出的以Zend Engine 2.0支持的全新PHP中,面向對象特性也不會像現在流行的Java或者C++那樣(關於這方面的內容可以參見我在developerWorks中國網站發表的另一篇文章)。但是如果採用完全面向過程(準確說是面向Web頁面)的方式,可以想見整個系統的設計會非常複雜,而由此帶來的編碼複雜和維護困難更加難以應付。
  2. 現有資料的嚴重缺乏。
    這是衆所周知的現象即針對Web項目的系統設計資料不足;而在這些有限資料中,關於PHP的設計資料又非常匱乏。如果本公司或本人也沒有相關的技術積累,系統分析員只能在黑暗中摸索方法(更壞的兩種情況,一是照搬其他項目比如Java或者C++的設計,二是認爲項目簡單而不負責任的草草了事)。

認識面對的系統
既然如此,採用何種方法妥善處理PHP系統的分析和設計?最初的構想應該需要分清項目承擔任務的類型:

  1. 涉及大量客戶本身或者客戶所在行業的商業邏輯的項目,包括辦公系統、訂單系統以及其他商業系統。
  2. 簡單網站項目,包括一些需要承擔高訪問量或要求快速響應的項目比如品牌網站或者活動網站以及其他一些網站。
  3. 綜合性網站項目。通常包含多個相對獨立的子系統比如新聞子系統、論壇子系統、產品陳列子系統等等。

PHP的設計初衷在於解決後兩種項目的迫切需求,語言本身對於這些項目進行了良好的改造。而衆多的PHP開發者對這些項目也具有或多或少的經驗,相關書籍中的範例也大都圍繞於此。相對說來第一種系統所有的資料不多,各種出版物對其內容也很少提及。因此在本文中將題所述對第一種類型的項目進行詳細講述(有關MVC模式和類封裝),同時附帶提及第二種項目(有關黑客代碼)以及第三種項目的設計方法。當然,並不是被歸類的這些項目就只能採用本文描述的方式,系統分析員需要權衡各方面因素加以選擇。

方案一:涉及大量商業邏輯項目
如何分離用戶界面和後臺操作?如何避免將商業邏輯混淆於一般的流程控制中?作爲一個嚴謹的商用項目,就需要考慮很多類似的問題。對於由PHP擔當的這類項目,貫徹Model-View-Controller(MVC)模式的設計是一個非常好的方法。

理論描述
在這裏我不想多加解釋MVC模式本身--簡單的從字面上以及應用上說,通過將系統的設計分爲Model模型/邏輯、View視圖/界面、Controller控制/流程三個邏輯部分達到良好的項目效果,以此便利各部分開發者的工作並降低日後的維護成本。(如果您熟悉JSP開發的Model 2模式,可以發現它也是MVC模式的很好體現。)就現實的項目開發而言,現存的很大問題包括網頁設計人員和程序開發人員的工作交錯和衝突以及商業邏輯嵌入頁面造成不可重用也很難維護等等。引入MVC模式一方面可以爲系統的總體設計指出明確的方向,對於開發團隊的分工也是良好的指導。

既然依照MVC模式要求對系統的總體結構在邏輯上分成三部分,那麼團隊的開發者中也存在着針對各個部分的開發者。

開發者角色相關係統邏輯職責
網頁設計人員View視圖/界面設計所有用戶界面的網頁模板。
控制流程開發人員Controller控制/流程編寫系統流程中的所有PHP頁面。
商業邏輯開發人員Model模型/邏輯開發系統設計中規定的各個類(其中的方法)。

由以上的表格可以看出,傳統的網頁設計和程序開發的人員分工被打破而取而代之的是根據系統邏輯劃定的職責。對於網頁設計人員,職責並沒有改變,準確說由於這樣的劃分避免了以往與程序設計人員的糾紛,他們完成的只是網頁模板,因此只需關注於純粹的網頁代碼(主要是HTML,也許會有其他客戶端的代碼比如WML之類)而根本不需要被服務器端的<? … ?>干擾。程序設計人員則被分爲兩部分:其中比較容易把握的是商業邏輯開發人員,他們的任務是根據系統分析員給定的模塊(準確說是類方法)完成之,在他們手中的PHP更像一般的程序設計語言(比如Java)而與Web沒有什麼關係;一時較難接受的是控制流程開發人員,他們的任務是在實現系統設計時制定的系統流程的同時,根據客戶端的輸入調用商業邏輯(相應的類方法)以及輸出更新的界面(對設計網頁模板進行處理),在他們手中PHP可以充分發揮Web編程語言的優勢。

代碼組織相關的話題
這樣的觀念有些抽象,沒有實例的演示很難接受。在舉例之前先介紹一下我對這類工程的推薦代碼結構:

一級目錄二級目錄三級目錄備註
/project_name項目源代碼根目錄
/Templates網頁模板目錄(View)
/admin管理控制檯目錄/admin下的網頁模板
/Include商業邏輯目錄(Model)
/Temp臨時代碼目錄(可選),可供開發者進行一些試驗代碼的測試
/images圖片目錄,網頁模板採用
/css樣式單目錄,網頁模板採用
/scripts客戶端代碼目錄,網頁模板採用
/admin管理控制檯目錄(可選),包含所有後臺管理的功能代碼
/other_dir對應與源代碼根目錄下的/other_dir,包含管理該類的功能代碼
/other_dir其他與相應功能相關的目錄,比如與用戶相關的/member目錄或者與從產品相關的/product目錄等等
/config.inc.php全局配置變量,定義系統中的全局變量
/security.inc.php安全策略控制(可選)
/error.php錯誤控制返回頁面(可選),也可以採用靜態頁面如/error.html或者其他頁面名稱

看完之後您是不是被喚起了一點使用Java進行Web開發的記憶?比如WEB-INF目錄下的classes目錄和lib目錄以及web.xml都是開發中的規則--雖然支持PHP的Web服務器不可能像Java應用服務器那樣自動加載這些目錄下的文件,但是規定一個合適的代碼組織模式還是非常有利於開發的便利和後續的維護的。(我開始考慮PHP項目的代碼結構就是由Tomcat的開發手冊中獲得了啓發。)

一個用戶登錄的例子
根據以上的代碼目錄,前文所說的MVC模式的實現可以得到更簡單的解釋。以最常見的用戶登錄功能爲例,設想/project_name目錄下有一個/member目錄包含有關於用戶的一切功能,其中包含了login.php頁面接受用戶登錄使用的用戶名稱和密碼,index.php頁面是登錄完成之後的用戶主頁,而/project_name目錄下的error.php是登錄失敗後的錯誤顯示頁面。

  1. 用戶通過系統的其他部分請求進入用戶主頁即/member/index.php頁面,此時該頁面判斷用戶情況:已登錄用戶則直接顯示本頁內容(可以採用檢查session等方法);未登錄用戶則需要登錄(重定向到/member/login.php);出現了未知錯誤(重定向到/error.php)。同時採取相應的反應。
  2. 假如用戶被引導至登錄頁面即/member/login.php頁面,該頁面接受用戶的登錄信息(用戶名稱和密碼),並判斷是否正確登錄:正確登錄則再次重定向到用戶主頁/member/index.php;登錄錯誤則重定向到/error.php。
  3. 假如用戶被引導至錯誤顯示頁面/error.php頁面(無論是從以上哪個頁面前來),都會顯示錯誤信息。

流程圖示如下:


根據以上的文字描述和圖示,再結合MVC模式的實現,可以非常輕鬆的寫出這幾個頁面的框架代碼:

  1. 先看簡單的頁面/error.php:


  2. 然後是/member/login.php:


  3. 最後是/member/index.php:


(注意:以上代碼只是片斷,而且沒有考慮項目全局,只起演示作用)

關於Controller
首先可以明確的是,以上的三個頁面代碼就是前文所說的Controller控制/流程代碼。很明顯,他們的不包含特定的操作,也沒有一行網頁代碼,有的只是與前面流程圖一致的流程控制代碼(放眼望去,這些頁面的共同特點是充滿了引用網頁模板並輸出、取得對象並執行其某個方法或者重定向)。

再選擇其中的一個頁面/member/login.php詳細的解釋。整個頁面通過判斷是否提交表單分爲兩個部分:顯示登錄表單供用戶填寫和處理登錄信息。作爲前者直接引用一個處於網頁模板目錄/Templates下對應該頁面的member_login.dwt並在解析後輸出;作爲後者先取得一個Member對象(該對象出於商業邏輯目錄/Include下的Member.inc.php中),然後獲得登錄判斷的結果後進行重定向。在這個控制頁面的代碼中,member_login.dwt作爲View視圖/界面出現,類Member作爲Model模型/邏輯出現,而頁面代碼本身就Controller控制/流程。下面就是加入標示的/member/login.php框架代碼:


(關於模板類以及在MVC模式中的應用,可以參考我在developerWorks中另一篇文章《 在PHP世界中選擇最合適的模板》)

關於Model
既然談到了Model,下面就是另一個重要的話題:類封裝在PHP項目中的應用。

請注意用詞"類封裝"--這和"面向對象"或者其他什麼"採用對象設計"的方法有着本質的不同。"類封裝"只是講述了將商業邏輯採用類方法的方式封裝成各個不同的類,因而這裏的"類"並不是因此採用了面向對象設計出現的"類"--準確的說,這裏的"類"其實是對一系列相關功能模塊進行合併的結果。

爲什麼不直接採用面向對象的方式而是採用這種看起來不倫不類的辦法去設計系統呢?PHP不是具有面向對象特性嗎?不錯,PHP具有這樣的特性,但是非常不完全(可以參考我在developerWorks的另一篇文章《 從Zend Engine 2.0的設計藍圖(草稿)看PHP的將來》)。舉例來說,PHP是沒有接口這一概念和實現方法的,同時也就沒有什麼多重繼承、方法重載之類的典型面向對象特徵。如果非要採用面向對象的設計方法,也許在概要設計階段可以非常輕鬆,但是詳細設計階段就會比較苦悶,而如果還有幸堅持到編碼階段簡直就是苦不堪言了。另一方面,如果不在系統中引入類的概念,而是採用函數來實現模塊功能,那麼可以想象在一個採用這樣"純粹"的中大型系統中會有多少的函數,由此帶來的麻煩非常明顯。

還是回到PHP語言本身。雖然PHP提供不了什麼實際的面向對象支持,但是還是提供了對類以及其中的屬性和方法的定義。那麼自然而然可以想到的是採用類的方法封裝相關函數模塊,既可以借鑑一些對象設計的優點,又可以避免完全採用函數模塊的一些缺點。

(一些採用函數模塊的系統會採用這樣一種方式:將相關的函數編寫在相同的文件中,這樣在引用時可以引入單獨的文件。比如Member.func.php這個文件中包含了所有與用戶相關的操作,在處理用戶登錄時可以先require這個文件,然後調用諸如member_login()這樣的函數。但是這樣的方式僅僅解決了系統中衆多函數的代碼組織問題,沒有解決名字衝突的問題。下面的舉例中就會看到。)

比如上文的用戶登錄實例中,如果採用函數模塊的方法,代碼也許是這樣:


而採用類封裝的方法,可能就是這樣:


也許您會覺得代碼並沒有什麼區別(甚至看起來採用函數模塊的代碼由於不需要取得新的對象而顯得更簡潔一些),而真正的不同是發生在include的文件裏面。採用函數模塊的方法將相關的函數集合在一個文件中加以組織(有些系統還不能做到這一點,那麼就會造成異常混亂的局面),而採用類封裝的方法在每一個文件中聲明一個和文件名相同的類(比如在Member.inc.php聲明一個Member的類,這一點和Java的規定相似);而在使用時,都需要先進行include(如果採用函數模塊又沒有進行很好的組織,也許有些人就會很"簡便"的將所有函數include進每一個頁面--PHP可不是Java那樣編譯執行,光是解析這些函數就會花費一段時間),但是關鍵就在於採用類封裝的方法可以清楚的指明調用的位置--某個類(Member)的某個方法(login):從避免名字衝突的角度來說這一點是非常成功的;而對於代碼檢查和維護而言,方便程度更是不言而喻。設想一個頁面需要完成若干功能,因而需要include數個文件:採用函數模塊的方法不能夠輕易的從函數調用中找到函數本身所在的文件(如果函數名稱或者include文件名稱沒有什麼統一規則,那麼這個工作就非常艱鉅了),而採用類封裝的辦法可以根據類名稱和類文件名稱準確定位類方法代碼的位置。(也許您會認爲這樣一個小小的好處不足掛齒,但是經歷一個維護工程之後也許就不會再有什麼異議。)

以上是採用類封裝方法的原因,決定採用這種方法設計系統只是第一步;完成整個系統的設計還有很多可以借鑑的經驗。

  1. 部分設計可以借鑑面向對象的思路。雖然PHP中沒有接口和抽象類的定義,繼承機制也非常不完全,但至少具備了基本的類定義和簡單的繼承關係。類似"公司-僱員"、"賣家-商品-買家"這類顯而易見的關係可以很容易在系統中通過類和類關係定義。既然PHP可以做到這一點,就按照實際的邏輯關係去定義即可。
  2. 經常會在系統中出現的另一個情況是關於個體和列表的關係--這樣說也許難以理解,想象一個BBS系統中的帖子列表和每個帖子之間,就是這樣的關係。根據設計經驗,這樣的關係大量存在於PHP或者其他Web系統中。對於這類關係,我個人建議可以採用以下Item和Item_List的類封裝方式:
  3. 由於PHP對於類的成員變量和方法並沒有語法上的訪問限制(均爲公開),因此會帶來對象使用方面的某些混亂。基於此,建議在開發團隊的代碼規範中加以規定,從代碼應用的級別上控制這一情況:
    首先,可以通過對成員變量和方法的註釋來說明其屬性,由此使用該對象的其他開發人員可以瞭解自己的使用方法是否觸犯了規定的訪問限制。(如果採用phpdoc等自動文檔生成的工具,開發人員甚至可以在不翻閱類源碼的情況下通過瀏覽類文檔正確使用它。)
    其次,對於成員變量訪問限制的考慮,可以將一些主要的、經常需要被訪問或更改的變量(在註釋中)聲明爲公開。這樣的作法可以省卻大量get()和set()方法的代碼--雖然在其他的面嚮對象語言中這一點被認爲非常醜陋,但是記住PHP不是Java,只要這樣的用法合理,就應該大膽使用。
  4. 從上面的示例代碼中您也許已經注意到了註釋的比重--雖然大家都瞭解註釋的重要性,但是仍然有必要提出。這個示例中採用了Javadoc的樣式,利用現有工具也可以很容易的直接生成文檔(當然您和您的開發團隊也可以定義自己的合適註釋樣式和文檔生成工具)。對於系統分析員來說,您在設計階段完成之後交付給您的開發夥伴的代碼部分很可能就是這些註釋佔絕大部分的框架代碼;你們之間交流的工具除了那些沒完沒了的圖表之外就是這些程序員最熟悉的代碼和註釋了。

在PHP系統中進行類的設計雖然不像構建面向對象系統那樣需要各種合理的模式介入(也沒有這樣的"本錢"爲之),但還是需要一番思量的。邏輯上的合理性和操作上的可行性都是檢驗的標準。

(說到類設計,又想到了適合PHP開發的IDE問題。據我所知比較專業一些有Zend出品的Zend IDE;另外還有作爲JBuilder的Open Tools出現的藉助JBuilder的PHP開發工具;不過最常用的還是PHPEd或者UltraEdit之類的編輯器。如果現有的編輯器可以非常聰明的支持PHP的類設計和代碼實現就非常理想了。)

關於View
最後說到的是View方面,雖然這部分內容與網頁設計人員聯繫比較緊密,不過PHP項目(以及其他Web項目)的系統分析員也必須關注這一話題。可以看出MVC模式的應用使得網頁開發人員和程序設計人員的各自工作成果不會像以前那樣互相影響,自然可以提高各自的工作效率(相互關係也許會比以前更加融洽一些)。但是對於系統分析員來說,將用戶界面分離爲各個獨立的網頁模板需要進行許多分析工作。

首先是確定整個系統的流程,這一點在系統設計的初期就應該做到。而對於View視圖/界面和Controller控制/流程來說,所有需要的頁面都是圍繞此流程產生。不過通常此時能夠在流程圖上看到的也許只是相關的參數在各個頁面之間傳遞,卻不能瞭解各個頁面展示的內容--這就是下一步分析用戶界面需要進行的工作。

在分析用戶界面的工作中,第一步可以確定各個頁面核心、對於完成流程必不可少的用戶界面元素(表單和表單域、鏈接等);第二步是確定頁面中需要出現的導航內容;最後還需要依據流程複覈。還是以上文的用戶登錄爲例。對於/member/login.php這個關鍵的頁面,第一步可以確定的是在用戶提交之前應該顯示一個表單,表單包含兩個文本框供用戶輸入用戶名稱和密碼;而提交之後根據流程在本頁面中不需要有用戶界面,取而代之的是利用Controller控制/流程這一邏輯層進行重定向。而第二步需要制定該頁面中(準確說是在顯示登錄表單時)需要提供的導航鏈接,在這裏可以加上到系統的主頁或者其他非註冊用戶頁的起點的鏈接(方便用戶臨時決定取消登錄)以及一個註銷現有用戶的鏈接(針對已登錄用戶)。之後進行復核,此時也許會發現這一設計似乎沒有考慮到在登錄前更好的區別是管理員登錄還是普通用戶登錄,那麼就可以在表單中增加一個隱藏域表示選擇登錄的用戶是準備以管理員還是普通用戶的身份進行登錄。

確定完用戶界面的元素,並不意味着可以將這些分析結果交付網頁設計人員進行製作了;還有最關鍵的一步沒有實施--爲分析完成的各個頁面制定模板所需的變量名稱。對於以上的用戶登錄實例,如果系統有識別曾經登錄用戶的功能(依據之前訪問時在客戶端設置的相關cookie值)並且把這個用戶名稱顯示在登錄表單的用戶名稱一欄,此時就需要在member_login.dwt設計中說明該表單域將被賦值爲一個模板變量(比如{USERNAME})。這一步驟完成之後就可以交付網頁設計人員進行製作了。

需要指出的是,在編碼階段很可能局部的一些系統設計需要進行修改,這其中也許就包括對網頁模板的修改,需要仔細處理。

對於代碼組織的補充說明
還有幾個文件和目錄沒有在上文提及:

  1. config.inc.php -- 如果您熟悉phpMyAdmin或者其他phpWizard.net釋出的項目,就應該非常清楚這個文件的作用:定義本項目範圍內的全局變量(在每個頁面中被include)。我個人認爲這是一個非常良好的設計,因此也提倡在項目中應用。另外,爲了保證與項目中其他的變量衝突,建議在該文件中定義一個多重數組,而各種全局變量都以該數組的某一個值出現。這樣方便團隊中的其他開發者只需要避免一個變量名的使用,而不是避免所有config.inc.php中出現的變量名。使用這個文件的另一個好處是由於將關鍵的變量(比如與服務器環境相關的變量)集中定義,可以方便的安裝和移植整個項目。
  2. security.inc.php -- 顧名思義這個文件控制並實施整個系統的安全策略。關於安全問題,可以想到的是兩種控制方案:在每個控制流程頁面頂端針對本頁面加以控制以及採用一個控制文件整個控制並被加入每個控制流程頁面。我個人提倡採用後一種方式,原因也很簡單:定義簡單而且維護方便。雖然相比每個頁面單獨定義,也許會損失一點點效率(一些不需要安全控制的頁面也需要include該文件並進行判別),但是獲得的是對系統安全的整體控制以及代碼維護的便利(損失一個if…else…的判別換取這樣的結果還是很值得的)。
  3. /Temp -- 很明顯存在於這個目錄下的都是一個臨時文件,並且這個目錄其實並不會出現在項目正式發行的版本中。如果開發時對一些函數的使用不甚明瞭或者試驗一段沒有相關經驗的代碼,都可以在此目錄下建立文件;因爲該目錄就位於項目代碼之中,可以非常便利的取得項目運行的上下文環境,大大降低了試驗代碼的成本。
  4. /admin -- 通常對系統的後臺管理內容應該放置在一個獨立的目錄中,我個人比較喜歡admin這個簡寫詞(當然也有一些情況系統分析員認爲不應該設置一個容易猜測的管理目錄名稱以增加一重對系統安全的保護)。
  5. /css和/scripts -- 都是與網頁設計也就是View視圖/界面有關的文件存放處,分別是樣式單和客戶端腳本。這樣做的好處在任何一本講述網站規劃的書籍中都會有所提及。

方案二:簡單網站項目
系統性能是這類項目追求的首要目標,而與此同時系統的維護和擴展幾乎可以不用多加考慮。(也許這句話聽起來有些絕對,但是根據客戶的需求和項目的性質判斷,盡最大可能以最短時間滿足客戶的需求並使得系統高效運轉就是項目成功的最好檢驗標準。)因此,也許這類項目就是PHP黑客的天堂(曾經我也是一個過分追求PHP使用效率的人)。由於這類項目的特殊性,這裏討論的範圍不僅僅侷限與系統設計而是從組建項目小組開始直到交付項目的過程。

首先需要關注的是參與項目的人選(雖然也許這是項目經理的職責,但是最熟悉PHP項目特點的系統分析員應該參與)。在PHP開發人員方面,至少應該選擇對PHP各種函數較爲熟悉的開發者(這類項目不適合作爲現實項目以培訓參與的開發新人),如果公司中還有能夠在源碼級別理解PHP的人員就更加理想(不過通常對於一般的PHP開發公司是不可能的)。而在網頁設計人員方面,最好可以選擇一些略通客戶端(比如JavaScript)以及服務器端(最好是PHP)腳本的人員;因爲這類項目的一大特點即是單個網頁代碼量較大且夾雜網頁代碼(通常是HTML)、客戶端腳本(比如JavaScript)和服務器端腳本(比如PHP),加入瞭解各種腳本語言的網頁設計人員的目的不是爲了增加團隊的PHP開發力量,而是避免在修改網頁時影響程序設計人員的工作。

其次就是面向過程,準確說是面向頁面的系統設計。相對第一類項目,客戶的需求在該類項目中表現得非常清晰,而且一般長期進行Web開發的公司對於這類網站項目也應該有一定的設計經驗積累。設計中需要圍繞整個系統的流程,包括每個頁面的輸入參數和輸出內容(包括網頁中出現的除導航鏈接之外的功能性鏈接),以求完全滿足客戶的需求;另一關鍵在於確定系統安全策略,在這類項目中主要是用戶等級的確定和頁面的訪問權限,並給出實現的方式。不過還需要指出的是,這類項目中由編碼階段返回設計階段的情況並不少見,對於局部設計(比如頁面傳入參數或者輸出鏈接)的更改應該加以及時控制。

最後是針對代碼和數據庫的優化。在這類項目中需要適當鼓勵開發人員的黑客態度。推薦的辦法是系統分析員給出每個頁面的僞代碼(框架代碼),而局部的實現則由各個程序開發人員和網頁設計人員進行。

對於PHP代碼方面,通常可以從如下幾方面考慮:

  1. 算法的選擇和功能實現的方式:模塊級別的優化,可以由幾名開發人員共同討論解決;
  2. 函數的使用:代碼級別的優化,需要開發人員對各類函數有清楚的認識,至少養成多多參考函數手冊的習慣;
  3. 數據庫的查詢和更改即SQL語句的使用:如果公司中有相關數據庫系統的管理人員,可以就一些優化問題徵詢他們的建議;
  4. 其他應該避免的問題:比如拷貝代碼、等不良代碼情況。

而根據我的經驗,通常會在這類項目中撰寫的黑客代碼如下:

  1. 循環語句的使用特別是在查找時的應用:此時注意while和for的區別(想必大家在大學課堂中都做過這類的程序),這也是良好的編程習慣;
  2. SQL語句的優化:首先是儘量避免多餘的數據庫交互,這是提高效率非常重要的一點;其次是不要害怕長達幾行的語句而寧願使用所謂簡單的語句;再次是認真考慮查詢語句返回的字段,減少不必要的數據。
  3. 表單提交值的獲取,比如複選框和文本域。精巧的表單域名稱設計可以減少一定的代碼量,而處理提交值時也需要注意處理的方式。

黑客代碼在這類項目中值得鼓勵,不過最好在每段代碼旁附上儘可能詳細的註釋。

由於該類項目的特殊性,完成項目的關鍵不僅僅在於系統設計階段,因此給出項目開始、系統設計、編碼以及測試、交付這一過程的簡單描述:

  1. 挑選合適人員組成項目小組,可以考慮銷售人員和客戶代表的加入。
  2. 系統分析員可以簡單的從客戶的需求以及以往項目經驗的結合中總結出系統所需的每個網頁並對其功能作出描述,同時確定初步的安全策略。這一步驟中可以加入銷售人員和客戶代表的加入。(此時網頁設計人員正在準備提供給客戶的一系列網站形象頁面。)
  3. 詳細設計中需要爲每個頁面確定位置和名稱,更加關鍵的是確定輸入參數和輸出內容以及不同級別用戶對於網頁的確切訪問權限。同時進行數據庫設計。該階段完成後至少應該提供系統的流程圖(包括訪問權限標識)以及數據庫設計資料。
  4. 網頁設計人員和程序開發人員拿到相關資料各自進行工作。對於前者,根據客戶認可的一套形象設計每個頁面;對於後者,開始進行"興奮的"(因爲此時要求的是高效簡介的代碼--黑客代碼)編碼工作。此階段工作中遇到的困難均需要反饋到系統分析員處,可能返回以上的第3步甚至第2步進行設計修改。
  5. 程序編寫和網頁設計結束後需要有一段整合的時間,也是程序開發人員對代碼進行自我測試的階段。同時在這一階段可以進行的是代碼(包括網頁代碼和程序代碼)和數據庫的優化工作。此階段結束後應該可以提供一個完整的系統。
  6. 真正的測試階段通常都比較倉促,這方面的技術和經驗公司也應該有一定積累(如果有條件希望採用一些軟件工具進行穩定性和抗壓能力的測試)。最後是提供一個可Web訪問的地址供客戶測試。此階段完成後可以提供正式交付客戶的系統。

方案三:綜合性網站項目
已經有一些大型網站使用PHP作爲主要的開發語言。對於這類項目,單純從PHP技術方面值得提出的話題不多,簡而言之還是根據網站各部分的實際應用情況(訪問強度、操作行爲等)選擇以上提出的兩種項目設計方法或者綜合使用。除此之外,根據我個人的經驗,項目團隊的組織和協調工作以及項目各期完成後的維護工作等等是較之單純的技術更加關鍵的因素。

對於這類項目,可以提出的建議是,適當採納一些開源軟件對於快速、優質的完成項目很有好處。項目的某些部分可以直接引入開源軟件項目的設計甚至是代碼,不過前提是系統設計人員對這些引入的項目需要非常瞭解,同時需要做好這些孤立的開源項目和整個項目之間的接合(比如安全策略的考慮和全局變量的引用等)。

舉例來說,根據客戶要求某個綜合網站需要以下的功能:

  1. 複雜的新聞發佈;
  2. 需要不多管理功能的在線論壇;
  3. 簡單的產品陳列;
  4. 需要用戶管理。

(很明顯這是一個企業網站的雛形。)

其中的1、2項很明顯可以借用一些成熟的開源軟件項目,而3項由於客戶需求簡單自主開發比較符合成本。由此看來4項則是整個系統中最重要的部分--需要做好與1、2項使用的開源軟件項目的用戶管理集成工作(3項由於自主開發的原因集成工作非常簡單)。(某些技術積累較好的公司甚至對於以上提及的集成部分都有簡單的解決方案,那麼這樣一個網站項目的完成所需成本非常微小。)

幾個特殊的功能點
另外還有一些通常項目中都會出現但是必須妥善處理的功能點:

1. 數據列表分頁。
關於這個功能,互聯網上的中文和英文資料都有許多。具體的技術和實施細節不需要多說,這裏只需要指出的是:

A. 建議封裝成某一個工具類的方法或者其他可複用的形式--這樣的好處不言自明,任何人都不希望系統中只要存在數據列表分頁的時候都會出現一堆幾乎相同的代碼。

B. 如果針對一些效率要求較高的項目(例如上文提到的"簡單網站項目"類型),應該直接使用PHP自帶的針對特定數據庫系統的操作函數以及與該數據庫系統相關的結果集截取技術(SQL語句),比如MySQL中的'LIMIT start, offset'之類;其他一些需要系統設計工整合理的項目(例如上文提到的"設計大量商業邏輯項目"),如果採用了通用的數據庫接口,出於兼容多種數據庫系統的考慮,可以採用此接口完成結果集的篩選,以損失的效率換取系統更好的可維護性和可擴展性。也就是說,對於採用特定數據庫操作函數還是第三方通用數據庫接口來實現數據列表分頁,需要考慮系統的性能和擴展兩方面因素。

2. 錯誤控制。
這一點在上文之中也有提及。除了建立屬於工具類的錯誤類之外,最好可以建立專門的錯誤顯示頁面。該頁面既可以是靜態的HTML頁面(表達一些對用戶的歉意和出錯之後的處理指導)或者動態的PHP頁面(可以包含具體的出錯原因和地點以及其他更詳細的信息,前提是在系統安全策略允許提供這些信息)。而錯誤類的任務就是接受正常的程序中拋出的錯誤,進行必要處理之後將信息一起重定向在錯誤顯示頁面上。


同時,建立出錯頁面對於開發階段也有一定好處,可以彌補現有PHP缺少類似try{…} catch{…} 塊的違例控制的缺點,將調試中的錯誤或者輸出通過錯誤類拋出並顯示出來。

3.上載與下載。
對於PHP來說,上載的實現並不會像其他流行的Web開發語言那樣需要第三方程序的支持,內建的機制可以非常簡單的處理。不過這裏提及的是一些複雜的上載功能實現。考察以下一個處理附加文件的流程:


該功能使得用戶在撰寫新的消息時可以附加其他文件,而且在消息沒有提交之前可以隨意的對已經附加的文件進行刪除或者繼續增加。這種需求會體現在許多辦公相關的系統中,作爲有經驗的系統分析員應該在系統設計階段制定完成針對該類功能的實施計劃。比如在圖中所示的流程中,其實是通過一個或者多個表單的互相提交完成(具體設計不再贅述,提供相關的PHP文件參考;另外的一個直觀的例子就是多數免費郵件系統的添加附件功能,如果有興趣可以考察一下)。

至於下載,將文件置於服務器Web可訪問目錄下、提供訪問者真實文件路徑是最簡單的解決辦法;不過一些系統中對文件的下載基於某些安全策略需要進行身份方面的判別方可予以下載,這樣的方式就會帶來隱患。通常採用的方式也許是將文件放置在Web可訪問目錄以外的服務器文件系統中或者存儲進數據庫系統--都需要一個簡單的程序取得文件內容並直接返回給發出請求的用戶(這其中涉及到一些HTTP輸出頭的問題請注意,提供一個PHP文件代替具體敘述)。在系統設計時針對不同的需求可以採用相應的辦法。

4. 客戶會話session的保持
PHP的現有版本已經內置了對session的支持,通常項目中都使用這樣的方式;一些特殊需要的項目(比如分佈系統)也許會採用複雜一些的處理方式。

在客戶端,通常採用的是設置cookie以識別特定的客戶,另一個可以應付不支持cookie的客戶端的方法是採用URL重寫加入足夠標示特定客戶的字符串。從這方面來說,採用PHP內置的session支持最爲理想,因爲它可以自動的進行客戶端的FALLBACK:如果客戶端支持cookie,那麼就順其自然;如果cookie不被支持,就採用URL重寫方式--一切都不需要開發者干預。如果採用其他session處理方式,或者自己編寫適應需要的session庫,需要注意的就是怎樣處理客戶端存儲數據的問題--cookie還是URL重寫還是兩者兼顧。

在服務器端,簡單說來只需要針對以字符串標示的每個特定客戶存儲相關的數據即可--可以採用的方式多種多樣,通常的方式是文件和數據庫。PHP內置的session支持中,默認的支持方式是在系統的臨時目錄或者制定的目錄下爲每個客戶建立一個文件存儲其數據;當然也可以修改設置使其支持數據庫的方式或者其他方式。如果自己編寫session庫,根據系統的需要選擇一種合適的存儲方式即可。值得指出的是,對於分佈系統,如何共享服務器端的session信息是需要極大關注的。

另外,在設置session變量的時候,PHP內置的session庫支持對象作爲變量值(實際上所有的變量值,不論是一般的變量還是數組或是對象,都在經過串行化之後被存儲),也就是說,以下代碼是可用的:


這一點對於上文提到的一些商用系統是有益的:首先,可以使用關於用戶的對象作爲一個session變量值存儲一套信息,而不是割裂的多個session變量;其次,如果具有類似購物車的功能,可以以非常符合整個系統設計的方式(即前文所述商用系統的設計方式,強調類封裝)將該購物車對象放入session中。

5. ……其他和特定項目有關的功能點……
如果能在系統設計階段就預見並解決這些功能點固然很好,即使有少量未發現的功能點遺留到了編碼階段也並不可怕--通常這樣的遺漏並不會影響整個系統的架構,只是需要返回設計階段加入相應的內容和文檔即可。畢竟對於相關項目經驗不太豐富的系統分析員來說,做到設計階段對這類功能點了然於胸是不太現實的。

關於其他
對於PHP的爭論從前很多,不過自從Java在Web方面的優勢越來越進入人們的視野之後,這樣的爭論倒偃旗息鼓了--看來大家都達成了共識--PHP對於嚴謹的商用系統還是無能爲力。不過基於此就一味否定PHP在商用系統中的應用也不大客觀,畢竟PHP還具有低成本的優勢(這裏的成本包括開發成本、使用成本和維護成本)。本文的目的除了講述一些PHP系統設計的方法之外,也希望吸引一些開發者或者企業採用PHP構建合適的商用系統。

另外,本文僅僅是我自己的一些經驗,如果您看到這裏時候已經有了自己的一些想法,我非常樂意與您分享--能夠推動如PHP這樣無商業支持的開源軟件的發展,畢竟是一件非常令人興奮的事情。(從這方面來說,我甚至想撰寫關於PHP開發的文檔資料和示範項目,就如同Sun Microsystems爲J2EE發佈的Blueprint和Java Pet Store--可惜暫時受到時間、精力以及個人能力的限制--也許春節假期是一個好時機:)

參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章