用封裝類來合理的設計PHP項目--談PHP項目中類的封裝
發(fā)表時間:2024-06-20 來源:明輝站整理相關軟件相關文章人氣:
[摘要]編碼對于合格的PHP程序員來說并不是什么難事(也許只是花費時間長短的問題),因此系統(tǒng)分析和設計這一階段就顯得尤為重要。不過本文并不打算討論和需求分析、獲取商業(yè)邏輯相關的話題,而是針對系統(tǒng)設計方面進行探討。面臨難題編碼對于合格的PHP程序員來說并不是什么難事(也許只是花費時間長短的問題),因此系統(tǒng)分...
編碼對于合格的PHP程序員來說并不是什么難事(也許只是花費時間長短的問題),因此系統(tǒng)分析和設計這一階段就顯得尤為重要。不過本文并不打算討論和需求分析、獲取商業(yè)邏輯相關的話題,而是針對系統(tǒng)設計方面進行探討。
面臨難題
編碼對于合格的PHP程序員來說并不是什么難事(也許只是花費時間長短的問題),因此系統(tǒng)分析和設計這一階段就顯得尤為重要。對于一個擔任PHP項目的系統(tǒng)分析員來說,面臨著兩個難題:
PHP語言本身的限制。
這一點在復雜系統(tǒng)的面向對象設計中尤其顯著。PHP的面向對象特性在現(xiàn)有版本中雖然得到了改善,但是還不甚健全,根本不足以擔任面向對象設計的實現(xiàn)語言;即使眼光長遠一些,在即將釋出的以Zend Engine 2.0支持的全新PHP中,面向對象特性也不會像現(xiàn)在流行的Java或者C++那樣(關于這方面的內(nèi)容可以參見我在developerWorks中國網(wǎng)站發(fā)表的另一篇文章)。但是如果采用完全面向過程(準確說是面向Web頁面)的方式,可以想見整個系統(tǒng)的設計會非常復雜,而由此帶來的編碼復雜和維護困難更加難以應付。
現(xiàn)有資料的嚴重缺乏。
這是眾所周知的現(xiàn)象即針對Web項目的系統(tǒng)設計資料不足;而在這些有限資料中,關于PHP的設計資料又非常匱乏。如果本公司或本人也沒有相關的技術積累,系統(tǒng)分析員只能在黑暗中摸索方法(更壞的兩種情況,一是照搬其他項目比如Java或者C++的設計,二是認為項目簡單而不負責任的草草了事)。
認識面對的系統(tǒng)
既然如此,采用何種方法妥善處理PHP系統(tǒng)的分析和設計?最初的構想應該需要分清項目承擔任務的類型:
涉及大量客戶本身或者客戶所在行業(yè)的商業(yè)邏輯的項目,包括辦公系統(tǒng)、訂單系統(tǒng)以及其他商業(yè)系統(tǒng)。
簡單網(wǎng)站項目,包括一些需要承擔高訪問量或要求快速響應的項目比如品牌網(wǎng)站或者活動網(wǎng)站以及其他一些網(wǎng)站。
綜合性網(wǎng)站項目。通常包含多個相對獨立的子系統(tǒng)比如新聞子系統(tǒng)、論壇子系統(tǒng)、產(chǎn)品陳列子系統(tǒng)等等。
PHP的設計初衷在于解決后兩種項目的迫切需求,語言本身對于這些項目進行了良好的改造。而眾多的PHP開發(fā)者對這些項目也具有或多或少的經(jīng)驗,相關書籍中的范例也大都圍繞于此。相對說來第一種系統(tǒng)所有的資料不多,各種出版物對其內(nèi)容也很少提及。因此在本文中將題所述對第一種類型的項目進行詳細講述(有關MVC模式和類封裝),同時附帶提及第二種項目(有關黑客代碼)以及第三種項目的設計方法。當然,并不是被歸類的這些項目就只能采用本文描述的方式,系統(tǒng)分析員需要權衡各方面因素加以選擇。
方案一:涉及大量商業(yè)邏輯項目
如何分離用戶界面和后臺操作?如何避免將商業(yè)邏輯混淆于一般的流程控制中?作為一個嚴謹?shù)纳逃庙椖,就需要考慮很多類似的問題。對于由PHP擔當?shù)倪@類項目,貫徹Model-View-Controller(MVC)模式的設計是一個非常好的方法。
理論描述
在這里我不想多加解釋MVC模式本身--簡單的從字面上以及應用上說,通過將系統(tǒng)的設計分為Model模型/邏輯、View視圖/界面、Controller控制/流程三個邏輯部分達到良好的項目效果,以此便利各部分開發(fā)者的工作并降低日后的維護成本。(如果您熟悉JSP開發(fā)的Model 2模式,可以發(fā)現(xiàn)它也是MVC模式的很好體現(xiàn)。)就現(xiàn)實的項目開發(fā)而言,現(xiàn)存的很大問題包括網(wǎng)頁設計人員和程序開發(fā)人員的工作交錯和沖突以及商業(yè)邏輯嵌入頁面造成不可重用也很難維護等等。引入MVC模式一方面可以為系統(tǒng)的總體設計指出明確的方向,對于開發(fā)團隊的分工也是良好的指導。
既然依照MVC模式要求對系統(tǒng)的總體結構在邏輯上分成三部分,那么團隊的開發(fā)者中也存在著針對各個部分的開發(fā)者。
開發(fā)者角色 相關系統(tǒng)邏輯 職責
網(wǎng)頁設計人員 View視圖/界面 設計所有用戶界面的網(wǎng)頁模板。
控制流程開發(fā)人員 Controller控制/流程 編寫系統(tǒng)流程中的所有PHP頁面。
商業(yè)邏輯開發(fā)人員 Model模型/邏輯 開發(fā)系統(tǒng)設計中規(guī)定的各個類(其中的方法)。
由以上的表格可以看出,傳統(tǒng)的網(wǎng)頁設計和程序開發(fā)的人員分工被打破而取而代之的是根據(jù)系統(tǒng)邏輯劃定的職責。對于網(wǎng)頁設計人員,職責并沒有改變,準確說由于這樣的劃分避免了以往與程序設計人員的糾紛,他們完成的只是網(wǎng)頁模板,因此只需關注于純粹的網(wǎng)頁代碼(主要是HTML,也許會有其他客戶端的代碼比如WML之類)而根本不需要被服務器端的<? … ?>干擾。程序設計人員則被分為兩部分:其中比較容易把握的是商業(yè)邏輯開發(fā)人員,他們的任務是根據(jù)系統(tǒng)分析員給定的模塊(準確說是類方法)完成之,在他們手中的PHP更像一般的程序設計語言(比如Java)而與Web沒有什么關系;一時較難接受的是控制流程開發(fā)人員,他們的任務是在實現(xiàn)系統(tǒng)設計時制定的系統(tǒng)流程的同時,根據(jù)客戶端的輸入調用商業(yè)邏輯(相應的類方法)以及輸出更新的界面(對設計網(wǎng)頁模板進行處理),在他們手中PHP可以充分發(fā)揮Web編程語言的優(yōu)勢。
代碼組織相關的話題
這樣的觀念有些抽象,沒有實例的演示很難接受。在舉例之前先介紹一下我對這類工程的推薦代碼結構:
一級目錄 二級目錄 三級目錄 備注
/project_name 項目源代碼根目錄
/Templates 網(wǎng)頁模板目錄(View)
/admin 管理控制臺目錄/admin下的網(wǎng)頁模板
/Include 商業(yè)邏輯目錄(Model)
/Temp 臨時代碼目錄(可選),可供開發(fā)者進行一些試驗代碼的測試
/images 圖片目錄,網(wǎng)頁模板采用
/css 樣式單目錄,網(wǎng)頁模板采用
/scripts 客戶端代碼目錄,網(wǎng)頁模板采用
/admin 管理控制臺目錄(可選),包含所有后臺管理的功能代碼
/other_dir 對應與源代碼根目錄下的/other_dir,包含管理該類的功能代碼
/other_dir 其他與相應功能相關的目錄,比如與用戶相關的/member目錄或者與從產(chǎn)品相關的/product目錄等等
/config.inc.php 全局配置變量,定義系統(tǒng)中的全局變量
/security.inc.php 安全策略控制(可選)
/error.php 錯誤控制返回頁面(可選),也可以采用靜態(tài)頁面如/error.html或者其他頁面名稱
看完之后您是不是被喚起了一點使用Java進行Web開發(fā)的記憶?比如WEB-INF目錄下的classes目錄和lib目錄以及web.xml都是開發(fā)中的規(guī)則--雖然支持PHP的Web服務器不可能像Java應用服務器那樣自動加載這些目錄下的文件,但是規(guī)定一個合適的代碼組織模式還是非常有利于開發(fā)的便利和后續(xù)的維護的。(我開始考慮PHP項目的代碼結構就是由Tomcat的開發(fā)手冊中獲得了啟發(fā)。)
一個用戶登錄的例子
根據(jù)以上的代碼目錄,前文所說的MVC模式的實現(xiàn)可以得到更簡單的解釋。以最常見的用戶登錄功能為例,設想/project_name目錄下有一個/member目錄包含有關于用戶的一切功能,其中包含了login.php頁面接受用戶登錄使用的用戶名稱和密碼,index.php頁面是登錄完成之后的用戶主頁,而/project_name目錄下的error.php是登錄失敗后的錯誤顯示頁面。
用戶通過系統(tǒng)的其他部分請求進入用戶主頁即/member/index.php頁面,此時該頁面判斷用戶情況:已登錄用戶則直接顯示本頁內(nèi)容(可以采用檢查session等方法);未登錄用戶則需要登錄(重定向到/member/login.php);出現(xiàn)了未知錯誤(重定向到/error.php)。同時采取相應的反應。
假如用戶被引導至登錄頁面即/member/login.php頁面,該頁面接受用戶的登錄信息(用戶名稱和密碼),并判斷是否正確登錄:正確登錄則再次重定向到用戶主頁/member/index.php;登錄錯誤則重定向到/error.php。
假如用戶被引導至錯誤顯示頁面/error.php頁面(無論是從以上哪個頁面前來),都會顯示錯誤信息。
流程圖示如下:
根據(jù)以上的文字描述和圖示,再結合MVC模式的實現(xiàn),可以非常輕松的寫出這幾個頁面的框架代碼:
先看簡單的頁面/error.php:
然后是/member/login.php:
最后是/member/index.php:
(注意:以上代碼只是片斷,而且沒有考慮項目全局,只起演示作用)
關于Controller
首先可以明確的是,以上的三個頁面代碼就是前文所說的Controller控制/流程代碼。很明顯,他們的不包含特定的操作,也沒有一行網(wǎng)頁代碼,有的只是與前面流程圖一致的流程控制代碼(放眼望去,這些頁面的共同特點是充滿了引用網(wǎng)頁模板并輸出、取得對象并執(zhí)行其某個方法或者重定向)。
再選擇其中的一個頁面/member/login.php詳細的解釋。整個頁面通過判斷是否提交表單分為兩個部分:顯示登錄表單供用戶填寫和處理登錄信息。作為前者直接引用一個處于網(wǎng)頁模板目錄/Templates下對應該頁面的member_login.dwt并在解析后輸出;作為后者先取得一個Member對象(該對象出于商業(yè)邏輯目錄/Include下的Member.inc.php中),然后獲得登錄判斷的結果后進行重定向。在這個控制頁面的代碼中,member_login.dwt作為View視圖/界面出現(xiàn),類Member作為Model模型/邏輯出現(xiàn),而頁面代碼本身就Controller控制/流程。下面就是加入標示的/member/login.php框架代碼:
(關于模板類以及在MVC模式中的應用,可以參考本站另一篇文章《在PHP中選擇合適的模板》)
關于Model
既然談到了Model,下面就是另一個重要的話題:類封裝在PHP項目中的應用。
請注意用詞"類封裝"--這和"面向對象"或者其他什么"采用對象設計"的方法有著本質的不同。"類封裝"只是講述了將商業(yè)邏輯采用類方法的方式封裝成各個不同的類,因而這里的"類"并不是因此采用了面向對象設計出現(xiàn)的"類"--準確的說,這里的"類"其實是對一系列相關功能模塊進行合并的結果。
為什么不直接采用面向對象的方式而是采用這種看起來不倫不類的辦法去設計系統(tǒng)呢?PHP不是具有面向對象特性嗎?不錯,PHP具有這樣的特性,但是非常不完全(可以參考本站另一篇文章《從Zend Engine 2.0的設計藍圖(草稿)看PHP的將來》)。舉例來說,PHP是沒有接口這一概念和實現(xiàn)方法的,同時也就沒有什么多重繼承、方法重載之類的典型面向對象特征。如果非要采用面向對象的設計方法,也許在概要設計階段可以非常輕松,但是詳細設計階段就會比較苦悶,而如果還有幸堅持到編碼階段簡直就是苦不堪言了。另一方面,如果不在系統(tǒng)中引入類的概念,而是采用函數(shù)來實現(xiàn)模塊功能,那么可以想象在一個采用這樣"純粹"的中大型系統(tǒng)中會有多少的函數(shù),由此帶來的麻煩非常明顯。
還是回到PHP語言本身。雖然PHP提供不了什么實際的面向對象支持,但是還是提供了對類以及其中的屬性和方法的定義。那么自然而然可以想到的是采用類的方法封裝相關函數(shù)模塊,既可以借鑒一些對象設計的優(yōu)點,又可以避免完全采用函數(shù)模塊的一些缺點。
(一些采用函數(shù)模塊的系統(tǒng)會采用這樣一種方式:將相關的函數(shù)編寫在相同的文件中,這樣在引用時可以引入單獨的文件。比如Member.func.php這個文件中包含了所有與用戶相關的操作,在處理用戶登錄時可以先require這個文件,然后調用諸如member_login()這樣的函數(shù)。但是這樣的方式僅僅解決了系統(tǒng)中眾多函數(shù)的代碼組織問題,沒有解決名字沖突的問題。下面的舉例中就會看到。)
比如上文的用戶登錄實例中,如果采用函數(shù)模塊的方法,代碼也許是這樣:
而采用類封裝的方法,可能就是這樣:
也許您會覺得代碼并沒有什么區(qū)別(甚至看起來采用函數(shù)模塊的代碼由于不需要取得新的對象而顯得更簡潔一些),而真正的不同是發(fā)生在include的文件里面。采用函數(shù)模塊的方法將相關的函數(shù)集合在一個文件中加以組織(有些系統(tǒng)還不能做到這一點,那么就會造成異;靵y的局面),而采用類封裝的方法在每一個文件中聲明一個和文件名相同的類(比如在Member.inc.php聲明一個Member的類,這一點和Java的規(guī)定相似);而在使用時,都需要先進行include(如果采用函數(shù)模塊又沒有進行很好的組織,也許有些人就會很"簡便"的將所有函數(shù)include進每一個頁面--PHP可不是Java那樣編譯執(zhí)行,光是解析這些函數(shù)就會花費一段時間),但是關鍵就在于采用類封裝的方法可以清楚的指明調用的位置--某個類(Member)的某個方法(login):從避免名字沖突的角度來說這一點是非常成功的;而對于代碼檢查和維護而言,方便程度更是不言而喻。設想一個頁面需要完成若干功能,因而需要include數(shù)個文件:采用函數(shù)模塊的方法不能夠輕易的從函數(shù)調用中找到函數(shù)本身所在的文件(如果函數(shù)名稱或者include文件名稱沒有什么統(tǒng)一規(guī)則,那么這個工作就非常艱巨了),而采用類封裝的辦法可以根據(jù)類名稱和類文件名稱準確定位類方法代碼的位置。(也許您會認為這樣一個小小的好處不足掛齒,但是經(jīng)歷一個維護工程之后也許就不會再有什么異議。)
以上是采用類封裝方法的原因,決定采用這種方法設計系統(tǒng)只是第一步;完成整個系統(tǒng)的設計還有很多可以借鑒的經(jīng)驗。
部分設計可以借鑒面向對象的思路。雖然PHP中沒有接口和抽象類的定義,繼承機制也非常不完全,但至少具備了基本的類定義和簡單的繼承關系。類似"公司-雇員"、"賣家-商品-買家"這類顯而易見的關系可以很容易在系統(tǒng)中通過類和類關系定義。既然PHP可以做到這一點,就按照實際的邏輯關系去定義即可。
經(jīng)常會在系統(tǒng)中出現(xiàn)的另一個情況是關于個體和列表的關系--這樣說也許難以理解,想象一個BBS系統(tǒng)中的帖子列表和每個帖子之間,就是這樣的關系。根據(jù)設計經(jīng)驗,這樣的關系大量存在于PHP或者其他Web系統(tǒng)中。對于這類關系,我個人建議可以采用以下Item和Item_List的類封裝方式:
Item類定義:Item.inc.php的代碼
Item_List類定義:Item_List.inc.php的代碼
由于PHP對于類的成員變量和方法并沒有語法上的訪問限制(均為公開),因此會帶來對象使用方面的某些混亂;诖,建議在開發(fā)團隊的代碼規(guī)范中加以規(guī)定,從代碼應用的級別上控制這一情況:
首先,可以通過對成員變量和方法的注釋來說明其屬性,由此使用該對象的其他開發(fā)人員可以了解自己的使用方法是否觸犯了規(guī)定的訪問限制。(如果采用phpdoc等自動文檔生成的工具,開發(fā)人員甚至可以在不翻閱類源碼的情況下通過瀏覽類文檔正確使用它。)
其次,對于成員變量訪問限制的考慮,可以將一些主要的、經(jīng)常需要被訪問或更改的變量(在注釋中)聲明為公開。這樣的作法可以省卻大量get()和set()方法的代碼--雖然在其他的面向對象語言中這一點被認為非常丑陋,但是記住PHP不是Java,只要這樣的用法合理,就應該大膽使用。
從上面的示例代碼中您也許已經(jīng)注意到了注釋的比重--雖然大家都了解注釋的重要性,但是仍然有必要提出。這個示例中采用了Javadoc的樣式,利用現(xiàn)有工具也可以很容易的直接生成文檔(當然您和您的開發(fā)團隊也可以定義自己的合適注釋樣式和文檔生成工具)。對于系統(tǒng)分析員來說,您在設計階段完成之后交付給您的開發(fā)伙伴的代碼部分很可能就是這些注釋占絕大部分的框架代碼;你們之間交流的工具除了那些沒完沒了的圖表之外就是這些程序員最熟悉的代碼和注釋了。
在PHP系統(tǒng)中進行類的設計雖然不像構建面向對象系統(tǒng)那樣需要各種合理的模式介入(也沒有這樣的"本錢"為之),但還是需要一番思量的。邏輯上的合理性和操作上的可行性都是檢驗的標準。
(說到類設計,又想到了適合PHP開發(fā)的IDE問題。據(jù)我所知比較專業(yè)一些有Zend出品的Zend IDE;另外還有作為JBuilder的Open Tools出現(xiàn)的借助JBuilder的PHP開發(fā)工具;不過最常用的還是PHPEd或者UltraEdit之類的編輯器。如果現(xiàn)有的編輯器可以非常聰明的支持PHP的類設計和代碼實現(xiàn)就非常理想了。)
關于View
最后說到的是View方面,雖然這部分內(nèi)容與網(wǎng)頁設計人員聯(lián)系比較緊密,不過PHP項目(以及其他Web項目)的系統(tǒng)分析員也必須關注這一話題?梢钥闯鯩VC模式的應用使得網(wǎng)頁開發(fā)人員和程序設計人員的各自工作成果不會像以前那樣互相影響,自然可以提高各自的工作效率(相互關系也許會比以前更加融洽一些)。但是對于系統(tǒng)分析員來說,將用戶界面分離為各個獨立的網(wǎng)頁模板需要進行許多分析工作。
首先是確定整個系統(tǒng)的流程,這一點在系統(tǒng)設計的初期就應該做到。而對于View視圖/界面和Controller控制/流程來說,所有需要的頁面都是圍繞此流程產(chǎn)生。不過通常此時能夠在流程圖上看到的也許只是相關的參數(shù)在各個頁面之間傳遞,卻不能了解各個頁面展示的內(nèi)容--這就是下一步分析用戶界面需要進行的工作。
在分析用戶界面的工作中,第一步可以確定各個頁面核心、對于完成流程必不可少的用戶界面元素(表單和表單域、鏈接等);第二步是確定頁面中需要出現(xiàn)的導航內(nèi)容;最后還需要依據(jù)流程復核。還是以上文的用戶登錄為例。對于/member/login.php這個關鍵的頁面,第一步可以確定的是在用戶提交之前應該顯示一個表單,表單包含兩個文本框供用戶輸入用戶名稱和密碼;而提交之后根據(jù)流程在本頁面中不需要有用戶界面,取而代之的是利用Controller控制/流程這一邏輯層進行重定向。而第二步需要制定該頁面中(準確說是在顯示登錄表單時)需要提供的導航鏈接,在這里可以加上到系統(tǒng)的主頁或者其他非注冊用戶頁的起點的鏈接(方便用戶臨時決定取消登錄)以及一個注銷現(xiàn)有用戶的鏈接(針對已登錄用戶)。之后進行復核,此時也許會發(fā)現(xiàn)這一設計似乎沒有考慮到在登錄前更好的區(qū)別是管理員登錄還是普通用戶登錄,那么就可以在表單中增加一個隱藏域表示選擇登錄的用戶是準備以管理員還是普通用戶的身份進行登錄。
確定完用戶界面的元素,并不意味著可以將這些分析結果交付網(wǎng)頁設計人員進行制作了;還有最關鍵的一步?jīng)]有實施--為分析完成的各個頁面制定模板所需的變量名稱。對于以上的用戶登錄實例,如果系統(tǒng)有識別曾經(jīng)登錄用戶的功能(依據(jù)之前訪問時在客戶端設置的相關cookie值)并且把這個用戶名稱顯示在登錄表單的用戶名稱一欄,此時就需要在member_login.dwt設計中說明該表單域將被賦值為一個模板變量(比如{USERNAME})。這一步驟完成之后就可以交付網(wǎng)頁設計人員進行制作了。
需要指出的是,在編碼階段很可能局部的一些系統(tǒng)設計需要進行修改,這其中也許就包括對網(wǎng)頁模板的修改,需要仔細處理。
對于代碼組織的補充說明
還有幾個文件和目錄沒有在上文提及:
config.inc.php -- 如果您熟悉phpMyAdmin或者其他phpWizard.net釋出的項目,就應該非常清楚這個文件的作用:定義本項目范圍內(nèi)的全局變量(在每個頁面中被include)。我個人認為這是一個非常良好的設計,因此也提倡在項目中應用。另外,為了保證與項目中其他的變量沖突,建議在該文件中定義一個多重數(shù)組,而各種全局變量都以該數(shù)組的某一個值出現(xiàn)。這樣方便團隊中的其他開發(fā)者只需要避免一個變量名的使用,而不是避免所有config.inc.php中出現(xiàn)的變量名。使用這個文件的另一個好處是由于將關鍵的變量(比如與服務器環(huán)境相關的變量)集中定義,可以方便的安裝和移植整個項目。
security.inc.php -- 顧名思義這個文件控制并實施整個系統(tǒng)的安全策略。關于安全問題,可以想到的是兩種控制方案:在每個控制流程頁面頂端針對本頁面加以控制以及采用一個控制文件整個控制并被加入每個控制流程頁面。我個人提倡采用后一種方式,原因也很簡單:定義簡單而且維護方便。雖然相比每個頁面單獨定義,也許會損失一點點效率(一些不需要安全控制的頁面也需要include該文件并進行判別),但是獲得的是對系統(tǒng)安全的整體控制以及代碼維護的便利(損失一個if…else…的判別換取這樣的結果還是很值得的)。
/Temp -- 很明顯存在于這個目錄下的都是一個臨時文件,并且這個目錄其實并不會出現(xiàn)在項目正式發(fā)行的版本中。如果開發(fā)時對一些函數(shù)的使用不甚明了或者試驗一段沒有相關經(jīng)驗的代碼,都可以在此目錄下建立文件;因為該目錄就位于項目代碼之中,可以非常便利的取得項目運行的上下文環(huán)境,大大降低了試驗代碼的成本。
/admin -- 通常對系統(tǒng)的后臺管理內(nèi)容應該放置在一個獨立的目錄中,我個人比較喜歡admin這個簡寫詞(當然也有一些情況系統(tǒng)分析員認為不應該設置一個容易猜測的管理目錄名稱以增加一重對系統(tǒng)安全的保護)。
/css和/scripts -- 都是與網(wǎng)頁設計也就是View視圖/界面有關的文件存放處,分別是樣式單和客戶端腳本。這樣做的好處在任何一本講述網(wǎng)站規(guī)劃的書籍中都會有所提及。
方案二:簡單網(wǎng)站項目
系統(tǒng)性能是這類項目追求的首要目標,而與此同時系統(tǒng)的維護和擴展幾乎可以不用多加考慮。(也許這句話聽起來有些絕對,但是根據(jù)客戶的需求和項目的性質判斷,盡最大可能以最短時間滿足客戶的需求并使得系統(tǒng)高效運轉就是項目成功的最好檢驗標準。)因此,也許這類項目就是PHP黑客的天堂(曾經(jīng)我也是一個過分追求PHP使用效率的人)。由于這類項目的特殊性,這里討論的范圍不僅僅局限與系統(tǒng)設計而是從組建項目小組開始直到交付項目的過程。
首先需要關注的是參與項目的人選(雖然也許這是項目經(jīng)理的職責,但是最熟悉PHP項目特點的系統(tǒng)分析員應該參與)。在PHP開發(fā)人員方面,至少應該選擇對PHP各種函數(shù)較為熟悉的開發(fā)者(這類項目不適合作為現(xiàn)實項目以培訓參與的開發(fā)新人),如果公司中還有能夠在源碼級別理解PHP的人員就更加理想(不過通常對于一般的PHP開發(fā)公司是不可能的)。而在網(wǎng)頁設計人員方面,最好可以選擇一些略通客戶端(比如JavaScript)以及服務器端(最好是PHP)腳本的人員;因為這類項目的一大特點即是單個網(wǎng)頁代碼量較大且夾雜網(wǎng)頁代碼(通常是HTML)、客戶端腳本(比如JavaScript)和服務器端腳本(比如PHP),加入了解各種腳本語言的網(wǎng)頁設計人員的目的不是為了增加團隊的PHP開發(fā)力量,而是避免在修改網(wǎng)頁時影響程序設計人員的工作。
其次就是面向過程,準確說是面向頁面的系統(tǒng)設計。相對第一類項目,客戶的需求在該類項目中表現(xiàn)得非常清晰,而且一般長期進行Web開發(fā)的公司對于這類網(wǎng)站項目也應該有一定的設計經(jīng)驗積累。設計中需要圍繞整個系統(tǒng)的流程,包括每個頁面的輸入?yún)?shù)和輸出內(nèi)容(包括網(wǎng)頁中出現(xiàn)的除導航鏈接之外的功能性鏈接),以求完全滿足客戶的需求;另一關鍵在于確定系統(tǒng)安全策略,在這類項目中主要是用戶等級的確定和頁面的訪問權限,并給出實現(xiàn)的方式。不過還需要指出的是,這類項目中由編碼階段返回設計階段的情況并不少見,對于局部設計(比如頁面?zhèn)魅雲(yún)?shù)或者輸出鏈接)的更改應該加以及時控制。
最后是針對代碼和數(shù)據(jù)庫的優(yōu)化。在這類項目中需要適當鼓勵開發(fā)人員的黑客態(tài)度。推薦的辦法是系統(tǒng)分析員給出每個頁面的偽代碼(框架代碼),而局部的實現(xiàn)則由各個程序開發(fā)人員和網(wǎng)頁設計人員進行。
對于PHP代碼方面,通?梢詮娜缦聨追矫婵紤]:
算法的選擇和功能實現(xiàn)的方式:模塊級別的優(yōu)化,可以由幾名開發(fā)人員共同討論解決;
函數(shù)的使用:代碼級別的優(yōu)化,需要開發(fā)人員對各類函數(shù)有清楚的認識,至少養(yǎng)成多多參考函數(shù)手冊的習慣;
數(shù)據(jù)庫的查詢和更改即SQL語句的使用:如果公司中有相關數(shù)據(jù)庫系統(tǒng)的管理人員,可以就一些優(yōu)化問題征詢他們的建議;
其他應該避免的問題:比如拷貝代碼、等不良代碼情況。
而根據(jù)我的經(jīng)驗,通常會在這類項目中撰寫的黑客代碼如下:
循環(huán)語句的使用特別是在查找時的應用:此時注意while和for的區(qū)別(想必大家在大學課堂中都做過這類的程序),這也是良好的編程習慣;
SQL語句的優(yōu)化:首先是盡量避免多余的數(shù)據(jù)庫交互,這是提高效率非常重要的一點;其次是不要害怕長達幾行的語句而寧愿使用所謂簡單的語句;再次是認真考慮查詢語句返回的字段,減少不必要的數(shù)據(jù)。
表單提交值的獲取,比如復選框和文本域。精巧的表單域名稱設計可以減少一定的代碼量,而處理提交值時也需要注意處理的方式。
黑客代碼在這類項目中值得鼓勵,不過最好在每段代碼旁附上盡可能詳細的注釋。
由于該類項目的特殊性,完成項目的關鍵不僅僅在于系統(tǒng)設計階段,因此給出項目開始、系統(tǒng)設計、編碼以及測試、交付這一過程的簡單描述:
挑選合適人員組成項目小組,可以考慮銷售人員和客戶代表的加入。
系統(tǒng)分析員可以簡單的從客戶的需求以及以往項目經(jīng)驗的結合中總結出系統(tǒng)所需的每個網(wǎng)頁并對其功能作出描述,同時確定初步的安全策略。這一步驟中可以加入銷售人員和客戶代表的加入。(此時網(wǎng)頁設計人員正在準備提供給客戶的一系列網(wǎng)站形象頁面。)
詳細設計中需要為每個頁面確定位置和名稱,更加關鍵的是確定輸入?yún)?shù)和輸出內(nèi)容以及不同級別用戶對于網(wǎng)頁的確切訪問權限。同時進行數(shù)據(jù)庫設計。該階段完成后至少應該提供系統(tǒng)的流程圖(包括訪問權限標識)以及數(shù)據(jù)庫設計資料。
網(wǎng)頁設計人員和程序開發(fā)人員拿到相關資料各自進行工作。對于前者,根據(jù)客戶認可的一套形象設計每個頁面;對于后者,開始進行"興奮的"(因為此時要求的是高效簡介的代碼--黑客代碼)編碼工作。此階段工作中遇到的困難均需要反饋到系統(tǒng)分析員處,可能返回以上的第3步甚至第2步進行設計修改。
程序編寫和網(wǎng)頁設計結束后需要有一段整合的時間,也是程序開發(fā)人員對代碼進行自我測試的階段。同時在這一階段可以進行的是代碼(包括網(wǎng)頁代碼和程序代碼)和數(shù)據(jù)庫的優(yōu)化工作。此階段結束后應該可以提供一個完整的系統(tǒng)。
真正的測試階段通常都比較倉促,這方面的技術和經(jīng)驗公司也應該有一定積累(如果有條件希望采用一些軟件工具進行穩(wěn)定性和抗壓能力的測試)。最后是提供一個可Web訪問的地址供客戶測試。此階段完成后可以提供正式交付客戶的系統(tǒng)。
方案三:綜合性網(wǎng)站項目
已經(jīng)有一些大型網(wǎng)站使用PHP作為主要的開發(fā)語言。對于這類項目,單純從PHP技術方面值得提出的話題不多,簡而言之還是根據(jù)網(wǎng)站各部分的實際應用情況(訪問強度、操作行為等)選擇以上提出的兩種項目設計方法或者綜合使用。除此之外,根據(jù)我個人的經(jīng)驗,項目團隊的組織和協(xié)調工作以及項目各期完成后的維護工作等等是較之單純的技術更加關鍵的因素。
對于這類項目,可以提出的建議是,適當采納一些開源軟件對于快速、優(yōu)質的完成項目很有好處。項目的某些部分可以直接引入開源軟件項目的設計甚至是代碼,不過前提是系統(tǒng)設計人員對這些引入的項目需要非常了解,同時需要做好這些孤立的開源項目和整個項目之間的接合(比如安全策略的考慮和全局變量的引用等)。
舉例來說,根據(jù)客戶要求某個綜合網(wǎng)站需要以下的功能:
復雜的新聞發(fā)布;
需要不多管理功能的在線論壇;
簡單的產(chǎn)品陳列;
需要用戶管理。
(很明顯這是一個企業(yè)網(wǎng)站的雛形。)
其中的1、2項很明顯可以借用一些成熟的開源軟件項目,而3項由于客戶需求簡單自主開發(fā)比較符合成本。由此看來4項則是整個系統(tǒng)中最重要的部分--需要做好與1、2項使用的開源軟件項目的用戶管理集成工作(3項由于自主開發(fā)的原因集成工作非常簡單)。(某些技術積累較好的公司甚至對于以上提及的集成部分都有簡單的解決方案,那么這樣一個網(wǎng)站項目的完成所需成本非常微小。)
幾個特殊的功能點
另外還有一些通常項目中都會出現(xiàn)但是必須妥善處理的功能點:
1. 數(shù)據(jù)列表分頁。
關于這個功能,互聯(lián)網(wǎng)上的中文和英文資料都有許多。具體的技術和實施細節(jié)不需要多說,這里只需要指出的是:
A. 建議封裝成某一個工具類的方法或者其他可復用的形式--這樣的好處不言自明,任何人都不希望系統(tǒng)中只要存在數(shù)據(jù)列表分頁的時候都會出現(xiàn)一堆幾乎相同的代碼。
B. 如果針對一些效率要求較高的項目(例如上文提到的"簡單網(wǎng)站項目"類型),應該直接使用PHP自帶的針對特定數(shù)據(jù)庫系統(tǒng)的操作函數(shù)以及與該數(shù)據(jù)庫系統(tǒng)相關的結果集截取技術(SQL語句),比如MySQL中的'LIMIT start, offset'之類;其他一些需要系統(tǒng)設計工整合理的項目(例如上文提到的"設計大量商業(yè)邏輯項目"),如果采用了通用的數(shù)據(jù)庫接口,出于兼容多種數(shù)據(jù)庫系統(tǒng)的考慮,可以采用此接口完成結果集的篩選,以損失的效率換取系統(tǒng)更好的可維護性和可擴展性。也就是說,對于采用特定數(shù)據(jù)庫操作函數(shù)還是第三方通用數(shù)據(jù)庫接口來實現(xiàn)數(shù)據(jù)列表分頁,需要考慮系統(tǒng)的性能和擴展兩方面因素。
2. 錯誤控制。
這一點在上文之中也有提及。除了建立屬于工具類的錯誤類之外,最好可以建立專門的錯誤顯示頁面。該頁面既可以是靜態(tài)的HTML頁面(表達一些對用戶的歉意和出錯之后的處理指導)或者動態(tài)的PHP頁面(可以包含具體的出錯原因和地點以及其他更詳細的信息,前提是在系統(tǒng)安全策略允許提供這些信息)。而錯誤類的任務就是接受正常的程序中拋出的錯誤,進行必要處理之后將信息一起重定向在錯誤顯示頁面上。
同時,建立出錯頁面對于開發(fā)階段也有一定好處,可以彌補現(xiàn)有PHP缺少類似try{…} catch{…} 塊的違例控制的缺點,將調試中的錯誤或者輸出通過錯誤類拋出并顯示出來。
3.上載與下載。
對于PHP來說,上載的實現(xiàn)并不會像其他流行的Web開發(fā)語言那樣需要第三方程序的支持,內(nèi)建的機制可以非常簡單的處理。不過這里提及的是一些復雜的上載功能實現(xiàn)?疾煲韵乱粋處理附加文件的流程:
該功能使得用戶在撰寫新的消息時可以附加其他文件,而且在消息沒有提交之前可以隨意的對已經(jīng)附加的文件進行刪除或者繼續(xù)增加。這種需求會體現(xiàn)在許多辦公相關的系統(tǒng)中,作為有經(jīng)驗的系統(tǒng)分析員應該在系統(tǒng)設計階段制定完成針對該類功能的實施計劃。比如在圖中所示的流程中,其實是通過一個或者多個表單的互相提交完成(具體設計不再贅述,提供相關的PHP文件參考;另外的一個直觀的例子就是多數(shù)免費郵件系統(tǒng)的添加附件功能,如果有興趣可以考察一下)。
至于下載,將文件置于服務器Web可訪問目錄下、提供訪問者真實文件路徑是最簡單的解決辦法;不過一些系統(tǒng)中對文件的下載基于某些安全策略需要進行身份方面的判別方可予以下載,這樣的方式就會帶來隱患。通常采用的方式也許是將文件放置在Web可訪問目錄以外的服務器文件系統(tǒng)中或者存儲進數(shù)據(jù)庫系統(tǒng)--都需要一個簡單的程序取得文件內(nèi)容并直接返回給發(fā)出請求的用戶(這其中涉及到一些HTTP輸出頭的問題請注意,提供一個PHP文件代替具體敘述)。在系統(tǒng)設計時針對不同的需求可以采用相應的辦法。
4. 客戶會話session的保持
PHP的現(xiàn)有版本已經(jīng)內(nèi)置了對session的支持,通常項目中都使用這樣的方式;一些特殊需要的項目(比如分布系統(tǒng))也許會采用復雜一些的處理方式。
在客戶端,通常采用的是設置cookie以識別特定的客戶,另一個可以應付不支持cookie的客戶端的方法是采用URL重寫加入足夠標示特定客戶的字符串。從這方面來說,采用PHP內(nèi)置的session支持最為理想,因為它可以自動的進行客戶端的FALLBACK:如果客戶端支持cookie,那么就順其自然;如果cookie不被支持,就采用URL重寫方式--一切都不需要開發(fā)者干預。如果采用其他session處理方式,或者自己編寫適應需要的session庫,需要注意的就是怎樣處理客戶端存儲數(shù)據(jù)的問題--cookie還是URL重寫還是兩者兼顧。
在服務器端,簡單說來只需要針對以字符串標示的每個特定客戶存儲相關的數(shù)據(jù)即可--可以采用的方式多種多樣,通常的方式是文件和數(shù)據(jù)庫。PHP內(nèi)置的session支持中,默認的支持方式是在系統(tǒng)的臨時目錄或者制定的目錄下為每個客戶建立一個文件存儲其數(shù)據(jù);當然也可以修改設置使其支持數(shù)據(jù)庫的方式或者其他方式。如果自己編寫session庫,根據(jù)系統(tǒng)的需要選擇一種合適的存儲方式即可。值得指出的是,對于分布系統(tǒng),如何共享服務器端的session信息是需要極大關注的。
另外,在設置session變量的時候,PHP內(nèi)置的session庫支持對象作為變量值(實際上所有的變量值,不論是一般的變量還是數(shù)組或是對象,都在經(jīng)過串行化之后被存儲),也就是說,以下代碼是可用的:
這一點對于上文提到的一些商用系統(tǒng)是有益的:首先,可以使用關于用戶的對象作為一個session變量值存儲一套信息,而不是割裂的多個session變量;其次,如果具有類似購物車的功能,可以以非常符合整個系統(tǒng)設計的方式(即前文所述商用系統(tǒng)的設計方式,強調類封裝)將該購物車對象放入session中。
5. ……其他和特定項目有關的功能點……
如果能在系統(tǒng)設計階段就預見并解決這些功能點固然很好,即使有少量未發(fā)現(xiàn)的功能點遺留到了編碼階段也并不可怕--通常這樣的遺漏并不會影響整個系統(tǒng)的架構,只是需要返回設計階段加入相應的內(nèi)容和文檔即可。畢竟對于相關項目經(jīng)驗不太豐富的系統(tǒng)分析員來說,做到設計階段對這類功能點了然于胸是不太現(xiàn)實的。
關于其他
對于PHP的爭論從前很多,不過自從Java在Web方面的優(yōu)勢越來越進入人們的視野之后,這樣的爭論倒偃旗息鼓了--看來大家都達成了共識--PHP對于嚴謹?shù)纳逃孟到y(tǒng)還是無能為力。不過基于此就一味否定PHP在商用系統(tǒng)中的應用也不大客觀,畢竟PHP還具有低成本的優(yōu)勢(這里的成本包括開發(fā)成本、使用成本和維護成本)。本文的目的除了講述一些PHP系統(tǒng)設計的方法之外,也希望吸引一些開發(fā)者或者企業(yè)采用PHP構建合適的商用系統(tǒng)。
另外,本文僅僅是我自己的一些經(jīng)驗,如果您看到這里時候已經(jīng)有了自己的一些想法,我非常樂意與您分享--能夠推動如PHP這樣無商業(yè)支持的開源軟件的發(fā)展,畢竟是一件非常令人興奮的事情。(從這方面來說,我甚至想撰寫關于PHP開發(fā)的文檔資料和示范項目,就如同Sun Microsystems為J2EE發(fā)布的Blueprint和Java Pet Store--可惜暫時受到時間、精力以及個人能力的限制--也許春節(jié)假期是一個好時機:)
參考資料
本文中提及的文章和代碼
代碼:用戶登錄實例(包括控制頁面index.php,login.php;網(wǎng)頁模板member_index.dwt,member_login.dwt;邏輯類Member.inc.php)相關附件;
代碼:類封裝實例(Item類定義Item.inc.php;Item_List類定義Item_List.inc.php)相關附件;
代碼:上載實例(控制頁面add.php,attach.php)相關附件;
代碼:下載實例(控制頁面download.php)相關附件;
代碼:實例代碼組織中的兩個全局文件(config.inc.php,security.inc.php)相關附件;
文章:關于PHP的未來;
文章:模板在PHP中的使用。
其他參考資料
PHP官方網(wǎng)站--http://www.php.net
包含軟件和文檔以及使用情況等(本文成文時的最近動向是PHP 4.1.0釋出,在某些方面有較大改進)。
為PHP提供商業(yè)支持的Zend公司--http://www.zend.com
包含PHP相關的工具和文字資料(可以尋找到一些與本文主題相關的話題)。
著名的開放源碼項目網(wǎng)站SoureForge--http://www.sourceforge.net
開放源碼項目的聚集地,并提供基于Web的各種便利工具(可以尋找到成千上萬PHP撰寫的項目)。