一、DPT介紹
PHP為什麼在主流的應用中總是那麼不出色,總是不如.Net/Java,就是因為在PHP處理大型應用的時候,那些不完整的面向對象機制、資料庫 處理的單一,不通用性等等,影響了PHP做大型應用。那麼,如何來改變這個狀況呢?當然就是需要引進一些新的設計方法,把PHP中不健全的面向對象機制完 整起來,進行更好的PHP大中型應用的開發。 從Java過來的MVC模式非常流行,而且已經有部分已經引伸進了PHP領域,設計模式的引進,就是 為了更好的控制項目開發。今天我要說一種設計模式,類似于MVC,它叫DPT模式。其實有時候我也覺得有點象Java裡面的DAO(Data Access Object),不過DAO是夾在業務邏輯層和資料庫資源層之間的,而DPT更多的是把業務邏輯也封裝在類裡,和DAO層在相同的内容中。
D - Data,資料收集層 P - Php,PHP資料調用層 T - Template,模闆層
首先,我們要對它進行簡單的了解。 Data,就是我們的資料層,它不是資料庫抽象類,而是通過資料庫接口,執行一些SQL,把資料擷取的過程,一般把這種操作封裝在類裡面,就形成了我們的資料收集層。 Php,就是對我們收集的資料進行整理,規劃,同時解析模闆進行資料的顯示。 Template,模闆層,就是我們的HTML頁,裡面不包含任何PHP代碼,隻有模闆标簽的内容,通過它來控制資料在頁面中有格式的顯示。 我們這裡三層中,每一層都是鼓勵由一個人來開發,然後通過PHPDoc之類的工具,把源代碼中的API生成文檔,由P層的人進行調用。
那麼,在實際的項目開發中,它是怎麼運作的呢,我們又如何把這種設計模式引進我們的項目中呢? 我們下面将運用一個實際的項目來講解DPT模式。閱讀一下内容必須具備基本的PHP4的面向對象程式設計、資料庫抽象類、模闆等知識。
我們目前為了加速PHP的開發,都使用PHP封裝了部分功能,比如資料庫操作抽象類,模闆類等等,這些都是為了開發複雜應用而應運而生的。目前比較 主流的資料庫抽象類有phplib db、PEAR::DB、ADODB等等,模闆處理類有phplib template、smartTemplate、Smarty等等。本文中都是使用PHP Group推薦的産品,資料口抽象類使用PEAR::DB,模闆處理類使用Smarty,如果對這兩個類庫不熟悉的讀者,請參考文章後面的連結。
二、項目體系結構
下面我們來建構我們基于DPT模式的PHP應用。(以下部分内容參考《MVC模式、類封裝還是黑客代碼》)
檔案目錄結構(隻涉及到關鍵的目錄) class 類庫,包含所有的資料收集層 template 模闆檔案存放目錄 include 常用庫,包括PEAR、Smarty等類庫,同時還有自己定義的基本函數 config.inc.php 基本配置檔案,包括資料庫配置,其他基本資訊配置 security.inc.php 安全處理頁,主要多傳遞的變量進行處理 init.inc.php error.php 錯誤處理頁
class目錄中存放了我們資料收集層中的内容,一般的建議是每個類檔案隻是針對一個表進行操作,比如cmsMessage.class.php, 那麼這個類就是屬于功能CMS裡面的,隻負責操作Message這個表。所有的資料庫互動和操作都是封裝在類裡的,在P層不允許出現任何直接操作資料庫的 語句。 template目錄中存放了我們的網頁模闆,模闆中都是使用Smarty标簽進行排列的,同時,在模闆中,都是建議使用JS+CSS來控制頁面,模闆中隻有DIV标簽來簡單的排版,這樣,非常利于網站改版和更換皮膚。 include 目錄就是對常用檔案的包含,比如PEAR::DB類、Smarty類庫檔案等。config.inc.php就是基本的配置檔案,包括資料庫、基本常量等 等,security.inc.php是安全處理頁,我們這裡主要是做一個變量的安全檢查,下面内容我們将仔細介紹。init.inc.php是一個初始 化操作的頁面,包括初始化資料庫連結,執行個體化模闆處理類等等操作,error.php是錯誤資訊處理頁,所有的錯誤資訊通過URL編碼後轉到該頁。
三、項目基本配置代碼 關鍵頁代碼執行個體:
define('DB_HOST', 'localhost'); //資料庫主機 define('DB_USER', 'root'); //資料庫連結使用者 define('DB_PASS', ''); //連接配接密碼 define('DB_NAME', 'cms'); //預設資料庫 define('DB_PORT', '3306'); //資料庫端口 define('DB_TYPE', 'mysql'); //資料庫類型 define('DB_OPT', '1'); //是否長期連結 define('TPL_TEMPLATE_DIR', './template/'); //模闆目錄 define('TPL_COMPILE_DIR', './template/templates_c/'); //模闆編譯目錄 define('TPL_CONFIGS_DIR', './template/configs/'); //模闆配置檔案目錄 define('TPL_CACHE_DIR', './template/cache/'); //模闆緩存目錄 define('TPL_LIFTTIME', '1'); //緩存時間 define('TPL_CACHEING', 'true'); //是否緩存 define('TPL_LEFT_DELIMITER', '{'); //左邊界符 define('TPL_RIGHT_DELIMITER', '}'); //右邊界符 define('ROOT_PATH', dirname(__FILE__)); //網站所在根目錄 define('URL_PATH', dirname($_SERVER[PHP_SELF])); //網站URL位址路徑 define('DB_PATH', ROOT_PATH.'/include/db'); //PEAR::DB目錄 define('TPL_PATH', ROOT_PATH.'/include/smarty'); //Smarty目錄
$arr_filtrate = array("'", '"', "/"); function var_filtrate($var) { global $arr_filtrate; foreach ($arr_filtrate as $value) { if (eregi($var, $value)) { return true; } return false; } }
if (phpversion() < '4.1.0') { $get = &$HTTP_GET_VARS; $post = &$HTTP_POST_VARS; } else { $get = &$_GET; $post = &$_POST; } if (count($get)) { foreach ($post as $get_var) { if (var_filtrate($get_var)) { exit('Commit get parameter falsity'); } } } if (count($post)) { foreach ($post as $post_var) { if (var_filtrate($post_var)) { exit('Commit post parameter falsity'); } } } 其實,以上過濾的方法也不是最好的,建議參考我的另兩篇防注入文章擷取更好的方法,連結參考附錄。
if (!isset($get[msg])) { exit('Not commit parameter'); } echo "Error Message: ". $get[msg]; echo "<p><a href='javascript:history.back()'>傳回上一頁</a>";
就是一些錯誤處理的作用,一般出的GET方式傳遞過來的消息都是經過urlencode()過的字元。
require_once(dirname(__FILE__).'config.inc.php'); require_once(ROOT_PATH.'security.inc.php'); require_once(DB_PATH.'DB.php'); require_once(TPL_PATH.'Smarty.class.php');
$db = DB::connect("DB_TYPE://[email protected]_PASS:DB_HOST/DB_NAME", DB_OPT); if (DB::isErro($db)) { return $dg->getMessage(); } $tpl = &new Smarty(); $tpl->templates_dir = TPL_TEMPLATE_DIR; $tpl->compile_dir = TPL_COMPILE_DIR; $tpl->cache_dir = TPL_CACHE_DIR; $tpl->configs = TPL_CONFIGS_DIR; $tpl->lifetime = TPL_LIFTTIME; $tpl->caching = TPL_CACHEING; $tpl->left_delimiter = TPL_LEFT_DELIMITER; $tpl->right_delimiter = TPL_RIGHT_DELIMITER;
基本檔案描述完畢。代碼寫了不少,隻是為了更好的了解這個模式。
四、架構實際開發
說明:
我們以下項目代碼都是以cms資料庫中topic表做例子,代碼隻是為了示範架構結構,沒有對代碼進行測試,不保證能夠正常運作。 topic的表結構:
CREATE TABLE `topic` ( `id` int(11) NOT NULL auto_increment, `title` varchar(255) NOT NULL default '', `addtime` int(11) default NULL, `author` varchar(50) default NULL, `type` int(11) default NULL, `option` int(11) default NULL, PRIMARY KEY (`id`), KEY `id` (`id`) );
(一)Data層:資料采集層
Data層主要就是針對資料庫的所有操作都封裝起來,然後通過接口的形式提供給Php層進行調用,同時在Data層裡也封裝了一些原始的資料庫操作 (類似于Java中的DAO)。一般Data層都是類的形式,儲存在我們上面的 /class目錄下,一般的準則是一個類檔案操作一個資料表,就是說不管具體的業務邏輯如何,所有的資料表操作都是封裝在一個類檔案裡的。比如說我們有一 個資料表叫做topic,那麼我們對應操作的類檔案就是:topic.class.php。其實這裡是可以做擴充的,比如說,我們的項目非常龐大,有很多 内容,比如包括有CMS、Blog、BBS等等,那麼我們就必須給每一個欄目配置設定一個資料庫,那麼針對目前操作資料庫的話,就使用類中封裝的連結方法進行 連結,就可以抛棄我們上面init.inc.php中初始化的操作,而操作在類裡面進行的連結。 假設我們目前操作的欄目是CMS系統,資料庫名叫做cms,那麼我們下面構造一個操作cms資料庫裡面的topic表的類來。
class cmsTopic { var cmsDBName; //資料庫名 var cTableName; //目前操作的表名 var cDsn; //資料連結源 var cDebug; //是否打開調試,1為是,0為否 var cDbPointer //連結資源 var cfetchMode //擷取資料庫資料的方式 var cEncode //資料庫中資料儲存的編碼格式,預設是UTF-8
function cmsTopic() { //配置資訊從config.inc.php中設定 $this->cfetchMode = DB_FETCHMODE_DEFAULT; $this->cTableName = "topic"; $this->cDsn = "mysql://". DB_USER.":". DB_PASS."@". DB_HOST."/". DB_NAME; $this->cEncode = "utf8"; }
function connectDatabase() { if (!is_object($cDbPointer)) { $this->cDbPointer = DB::connect($this->cDsn); if ($this->cEncode=="utf8") { $this->cDbPointer->query("set names 'utf8'"); } $this->cDbPointer->setFetchMode($this->cfetchMode); if (DB::isError($this->cDbPointer)) { return false; } return $this->cDbPointer; } }
function closeDatabase() { if (is_object($this->cDbPointer)) { $this->cDbPointer->disconnect(); } }
function insert($arr) { if(!is_array($arr) || count($arr) == 0){ return false; } if("" == $this->cTableName) return false; $db = $this->connectDatabase(); $res = $db->autoExecute($this->cTableName,$arr,DB_AUTOQUERY_INSERT); if(DB::isError($res)){ return $res; }else{ $insertId = ($db->getOne("select LAST_INSERT_ID();")); if($insertId>0) { return $insertId; } else { return true; } } } function update($id,$arr) { if("" != $id && !(is_array($arr))){ return false; }
$db = $this->connectDatabase(); $res = $db->autoExecute($this->cTableName,$arr,DB_AUTOQUERY_UPDATE,"id = '$id'");
if(DB::isError($res)){ return false; }else{ return true; } }
function delete($id) { $db = $this->connectDatabase(); $res = $db->query("DELETE FROM ".$this->cTableName." WHERE id = '$id'"); if(DB::isError($res)){ return false; }else{ return true; } } }
上面的代碼一個很基本的針對一個表操作的類雛形已經描述出來了,包括連接配接資料庫,基本的資料庫原始操作都有了。你肯定會問,為什麼沒有把 select的操作封裝進去?主要是因為select是SQL裡最複雜的操作,不可能寫那麼通用的一個方法去操作它,是以好不如不寫,自由發揮。
那麼我們需要加上一些基本的功能呢?比如讀取内容、新增加一篇文章等操作,那麼我們還必須在類裡面添加一些方法,比如我們增加提取一篇文章内容、提取指定時間的文章、提取指定類别的文章、統計目前所有文章的總數等操作。
class cmsTopic { // ...上面已經描述的方法省略
function getTopicContentById($id, $cols="*") { $db = $this->connectDatabase($this->cDsn); $sql = "SELECT $cols FROM ". $this->cTableName ." WHERE id = '$id'"; $result = $db->getAll($sql); if (DB::isError($result)) { return $result->getMessage(); } else { $db->disconnect(); return $result; } }
function getTopicBySpecifyTime($startTime=0, $endTime=0, $cols="*") { $db = $this->connectDatabase($this->cDsn); $start = ($startTime == 0) ? "" : "WHERE addtime > $startTime"; $end = ($endTime == 0) ? "" : "AND addtime < $startTime"; $sql = "SELECT $cols FROM ". $this->cTableName ." ".$start ." ".$end; $result = $db->getAll($sql); if (DB::isError($result)) { return $result->getMessage(); } else { $db->disconnect(); return $result; } }
function getTopicByType($type, $cols="*") { $db = $this->connectDatabase($this->cDsn); $sql = "SELECT $cols FROM ". $this->cTableName ." WHERE type = '$type'"; $result = $db->getAll($sql); if (DB::isError($result)) { return $result->getMessage(); } else { $db->disconnect(); return $result; } } function getTopicSum($type="") { $db = $this->connectDatabase($this->cDsn); $typeStr = ($type == "") ? "" : " WHERE type = '$type'"; $sql = "SELECT count(id) FROM ". $this->cTableName ." ".$typeStr; $result = $db->getOne($sql); if (DB::isError($result)) { return $result->getMessage(); } else { $db->disconnect(); return $result; } } }
上面我們構造了一些資料提取類,這應該就是我們Data層的核心了。寫方法的時候要盡量考慮到擴充性,比如對列的提取,比如一個方法适合多種情況, 比如排序等等,考慮的越多,以後維護起來就比較容易,當然,我推薦的方法是一個方法盡量就做一件事情,如果一個函數要做多個事情,那麼就寫成多個函數,這 樣便于代碼重用和維護性,我個人認為一個方法最用不要超過100行。
如果函數中有涉及到資料庫的操作,一定記得結尾的時候把資料關閉掉,不然很容易把伺服器資源占用光。當然,你也可以在PHP層去關閉連接配接。比如,你 需要很多次調用同一個方法,那麼這個方法如果反複的連接配接資料庫又關閉資料庫,也很浪費資源,而且速度慢,這個時候就可以把關閉資料庫的操作在Php層進行 關閉,你可以先構造好一個方法來進行,比如我們上面的 closeDatabase() 方法。
(二)Php層:資料調用層
PHP層主要就是把從Data層收集的資料再這一層進行調用。因為我們基本的原則就是把所有跟資料庫的操作都封裝在Data層裡,在其他層都不涉及 到任何的直接對資料庫的操作,這樣能夠進行良好的封裝,這樣有點類似于 JSP和Javabean,Javabean的類負責和資料庫互動,JSP負責調用Javabean來輸出資料。我們這裡的PHP層就相當于JSP層,前 面的Data層就相當于Javabean層,這樣玻璃他們之間的耦合度,能夠友善程式日後的維護。
我們這裡的PHP層主要就是複雜從資料庫種提取資料,完成一些簡單的邏輯,然後把資料輸出到Template(模闆層)。現在我們利用示例代碼來看看PHP層是如何調用Data層的資料的。
require_once("init.inc.php"); require_once("class/cms_topic.class.php");
//執行個體化Data層對象 $topic = new cmsTopic(); //擷取文章類型變量 $topicType = intval(get("type")); //從Data層中把資料提取過來 $topicList = getTopicByType($topicType);
//給模闆變量指派後輸出頁 $tpl->assign("topic", $topicList); $tpl->assign("topic.html");
代碼是不是很簡單?就是把資料擷取過來,然後解析到模闆層中去處理,是以這樣如果以後出現問題改起來比較容易,比如是資料擷取的問題,那麼直接改上面的類檔案就行,如果是模闆顯示的問題,那麼直接修改模闆層中的對應的模闆頁就可以,非常便于維護。
(三)Template層:模闆層
這個模闆層就是我們常說的網頁了,不過這裡就是包含了一些Smarty的模闆變量和HTML混和,模闆頁處理的時候就對頁面中的模闆變量進行替換,最後我們看到的結果就是模闆頁和PHP層中的程式輸出混和的結果。
一般模闆頁設計的時候,最好遵循Web标準,就是說盡量在頁面中不使用表格等html标簽來控制頁面,而是使用div層來存放資料,使用css樣式 表來控制頁面布局,這樣對包括JavaScript腳本的編寫,以後頁面的改版等等非常有好處。而且如果要還模闆也很簡單,隻需要把css檔案替換就可以 達到效果。當然,如果對web标準不了解,那麼建議去閱讀一下《網站重構》這本書。
我們下面就簡單的描述一下Template(模闆層)的代碼是如何的。
{* 加載頭部檔案 *} {include file="header.html"}
{* 模闆主體 *} <div> {* 左邊導覽列 *} </div>
<div> <h3>文章清單</h3> {section name=topicList loop=$topic} 标題:<a href="" target="_blank" rel="external nofollow" >{$topic[topicList].title|escape:"html"|truncate:30:"...":true}</a> | 時間:{$topic[topicList].addtime|date_format:"%Y年%m月%d日"} | 作者:{$topic[topicList].author|escape:"html"}<br /> {sectionelse} 暫時沒有任何文章 {/section} </div>
{* 加載底部檔案 *} {include file="foot.html"}
模闆頁中大緻可能有一些JavaScript程式,或者有樣式檔案,一般使用樣式檔案來控制頁面的布局和顯示效果。我們這裡沒有詳細的描述,在實際項目中可以由網頁制作人員去負責。
五、使用DPT模式的項目規劃
一般在所有的軟體項目或者是網站項目中,要保證一個項目能夠順利完整的完成,那麼便需要技術主管或者架構師良好的設計和管理。一般所有項目中人是最 難控制的因素,你可以把項目指定的非常完善,架構可以選擇的非常合理,但是你不能控制人的因素,不能保證項目的中的某個成員可能在任何時候離開項目。當在 PHP項目中,如果一個項目角色忽然的離去,可能導緻項目要停頓,要重新找人來接替,影響了項目的進度,那麼如何有效的控制和解決這些問題。
在一個使用DPT設計模式的項目中,項目中個個角色分别有網頁設計師、網頁制作人員、用戶端腳本JavaScript程式員、伺服器端PHP程式 員。他們的分工都是什麼呢?網頁設計師負責設計網頁的界面,生成效果圖,然後由網頁制作人員去做成網頁,當然,如果是遵循Web标準的項目的話,那麼網頁 制作人員主要的任務就是負責頁面布局樣式的編寫。用戶端程式員主要是負責用戶端腳本的編寫,比如針對頁面中需要使用的JavaScript進行編寫, PHP程式員主要是負責我們上面Data & PHP & Template 三層的代碼編寫,當然,如果項目足夠龐大,完全可以拆分出來,有PHP程式員負責Data層,有PHP程式員負責Php和Template層,分工清晰, Php層程式員隻是需要調用Data層程式員已經寫好的類庫進行調用,不用關心類是如何實作的。
這樣一個項目架構下來,可以按照任務需要來安排某個子產品的人的數量,最大限度的把項目規劃好。當然,項目中一些必要的因素是要考慮的,比如,如何讓 網頁制作人員、用戶端腳本程式員和PHP程式員良好的合作,那麼就是分離他們的責任,比如,模闆頁必須由PHP程式員來編寫,然後送出給 JavaScript程式員制作用戶端腳本,最後再由網頁制作人員通過CSS來控制布局,那麼Php程式員在模闆頁中就必須使用div等标簽來定義一個塊 的資料,如果任何一塊出了問題,那麼對應找相應責任人,就能夠很好的避免彼此推卸責任,或者權責不分的情況,這樣有利于管理,也有利于每個開發成員之間的 良好合作。
為了防止項目失控,或者不會因為項目成員的離開而影響項目的進度和管理,必須有相應的方法和規則。我們主要針對PHP程式員來進行描述,部分方法同樣适用于網頁制作人員和用戶端腳本程式員。
(1)編碼規範
項目開發中為了便于維護和以後其他人接手代碼,必須統一編碼規範,包括對目錄、檔案名、類、函數、變量、注釋等等都必須遵循标準,而且為了代碼的維 護,必須要求PHP程式員編寫注釋。目前基本遵循的是Fredrik Kristiansen寫的《PHP 編碼規範》,或者是PEAR中代碼的規範。 如果代碼為了做成接口,或者需要做成參考的文檔友善以後維護代碼,使用phpDoc等工具,那麼為了能夠使用PEAR包中的phpDoc能夠正常識别,是以一般建議遵循PEAR包的規範,主要是DPT模式中Data層中類的的編碼必須規範。
PEAR中pear.php中基類的部分代碼:
class PEAR { // {{{ properties
var $_debug = false; //其他屬性省略... // }}}
// {{{ constructor
function PEAR($error_class = null) { //方法内部代碼省略... }
// }}} }
對于上面内部标簽的解釋請參考文章結尾連結中的《php Documentor 1.2.2 使用說明規範》
(2)單元測試
單元測試是對程式進行的第一步測試,并且是程式員自己做的測試,沒有誰比自己更了解自己寫的代碼,是以單元測試對保證程式的穩定性尤為重要。是以一 般項目中,特别是寫類庫的程式員,一定要對自己寫的代碼進行單元測試,隻要保證資料能夠穩定的擷取,才能保證Php層的程式員能夠正确的擷取資料。針對 PHP的單元測試工具目前有SimpleTest和PEAR包中的phpUnit/phpUnit2,phpUnit2是針對PHP 5的測試工具,這個可以按照項目的實際情況來決定使用什麼測試工具。SimpleTest和phpUnit的使用參考文章請參考文章最後的連結。
(3)程式安全
在今天這個黑客橫行,SQL Injection泛濫的時候,為了保證程式能夠正确完整的運作,那麼就必須要求程式員對自己的程式進行安全的編碼。
主要是兩方面,一方面是伺服器端的安全,主要就是要過濾使用者送出的變量,記得網絡上流行的一句話:“不要相信使用者送出的任何資料”,因為你不能保證 這個使用者是惡意的還是善意,是普通使用者還是黑客,是以不管是誰,一定要過濾掉它的變量,不過是通過GET方式還是POST方式過來的資料,統統過濾。主要 是過濾一些特殊自如,比如/"'等等,保證伺服器和資料的安全。
另一方面就是針對用戶端的威脅,比如送出一些惡意的JavaScript代碼,當普通使用者檢視該頁的時候,那麼将給對方帶來影響或者傷害,那麼會極 大的影響網站或者軟體的聲譽,這是要避免的。同樣的,這個也是要良好的過濾變量,如果無法在使用者送出資料的時候過濾,那麼就在顯示輸出的時候進行過濾,主 要是過濾一些HTML标簽和JavaScript代碼,保證客戶浏覽的安全。
關于一些基本的安全問題可以參考文章後面的連結。
(4)代碼同步(版本管理)
但一個項目是多人開發的時候,那麼代碼同步或者說是版本管理就尤為重要,因為為了保證每個開發成員的代碼都是最新的,最穩定的,那麼必須要使用 CVS等版本管理工具,當然,你想使用VSS也無可厚非。一般建議是專門使用一台伺服器來供所有開發人員開發,不建議把代碼儲存在開發人員的本地上,因為 大部分人都是使用Windows等作業系統,如果發生病毒感染、裝置損壞或者其他因素,那麼代碼将壯烈犧牲,這樣造成又要重複開發的浪費,如果把代碼直接 存放在伺服器上,一般伺服器安裝都是Unix/Linux系統,能夠基本保證代碼的安全和完整。
CVS的安裝使用文章可以參考文章最後的連結。
(5)多子產品通信
如果項目足夠龐大,門戶級的應用,那麼肯定涉及到多欄目/子產品之間的通信
>>>>>> 附錄:(文章相應連結)<<<<<<
PEAR http://pear.php.net Smarty http://smarty.php.net phpUnit http://phpunit.sourceforge.net SimpleTest http://simpletest.sourceforge.net | http://www.lastcraft.com/simple_test.php phpDocumentor http://www.phpdoc.org
《PEAR:建立中間的資料庫應用層》 http://www-128.ibm.com/developerworks/cn/linux/sdk/php/pear4/index.html
《模闆引擎SMARTY》 http://www-128.ibm.com/developerworks/cn/linux/l-smart/index.html
《MVC模式、類封裝還是黑客代碼》 http://www-128.ibm.com/developerworks/cn/linux/sdk/php/php_design/index.html
《PHP中的代碼安全和SQL Injection防範》 http://blog.csdn.net/heiyeshuwu/archive/2005/06/14/394225.aspx
《在PHP中使用SimpleTest進行單元測試》 http://www.hsboy.com/blog/archives/90-PHPOESimpleTesta.html
《用phpUnit幫你調試php程式》 http://www.ccw.com.cn/htm/app/aprog/01_4_13_4.asp
《php Documentor 1.2.2 使用說明規範》 http://www.phpx.com/happy/top98827.php
《PEAR:使用PHPDoc輕松建立你的PEAR文檔》 http://www-128.ibm.com/developerworks/cn/linux/sdk/php/pear3/index.html
《PHP 編碼規範》 http://www.phpe.net/html/php_coding_standard_cn.html
《CVS使用手冊》 http://www.chedong.com/tech/cvs_card.html