張志瑜
PHP是時(shí)下流行的動(dòng)態(tài)網(wǎng)頁開發(fā)語言之一,受到包括FACEBOOK等行業(yè)巨頭在內(nèi)的眾多企業(yè)青睞,其實(shí)際應(yīng)用非常之廣。掌握基本的PHP語言有利于計(jì)算機(jī)專業(yè)學(xué)生日后從事網(wǎng)站維護(hù)管理的需要。然而在實(shí)際中,工業(yè)生產(chǎn)的潮流已經(jīng)不甘于使用半成品的CMS(內(nèi)容管理系統(tǒng))或者WordPress,而是使用更具個(gè)性化的PHP框架來進(jìn)行快速開發(fā),掌握簡單的幾個(gè)函數(shù)已經(jīng)難以處理日益豐富的網(wǎng)站開發(fā)維護(hù)需要。業(yè)界的發(fā)展潮流傾向代碼和界面分離的做法使得不僅PHP程序員,甚至連前端的頁面美工也需要對(duì)PHP有較為深刻的認(rèn)識(shí)。
主流的PHP框架多數(shù)采用MVC框架,MVC框架把網(wǎng)頁請(qǐng)求分為Controller(控制),Model(模型)和View(視圖)三部分,通過減低內(nèi)容模塊之間的耦合度,從而簡化開發(fā)流程和提高代碼復(fù)用。用PHP語言來實(shí)現(xiàn)MVC框架可以看作是繼PHP語言入門的后續(xù)課程和進(jìn)階,通過實(shí)現(xiàn)MVC框架來使得學(xué)生對(duì)PHP和MySQL數(shù)據(jù)庫有更加具體和深刻的認(rèn)識(shí),從建立項(xiàng)目,解決問題中更感性地掌握PHP的用途,比面向過程式的開發(fā)更加有趣。雖然現(xiàn)成的MVC框架(Yii,CodeIgniter,CakePHP)為數(shù)不少,但是掌握獨(dú)自開發(fā)一個(gè)完整的MVC框架能夠使得開發(fā)者更加了解整套框架的運(yùn)作,也能夠使得開發(fā)者更快地掌握運(yùn)用其他框架開發(fā)。
對(duì)于使用PHP語言來實(shí)現(xiàn)完整的MVC網(wǎng)站框架,需要開發(fā)者使用現(xiàn)有的工具解決好不同的類之間的控制和處理。我們大致可以根據(jù)右圖提供的思路來建立MVC模型的雛形。雖然沒有專業(yè)框架擁有很多處理類(緩存類,安全類,輸入輸出處理類),但是最基本的MVC框架然仍不能想象得過于簡單,很多問題需要我們不斷地思考和解決。
1 獲取用戶請(qǐng)求
用戶發(fā)出的網(wǎng)頁請(qǐng)求通過URL來傳遞,傳遞的思路就是把需要訪問的頁面分解成多個(gè)$_GET參數(shù)(如:http://localhost/account/list,控制類是account,輸出list頁面)。實(shí)際上網(wǎng)頁服務(wù)器會(huì)解析成網(wǎng)站目錄下account目錄下的list頁面。在實(shí)際和預(yù)設(shè)兩者的歧義中,我們考慮使用.htaccess來實(shí)現(xiàn)請(qǐng)求的轉(zhuǎn)化。.htaccess文件是Apache服務(wù)器中的一個(gè)配置文件,它負(fù)責(zé)相關(guān)目錄下的網(wǎng)絡(luò)配置,通過.htaccess文件,可以實(shí)現(xiàn)網(wǎng)頁重定向,自定義錯(cuò)誤頁面,改進(jìn)文件擴(kuò)展名,特定用戶訪問權(quán)限設(shè)置,配置默認(rèn)文檔等功能。.htaccess建立在網(wǎng)站站點(diǎn)的目錄里而不是在Apache安裝目錄下,它具有分布式配置的方式,在目錄中可以放置一個(gè)包含一個(gè)或多個(gè)指令的文件方式,以及作用于此目錄及其所有子目錄。最普遍的例子是使用一下語句來實(shí)現(xiàn)用戶請(qǐng)求的轉(zhuǎn)義:
REWRITERULE
^(.*)$ INDEX.PHP?URL=$1
經(jīng)過.htaccess處理之后,PHP會(huì)把用戶的請(qǐng)求轉(zhuǎn)化成$_GET[‘url]變量。最普遍的做法卻存在一個(gè)小問題,用戶輸入包含后綴名的完整URL的時(shí)候(如http://localhost/account/list.php),PHP則會(huì)把list.php作為整體而不把.php作為后綴名看待。后續(xù)操作會(huì)因?yàn)檎也坏絣ist.php(只有l(wèi)ist方法)而報(bào)錯(cuò)。所以我們可以通過小小的修改來對(duì)請(qǐng)求進(jìn)行細(xì)化:
REWRITERULE
^([A-ZA-Z0-9\/\-_]+)\.?([A-ZA-Z]+)?$ INDEX.PHP?URL = $1&EXTENSION = $2
經(jīng)過修改后,PHP會(huì)把.php作為$_GET[‘extention]參數(shù),則更有利于后續(xù)操作針對(duì)不同的后綴名來進(jìn)行不同的處理。
2 對(duì)用戶請(qǐng)求進(jìn)行分析
無論是使用單個(gè)文件或者獨(dú)立出一個(gè)路由的類,思路都是對(duì)$_GET[‘url]進(jìn)行分析分拆,使用explode_array函數(shù)提取不同的部分。再以call_user_func_array()來進(jìn)行控制類和方法類的調(diào)用。除了控制類參數(shù)部分和方法類參數(shù)部分,其余部分可以數(shù)組的方式調(diào)用模型類。
$RT = new Router($request);
Session::init();
$controller = $RT->getController();
$controller = new $controller;
$method = $RT->getMethod();
$params = $RT->getParams();
if (empty($params)) {
call_user_func(array($controller, $method));
} else {
call_user_func_array(array($controller, $method), $params);
}
3 控制類的設(shè)計(jì)
控制類(Controller)是MVC處理模式的主要部分,常用思路是把單一類別的網(wǎng)頁作為類名(例如與用戶有關(guān)的可以定義為account類),具體的某個(gè)頁面就是類中的方法(例如查看單個(gè)用戶的信息,如account類中的profile函數(shù))。我們一般先建立一個(gè)名為Controller的抽象類,統(tǒng)一定義初始化函數(shù)(__construct())和主函數(shù)(index(),某個(gè)類的默認(rèn)頁面),具體的類則通通繼承這個(gè)抽象類。
控制類不能簡單的包含模型類(model)和視圖類(view)兩個(gè)元素,因?yàn)樵诔S玫木W(wǎng)站開發(fā)中往往需要Session,網(wǎng)頁分頁等支持。所以我們采用流行框架中用到load類的方法。值得一提的是,CodeIgniter不是使用Loader類而是使用指針函數(shù)來加載其他類,采用這種方法的話需要額外建立一個(gè)全局變量來存儲(chǔ)加載了的類的指針數(shù)組。另外還可以額外加載registry類來存儲(chǔ)加載函數(shù),registry類作為控制類的一個(gè)元素。解決了存儲(chǔ)加載類,我們則用家里L(fēng)oader類來加載不同模塊(Session,Input,Output)等。
模型類(Model類)與視圖類(View類)不同,正如上圖所示,模型類(Model類)并不是每個(gè)控制類都需要調(diào)用的(如只顯示靜態(tài)頁面,或者調(diào)用緩存頁面),所以通常的做法是把模型類也歸納在Loader類的調(diào)用范疇。
視圖類(View類)則是必須包含的元素,因?yàn)槊總€(gè)控制類最終目錄都是要通過調(diào)用頁面(視圖類,PHP頁面文件)來顯示。View類既可以也歸納為Loader類調(diào)用,也可以獨(dú)立在Loader類之外。需要開發(fā)者考慮調(diào)用的時(shí)候考慮調(diào)用單個(gè)PHP頁面還是包含額外的PHP頁面模塊。開發(fā)者可以根據(jù)設(shè)計(jì)思路來做出不同的選擇。
abstract class Controller
{
protected $_registry;
protected $load;
public function __construct()
{
$this->_registry = Registry::getInstance();
$this->load = new Load;
}
abstract public function index();
final public function __get($key)
{
if ($return = $this->_registry->$key) {
return $return;
}
return false;
}
}
4 模型類的設(shè)計(jì)
模型類(Model類)的作用在與對(duì)數(shù)據(jù)進(jìn)行處理,把數(shù)據(jù)處理的結(jié)果和分析數(shù)據(jù)返回給視圖類(View類)進(jìn)行顯示。與控制類(Controller類)相同,我們需要先建立模型的抽象類。主要是對(duì)數(shù)據(jù)庫的加載,眾所周知數(shù)據(jù)庫對(duì)一個(gè)動(dòng)態(tài)網(wǎng)頁是多么的重要,所以數(shù)據(jù)庫的處理我們也必須使用獨(dú)立的數(shù)據(jù)庫類(Database類)。Database類可以是對(duì)PDO類的繼承,這樣方便我們快速調(diào)整不同的數(shù)據(jù)庫(MySQL,SQLite等),對(duì)于只專注于某種數(shù)據(jù)庫的應(yīng)用,我們可以使用其特定的類(如MySQLi類)。
在Database類的方法實(shí)現(xiàn)中,我們建議繁瑣的多次SQL操作描述成較為容易理解的操作集合函數(shù)。此外我們也可以直接使用現(xiàn)有的ORM庫(如Redbean)來代替Database類,把ORM作為模型類(Model類)的元素。
對(duì)象關(guān)系映射(Object Relational Mapping,簡稱ORM)是一種為了解決面向?qū)ο笈c關(guān)系數(shù)據(jù)庫存在的互不匹配的現(xiàn)象的技術(shù)。 簡單的說,ORM是通過使用描述對(duì)象和數(shù)據(jù)庫之間映射的元數(shù)據(jù),將程序中的對(duì)象自動(dòng)持久化到關(guān)系數(shù)據(jù)庫中。本質(zhì)上就是將數(shù)據(jù)從一種形式轉(zhuǎn)換到另外一種形式。ORM提供了所有SQL語句的生成,代碼人員遠(yuǎn)離了數(shù)據(jù)庫概念。從一個(gè)概念需求(例如一個(gè)HQL)映射為一個(gè)SQL語句,并不需要什么代價(jià),連1%的性能損失都沒有。真正的性能損失在映射過程中,更具體地講,是在對(duì)象實(shí)例化的過程中。
abstract class Model
{
protected $_registry;
protected $load;
public function __construct()
{
require_once CONFIG_PATH.'db.php';
$this->db = new Database($CFG['db']);
}
}
5 視圖類的設(shè)計(jì)
與其說是視圖類(View類),不如說是PHP頁面就更為準(zhǔn)確,視圖類(View類)可以看做基本的PHP頁面,控制類(Controller類)調(diào)用(require(),require_once())這些頁面進(jìn)行顯示出最后效果。關(guān)鍵是需要對(duì)模型類(Model類)產(chǎn)生的數(shù)據(jù)進(jìn)行處理在傳遞給View。
在MVC框架的過程中,我們可以使用Output類來輔助視圖語句的輸出,例如把HTML的form代碼拆分成幾個(gè)echo()函數(shù)。
6 使用現(xiàn)有的PHP庫
我們已經(jīng)建立一個(gè)簡單的MVC框架雛形,而且可以在這個(gè)雛形之上不斷的改進(jìn)和賦予更加高級(jí)的特性和框架功能(Cookies,Security等)。我們發(fā)現(xiàn)原來很多功能,函數(shù)都可以手工去一一打造,然而在快速開發(fā)或團(tuán)隊(duì)開發(fā)的時(shí)候,重復(fù)的快發(fā)基本的功能模塊,除了加深開發(fā)者的基本功之外,對(duì)開發(fā)幫助不大。我們的框架可以和容易的去調(diào)用現(xiàn)有得第三方的PHP庫(如PEAR)。但是我們也需要通過類似于spl_autoload_register()去改進(jìn)PHP的自動(dòng)加載函數(shù)。
通過對(duì)MVC框架的實(shí)現(xiàn),開發(fā)者可以加深對(duì)PHP語言認(rèn)識(shí)和開拓網(wǎng)站開發(fā)的思路。針對(duì)于PHP初學(xué)者來說,本案例就是一門PHP動(dòng)態(tài)網(wǎng)站開發(fā)的進(jìn)階課程。從中可以學(xué)習(xí)到PHP語言課學(xué)習(xí)中很少用到的方法:全局函數(shù),指針函數(shù),類,繼承,抽象類等。也為日后的CodeIgniter等框架的學(xué)習(xí)打下基礎(chǔ)。
[責(zé)任編輯:湯靜]