淺談PHP代碼設計結構

火星人 @ 2014-03-12 , reply:0


  

coding多年,各種代碼日夜相伴,如何跟代碼友好的相處,不光成為職業生涯的一種回應,也是編寫者功力的直接顯露。

如何看待程序和代碼呢?

那就讓我們從程序定義來談起,

如果從業務最終呈現來看,一個程序可以看成是一個真實業務需求的邏輯代碼映射。

如果從程序邏輯結構看,程序就是數據結構加演算法的結合。

這樣看,為滿足更多的業務需求,更好的滿足這些需求,就需要更多的程序代碼,

當程序代碼堆積達到一定數量后,如何管理好,整理好已有的代碼將會成為一個只管重要的問題。這個也是一個程序員編程3~5后,從中級向更高級別探索的一個瓶頸。

滿足需要可工作的代碼是好的,可被多個需求不斷復用的代碼,就是更好的了。

隨著軟體設計的發展,代碼的集合,功能邏輯不斷向下沉澱封裝的趨勢越來越明確。

使用好一個工具很快,掌握好一種設計思想就要不斷的嘗試和改進了。

有專門處理數據的代碼,有專門處理呈現的代碼,如何在業務流程中管理配置他們?這些邏輯如何更好的被封裝,被複用。

其實對於PHPer來說,這些思想在處理具體業務來說有些麻煩,這也是PHP的最大優勢非常的自由方便,自由簡單隨意的基本語法,方便的連內存資源都不用考慮,很快就可以hello一個,

但這也正是PHP一個先天的重大劣勢,沒有一個系統的成脈絡的設計體系,

PHP出生時就是一個單一的滿足業務的語言,並沒有像JAVA一樣有很系統設計體系和原則。在JAVA有三個最基礎的設計原則:1,不支持全局變數。2,不寫萬能類。3,代碼必須是類封裝。

JAVA的第一個,第三個原則是在語法上就限制了,第二個原則是評判一個JAVA程序員是否入門的標準。PHP相對來說就沒什麼這樣的語法上的設計原則限制,可接觸了一些big company真沒有體系原則呀,哎,

但在我們設計思想里可不能真的沒有原則呀!

PHP程序其實是怎麼方便怎麼來,解釋器很強大,可以屏蔽包容各種思路的程序代碼,只要語法OK,不在乎代碼設計。

正因早期的PHP太隨意了,入門很容易,不用很好的對代碼進行有效的管理和方便的復用。

隨著PHP的發展,PHP已經告別在PHP3~PHP4時代動態標記語言,但因為向上兼容原則,PHP還是一個語法寬鬆的語言,

系統化的程序設計原則還沒有強制融入到語言核心中來。

這樣並不代表我們不需要使用成熟的設計思想來完善和編寫我們的程序代碼。

JAVA的程序設計原理和代碼積累,是JAVA的精髓,隨著時間的積累越發明顯。

將JAVA的程序設計思想,引入到PHP的編程過程中來。是一個完善PHP代碼的很好的方法。

1,代碼分級封裝

2,文件靈活調用載入,資源隨用隨創建

3,平整抗老化的目錄結構

解決這些能為程序編寫過程,帶來非常多的益處。

如何解決呢?
可以通過對於MVC設計思想進行的拆解封裝,來實現清晰,有效,一致性的程序設計思想。
一個程序從邏輯結構上可看做是模型(Model),視圖(View)和控制Controller)三個邏輯塊。
在JAVA中Model層實現系統中的業務邏輯,通常可以用JavaBean或EJB來實現。 View層用於與用戶的交互,通常用JSP來實現。Controller層是Model與View之間溝通的橋樑,通常是router servlet嚮應用端的擴展。
可 PHP 沒有這樣清晰的劃分,所以需要將設計思想揉入到程序代碼中去。

1,程序代碼類封裝

對於一個應用需求(ex: similar相似商品頁),將需求先分為3個文件:similar.controller.php;similar.model.php.similar.view.php
通過名字我們也可以看出各文件中的代碼用途,剩下就是要給各個文件中的類進行命名。
因為是這對一個應用的不同操作類,所以需要給各個類加不同的類目後綴,

例如similar.controller.php

1
2
3
4
5
6
7
8
9
10
11
//controller 控制程序類 完成此類中的方法是程序各頁面流程,每一個方法對應一個同步請求頁面(以配合頁面的調用規則)
class similar{
//相似商品頁的展現
functio do_opensearch($_param){
//請求參數過濾
//請求商品數據
//根據呈現規則處理商品數據
//渲染商品數據獲取呈現html
//展示呈現html
}
}

對應的請求就是

http://s.etao.com/similar/opensearch/

similar.model.php

1
2
3
//model 數據操作程序類 完成對此應用的數據請求封裝,返回結果的結構解析處理
class similarMODEL{
}

similar.view.php

1
2
3
4
5
6
//model 呈現操作程序類 完成對此應用的數據呈現封裝,返回給已經處理好的
class similarVIEW{
function view_getDetail($_goodDataArr){
return $html;
}
}

2,類調用

我們很清晰的將各個邏輯將不同代碼封裝在了不同文件和類中了。如何靈活的調用呢?PHP是一個解釋語言,沒有像JAVA和C一個的編譯過程,無法享受編譯語言,在預編譯過程中的自動連接功能。
其實現在PHP解釋器也已經意識到這個問題,並在5.3.1以後提出了autoload方法來解決,當解釋器遇到一個非聲明類或者函數時會自動運行autoload去再載入一次,如果還沒有才報錯。
詳情參見 http://php.net/manual/en/language.oop5.autoload.php

但這樣還是一個被動解決方案。下面提供一個主動動態載入方案。加入資源系統調動類
在講解這個類的使用之前先看一下我們之前載入並使用一個類文件的過程:
文件:similar.controller.php

1
2
3
require_once PATH."similar.model.php";
$sModelObj = new similarMODEL;
$goodsData = $sModelObj->getGoodsInfoByNid($nid);

這是在過程中調用,如果在controller類內調用的話就需要寫成這樣,

1
2
3
4
5
6
7
8
9
10
11
12
require_once PATH."similar.model.php";
class similar{
public $_modelObj;
function __construct(){
$this->_modelObj = new similarMODEL;
}
//相似商品頁的展現
functio do_opensearch($_param){
//請求商品數據
$goodsInfoArr = $this->_modelObj->getGoodsInfoByNid($nid);
}
}//end class

如果controller類需要使用另一個MODEL,例如forest,ha3什麼的就要聲明多個類內元素變數。如果利用PHP 的變數傳導特性,建立一個資源調度類,來統一載入和調度需要的類並聲明,使用起來就會方便很多。下面我們來詳細的分解下這個資源調度類的設計,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 載入指定類型的類程序
**/
class Box {
//聲明一個進程內資源對象池
public static $_modelObjArr;
//獲取一個資源對象
public static function getObj($_appName,$_typeStr='class') {
 
if($_typeStr=='class'){
$className = $_appName;
}else{
$className = $_appName.strtoupper($_typeStr);
}
//資源對象已創建 直接返回使用
if( isset(self::$_modelObjArr[$className]) && is_object(self::$_modelObjArr[$className]) ){
return self::$_modelObjArr[$className;
}
//載入文件資源類文件
$file = dirname(__FILE__)."{$_appName}/{$_appName}.{$_typeStr}.php";
if( file_exists($file) ){
require_once $file;
if( class_exists($className) ){
return self::_createObj($className);
}else{
$errStr = "no class {$className} in file {$file}"; //類名錯誤
}
} else {
$errStr = "no class file {$file}"; //類文件錯誤
}
self::_showErr($errStr);
}
//創建資源對象
public static function _createObj($_className){
if( isset(self::$_modelObjArr[$_className]) && is_object(self::$_modelObjArr[$_className]) ){
return self::$_modelObjArr[$_className];
}else{
self::$_modelObjArr[$_className] = new $_className();
return self::$_modelObjArr[$_className];
}
}
//錯誤提示
public static function _showErr($_errTypeStr=''){
echo $_errTypeStr; exit;
//errorlog($_errTypeStr);
}
}//end class

改用Box資源調度類,載入類程序
文件:similar.controller.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class similar{
 
function __construct(){
}
//相似商品頁的展現
functio do_opensearch($_param){
//請求商品數據
$goodsInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid);
$html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr);
//其他已封裝好的數據邏輯
$hotHtml = $this->_getHotHtml();
}
function do_search($_param){
$goodsInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid);
$html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr);
}
function _getHotHtml(){
$hotInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid);
$html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr);
return $hotInfoArr;
}
}//end class

這樣在一個響應進程內 “similar.model.php”; 文件只會被載入一次,similarMODEL 也只會被創建一次,
隨使隨調用。不用提前聲明,不用考慮重複載入,重複創建。

Box解決了類與類間資源的調用和創建。

那如何讓一個controller類文件中的function 運行呢?我們需要一個請求調度類 Bin。

之前我們的請求響應都市通過websearch的io model來完成。吧請求的path映射到實際文件中。
例如:
xxxx.taobao.com/similar/opensearch/index.php 或者是 xxx.etao.com/similar/opensearch.php 這樣的文件,websearch將host替換成實際文件系統的path。

這樣外部程序可以各種掃描我們的目錄,一旦疏忽就會把一些常用方法暴露出來,ex:xxxx.taobao.com/tools/info.php 什麼的,有風險隱患。

當然加一些過濾條件是可以屏蔽這樣的問題,但會增減webserver的邏輯,一旦有改動就會改動webserver的conf。麻煩。索性我們就webserver踏踏實實的做好io和http打包解包,

讓app server來過濾和靈活配置請求調用。再webserver中將所有的訪問都映射(rewitre)到host/dispatch.php,由Bin類來實現調度和分配。實現應用邏輯單一入口調用結構。

dispatch.php完成一個平台分配,性能監控,配置載入,應用啟動,分類型輸入(同步,非同步,PC,mobile),等平台級功能。

Bin類就需要如下功能: 解析請求,將webserver傳遞的環境變數傳遞到響應的實際處理應用來完成分解,提出similar 應用名稱,opensearch 方法名稱,

還有GET,POST請求參數,組裝成請求參數數組,

1
2
$this->_paramArr = explode( '/', trim(strtok( urldecode($_SERVER["REQUEST_URI"]),'?'),'/'));
$this->_paramArr = array_merge($this->_paramArr,$_GET);

這樣$this->_paramArr[0]就是appname應用名稱
這樣$this->_paramArr[1]就是function方法名稱

平台流程就可以裝載appname.class.php 文件並執行 appname->do_function($this->_paramArr);方法。
這樣也支持/similar/opensearch/sort/這樣的傳參,sort是$this->_paramArr[2]的一個參數

如果$this->_paramArr[0]等於ajax也就是對於的這是一個js發起的非同步請求,

要裝載的就是appname.ajax.php,並執行appnameAJAX->ajax_function();

將同步和非同步請求分文件。減少載入大文件對內存的佔用。

同時也可以將可訪問目錄跟應用目錄物理上分類,讓掃描程序無用。

例如:

/home/website/host/ 這個目錄只放index.php一個文件,和css,js,images一些沒有加入cdn的呈現渲染文件。將webserver的docmentroot 或者laction 設置在這個目錄
/home/website/app/ 這個目錄來存放編寫的類程序文件,配置,由/host/index.php程序載入app目錄下的類程序文件

一個應用好辦,一堆應用我們怎麼辦呢?下面我們來說說多應用的目錄結構。

3,目錄結構 (抗衰老很重要)
PHP的設計理念這是少又少,連個包的概念都沒有。只好我們自己用目錄來包裝包概念。從訪問結構,開發結構,部署結構上來分別介紹目錄結構的作用.

3a.訪問結構
將訪問目錄host,和應用程序目錄app可以看成是一個website,這個site下面的應用可以這樣的

可以這樣建立
/website/host
/website/app/similaer
/website/app/cmp
/website/app/srp
/website/app/…..
假如管理工具也可以再website下同另起一個目錄
/website/admin/firebox
/website/admin/seoAny
這樣,所有的請求都會到/website/host/index.php由index.php在根據請求規則,載入響應的邏輯程序。

跟/webiste/host并行的有一個system目錄,/website/system/,將box.class.php,bin.class.php,base.class.php等平台文件存放其中。
一個響應的執行流程就是
webserver query ->[ /website/host/index.php include /website/system/box,bin,base (應用架構)] 平台-> [ /website/app/ (應用流程) | /website/admin/]頁面

3b.開發結構
在開發和維護過程中,會有一些同樣地php庫文件,如curl通信,xml解析,log,timer,template(appview),等自己開發的類程序,也有像smarty,big2gb等第三方類庫。
各個項目都通用,比價,主搜,我的一淘,那就可以統一建立一個PHPLIBS目錄與website平級
/website/host/
/website/app/
/website/system/
/PHPlibs/etao/ 自己的類庫
/PHPlibs/other/ 第三方類庫

每一個website一個svn
PHPlibs/獨立一個svn 專人維護,多快好省精深專

將template文件從website/中分離出來建立一個
webtemplate/目錄 跟ued同學共享一個svn, 讓ued同學按前段template類的規則書寫模板文件。按應用分目錄存儲。
如:
webtemplate/srp
webtemplate/cmp
webtemplate/opensearch
webtemplate/frame 統一呈現模板,頁頭尾
webtemplate/…..
website/app中的程序通過template類方法來調用以上模板文件。

同時在開發和維護過程中也會遇到需要shell來執行的php script ,如定時生成公共頁頭尾,定時取mysql庫中的epid數據等。或者臨時做一個演算法的程序。
那我們就可以在
/website/app/的相關應用中加入app.sh.php程序文件,

/website/app/similar/similar.sh.php 這個文件的類程序similarSH是通過
/website/script/run.php 來執行的。 script/run.php是一個shell端的控制器響應CLI請求,host/index.php是一個web端的控制器響應CGI請求
還可以在script/run.php 外包裝一個debug.bat的文件,可已經在dos,或者包裝一個debug.sh 程序再shell下直接交互調試。
具體參考代碼

還有一些程序處理文件,如由vm文件轉換成的php模板的公共頁頭尾文件,會有一個跟website平行的webdata文件,
/website/….
/webdata/pageframe等目錄中,在app中直接調用

3c.部署結構
通過上線腳本分別checkout
/website/
/PHPlibs/
/webtemplate/ svn資源庫

然後打包rpm,推送上線。

單獨上線,也是使用上線腳本配置檢查出相應文件,推送到前段生產機。

4,代碼規範

關於代碼格式規範,看之前大家也都積極的討論了很久了。

代碼格式規範,我們在IDE中配置一下,自動使用,並通過不斷的review和提醒中培養好的書寫易讀代碼的習慣就好了。

代碼設計規範,是需要根據架構指定,並堅持執行的。

下面談到代碼設計都是基於剛剛談到的,資源調用,請求分配來制定的。

4a,關於目錄
//應用程序
/website/app/….
//對外訪問目錄
/website/host/index.php
/website/host/css
/website/host/js
/website/host/images
//平台系統文件
/website/host/images
//調試,維護腳本
/website/script/
//模板庫 對應/website/app/下的目錄
/webtemplate/….
//程序類庫
/PHPlibs/
//數據文件目錄
/webdata

沒什麼好說的,大家體會。

4b,關於文件
文件的命名規範主要是指定內部調用,和請求調用
website/app/appname/appname.apptype.php
apptype: class,model,view,ajax,api,sh ,….
調用:
Box::getObj(appname,apptype)->function();

appname.class.php文件為controller文件,類中的方法做同步請求響應。
appname.ajax.php文件也是controller文件,類中的方法做非同步請求響應。
appname.sh.php文件也是controller文件,類中的方法做CLI響應。
appname.api.php文件可以看成是controller文件,類中的方法是對一個公共,或通用邏輯的封裝,如獲取圖片完整路徑。

4c,關於類和方法
類命名:
除class文件中的直接以appname命名,其他的類文件都是appname+APPTYPE命名,APPTYPE需要大寫。好做語法區分。
方法命名:
在方法聲明中加前綴,說明方法的作用,如view類中的方法加html_getSimilarList(), model方法中加入db_getData()從資料庫取,file_getData()從文件取,url_getData()從引擎或者遠端取

4d,關於變數和註釋
類內變數, 參數入口變數 加_前綴, 跟過程中使用的局部變數,加以區分,增加可讀性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class similar{
public $_obj;
function do_getpage($_param){
}
}
 
文件註釋 = 類註釋
在文件開頭加入,說明這個類的功能:
/**
* @package:
* @access: MixrenSystemBox.inc.php
* Summary: 系統 應用模板控制分配程序; 完成請求分析; 模塊載入; 請求處理(執行);
* @Created: Fri Dec 25 16:41:02 CST 2009
* @Author: Zhurong
* @Generator: EditPlus2 & Dreamweaver & Zend & eclipse
*/
 
方法註釋
/**
* 初始化請求
* param參數說明
**/
private function _parseApp(){
$this->_queryStr = urldecode($_SERVER["REQUEST_URI"]);
$this->_paramArr = explode( '/', trim(strtok($this->_queryStr,'?'),'/'));
//分配請求模塊
$appName = DEFAULT_APP_NAME;
$this->_className = $appName;
$this->_appFile = APP_PATH . "{$appName}/{$appName}.controller.php";
$this->_method = empty($this->_paramArr[0]) ? DEFAULT_APP_METHOD : $this->_paramArr[0];
$this->_method = "do_{$this->_method}";
}
 
類結章節附註釋
class Bin{
}//end class

為主要過程,演算法,加入註釋,方便維護和閱讀。

在上線前會通過上線腳本對文件進行 php -w 去註釋過程,所以註釋寫的長不佔用程序執行過程中的載入內存。

5,代碼監測

專門的代碼掃描腳本。

掃描/website/app/下的目錄,文件,方法,註釋量。

出報告,多少應用目錄; 多少同步響應類;多少非同步響應類;多少同步頁面;多少非同步介面;多少邏輯介面;多少model介面;多少模板;各個文件的代碼註釋率;等等。

基於以上設計思想建立了從文件夾-》分段類文件-》功能模塊方法 的樹形結構設計程序架構,最大化實現CAP原則 consistency(一致性),availability(可復用),Partition(有效劃分)

讓PHP代碼更好的積累。

模板(純html文件,呈現配置),程序數據配置文件是被載入到程序中,而不是現在將程序載入到html中一段段解釋執行。

 






[火星人 via ] 淺談PHP代碼設計結構已經有213次圍觀

http://www.coctec.com/docs/program/show-post-71354.html