ThinkPHP的控制器層由核心控制器和業(yè)務(wù)控制器組成,核心控制器由系統(tǒng)內(nèi)部的App類完成,負(fù)責(zé)應(yīng)用(包括模塊和操作)的調(diào)度控制,包括HTTP請(qǐng)求攔截和轉(zhuǎn)發(fā)、加載配置等,業(yè)務(wù)控制器則由用戶定義的Action類或者其他控制器類完成。
我們通過(guò)前面的學(xué)習(xí),已經(jīng)了解了基本的控制器用法,這一篇我們來(lái)講述下控制器的一些特性和高級(jí)用法,來(lái)探索ThinkPHP控制器的神秘外衣。[-more-]
Action參數(shù)綁定
在前面的內(nèi)容中,我們涉及的所有操作方法都是沒(méi)有任何參數(shù)的,其實(shí)從3.1版本開(kāi)始,可以支持參數(shù)綁定功能。Action參數(shù)綁定的原理是把URL中的參數(shù)(不包括分組、模塊和操作名)和控制器的操作方法中的參數(shù)(按變量名)進(jìn)行綁定。
例如,我們給Blog模塊定義了兩個(gè)操作方法read和archive方法,并且給read操作需要指定一個(gè)id參數(shù),archive方法指定年份(year)和月份(month)兩個(gè)參數(shù)。為了演示方便,我們省去了具體操作方法的業(yè)務(wù)代碼,僅僅用echo 輸出當(dāng)前的參數(shù)。
class BlogAction extends Action{
public function read($id){
echo 'id='.$id;
}
public function archive($year='2012',$month='01'){
echo 'year='.$year.'&month='.$month;
}
}
URL的訪問(wèn)地址分別是:
http://serverName/index.php/Blog/read/id/5
http://serverName/index.php/Blog/archive/year/2012/month/03
兩個(gè)URL地址中的id參數(shù)和year和month參數(shù)會(huì)自動(dòng)和read操作方法以及archive操作方法的同名參數(shù)綁定。
輸出的結(jié)果依次是:
id=5
year=2012&month=03
Action參數(shù)綁定的參數(shù)必須和URL中傳入的參數(shù)名稱一致,但是參數(shù)順序不需要一致。也就是說(shuō)
http://serverName/index.php/Blog/archive/month/03/year/2012
和上面的訪問(wèn)結(jié)果是一致的,URL中的參數(shù)順序和操作方法中的參數(shù)順序都可以隨意調(diào)整,關(guān)鍵是確保參數(shù)名稱一致即可。
如果用戶訪問(wèn)的URL地址是(至于為什么會(huì)這么訪問(wèn)暫且不提):
http://serverName/index.php/Blog/read/
那么會(huì)拋出下面的異常提示:
參數(shù)錯(cuò)誤:id
報(bào)錯(cuò)的原因很簡(jiǎn)單,因?yàn)樵趫?zhí)行read操作方法的時(shí)候,id參數(shù)是必須傳入?yún)?shù)的,但是方法無(wú)法從URL地址中獲取正確的id參數(shù)信息。由于我們不能相信用戶的任何輸入,因此建議你給read方法的id參數(shù)添加默認(rèn)值,例如:
public function read($id=0){
echo 'id='.$id;
}
這樣,當(dāng)我們?cè)L問(wèn)
http://serverName/index.php/Blog/read/
的時(shí)候 就會(huì)輸出
id=0
當(dāng)我們?cè)L問(wèn)
http://serverName/index.php/Blog/archive/
的時(shí)候,輸出:
year=2012&month=01
參數(shù)綁定功能不受路由影響,從路由中匹配和URL傳入的參數(shù)一樣有效,并且綁定的參數(shù)如果需要特殊處理和過(guò)濾的話,需要另行處理。
空模塊和空操作
空操作是指系統(tǒng)在找不到指定的操作方法的時(shí)候,會(huì)定位到空操作(_empty)方法來(lái)執(zhí)行,利用這個(gè)機(jī)制,我們可以實(shí)現(xiàn)錯(cuò)誤頁(yè)面和一些URL的優(yōu)化。
例如,下面我們用空操作功能來(lái)實(shí)現(xiàn)一個(gè)城市切換的功能。
我們只需要給CityAction類定義一個(gè)_empty (空操作)方法:
<?php
class CityAction extends Action{
public function _empty($name){
//把所有城市的操作解析到city方法
$this->city($name);
}
//注意 city方法 是 protected 方法
protected function city($name){
//和$name這個(gè)城市相關(guān)的處理
echo '當(dāng)前城市' . $name;
}
}
接下來(lái),我們就可以在瀏覽器里面輸入
http://serverName/index.php/City/beijing/
http://serverName/index.php/City/shanghai/
http://serverName/index.php/City/shenzhen/
由于CityAction并沒(méi)有定義beijing、shanghai或者shenzhen操作方法,因此系統(tǒng)會(huì)定位到空操作方法
_empty中去解析,_empty方法的參數(shù)就是當(dāng)前URL里面的操作名,因此會(huì)看到依次輸出的結(jié)果是:
當(dāng)前城市:beijing
當(dāng)前城市:shanghai
當(dāng)前城市:shenzhen
空模塊的概念是指當(dāng)系統(tǒng)找不到指定的模塊名稱的時(shí)候,系統(tǒng)會(huì)嘗試定位空模塊(EmptyAction),利用這個(gè)機(jī)制我們可以用來(lái)定制錯(cuò)誤頁(yè)面和進(jìn)行URL的優(yōu)化。現(xiàn)在我們把前面的需求進(jìn)一步,把URL由原來(lái)的
http://serverName/index.php/City/shanghai/
變成
http://serverName/index.php/shanghai/
這樣更加簡(jiǎn)單的方式,如果按照傳統(tǒng)的模式,我們必須給每個(gè)城市定義一個(gè)Action類,然后在每個(gè)Action類的index方法里面進(jìn)行處理。 可是如果使用空模塊功能,這個(gè)問(wèn)題就可以迎刃而解了。 我們可以給項(xiàng)目定義一個(gè)EmptyAction類
<?php
class EmptyAction extends Action{
public function index(){
//根據(jù)當(dāng)前模塊名來(lái)判斷要執(zhí)行那個(gè)城市的操作
$cityName = MODULE_NAME;
$this->city($cityName);
}
//注意 city方法 本身是 protected 方法
protected function city($name){
//和$name這個(gè)城市相關(guān)的處理
echo '當(dāng)前城市' . $name;
}
}
接下來(lái),我們就可以在瀏覽器里面輸入
http://serverName/index.php/beijing/
http://serverName/index.php/shanghai/
http://serverName/index.php/shenzhen/
由于系統(tǒng)并不存在beijing、shanghai或者shenzhen模塊,因此會(huì)定位到空模塊(EmptyAction)的默認(rèn)操作(index)去執(zhí)行,會(huì)看到依次輸出的結(jié)果是:
當(dāng)前城市:beijing
當(dāng)前城市:shanghai
當(dāng)前城市:shenzhen
空模塊和空操作還可以同時(shí)使用,用以完成更加復(fù)雜的操作。
前置和后置操作
如果當(dāng)前訪問(wèn)的操作是存在的,系統(tǒng)會(huì)檢測(cè)當(dāng)前操作是否具有前置和后置操作,如果存在就會(huì)按照順序執(zhí)行,前置和后置操作的方法名是在要執(zhí)行的方法前面加 _before_和_after_,例如:
class IndexAction extends Action{
//前置操作方法
public function _before_index(){
echo 'before<br/>';
}
public function index(){
echo 'index<br/>';
}
//后置操作方法
public function _after_index(){
echo 'after<br/>';
}
}
如果我們?cè)L問(wèn)
http://serverName/index.php
結(jié)果會(huì)輸出
before
index
after
對(duì)于任何操作方法我們都可以按照這樣的規(guī)則來(lái)定義前置和后置方法。
需要注意的是,如果在操作方法里面使用了exit或者error方法的話 有可能不會(huì)再執(zhí)行后置方法了。
跳轉(zhuǎn)和重定向
系統(tǒng)的Action類內(nèi)置了兩個(gè)頁(yè)面跳轉(zhuǎn)方法error和success,分別用于錯(cuò)誤(提示)跳轉(zhuǎn)和成功(提示)跳轉(zhuǎn)。兩個(gè)方法都會(huì)輸出一個(gè)提示信息頁(yè)面,然后自動(dòng)跳轉(zhuǎn)到指定的地址。如果當(dāng)前請(qǐng)求是ajax方式的話,則會(huì)自動(dòng)進(jìn)行ajax數(shù)據(jù)返回。下面是一個(gè)簡(jiǎn)單的例子:
$User = M('User'); //實(shí)例化User對(duì)象
$result = $User->add($data);
if($result){
//設(shè)置成功后跳轉(zhuǎn)頁(yè)面的地址,默認(rèn)的返回頁(yè)面是$_SERVER['HTTP_REFERER']
$this->success('新增成功', '/User/list');
} else {
//錯(cuò)誤頁(yè)面的默認(rèn)跳轉(zhuǎn)頁(yè)面是返回前一頁(yè),通常不需要設(shè)置
$this->error('新增失敗');
}
Success和error方法都有對(duì)應(yīng)的模板,并且是可以設(shè)置的,默認(rèn)的設(shè)置是系統(tǒng)模板:
//默認(rèn)錯(cuò)誤跳轉(zhuǎn)對(duì)應(yīng)的模板文件
'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl',
//默認(rèn)成功跳轉(zhuǎn)對(duì)應(yīng)的模板文件
'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.tpl',
我們可以在項(xiàng)目配置文件中修改為使用項(xiàng)目?jī)?nèi)部的模板文件
//默認(rèn)錯(cuò)誤跳轉(zhuǎn)對(duì)應(yīng)的模板文件
'TMPL_ACTION_ERROR' => 'Public:error',
//默認(rèn)成功跳轉(zhuǎn)對(duì)應(yīng)的模板文件
'TMPL_ACTION_SUCCESS' => 'Public:success',
如果你的操作不需要任何提示頁(yè)面,也可以直接使用頁(yè)面重定向功能。
系統(tǒng)提供了redirect方法實(shí)現(xiàn)頁(yè)面的重定向功能。
例如:
//重定向到New模塊的Category操作
$this->redirect('New/category', array('cate_id' => 2), 5, '頁(yè)面跳轉(zhuǎn)中...');
上面的用法是停留5秒后跳轉(zhuǎn)到New模塊的category操作,并且顯示頁(yè)面跳轉(zhuǎn)中字樣,重定向后會(huì)改變當(dāng)前的URL地址。
redirect方法的第一個(gè)參數(shù)和第二個(gè)參數(shù)的配合來(lái)完成實(shí)際的URL地址的組裝,用法和U函數(shù)的用法基本一致。
如果你僅僅是想重定向要一個(gè)指定的URL地址,而不是到某個(gè)模塊的操作方法,可以直接使用redirect函數(shù)重定向,例如:
//重定向到指定的URL地址
redirect('/New/category/cate_id/2', 5, '頁(yè)面跳轉(zhuǎn)中...');
Redirect方法的第一個(gè)參數(shù)是要跳轉(zhuǎn)的實(shí)際URL地址。
AJAX返回
目前的很多WEB應(yīng)用中大量運(yùn)用了ajax操作,系統(tǒng)也提供了一個(gè)用于ajax數(shù)據(jù)返回的方法ajaxReturn方法,用法:
$this->ajaxReturn(返回?cái)?shù)據(jù)[,返回?cái)?shù)據(jù)格式]);
目前已經(jīng)支持的ajax返回?cái)?shù)據(jù)格式包括:XML JSON JSONP EVAL。
下面是一個(gè)簡(jiǎn)單的例子:
$data['status'] = 1;
$data['info'] = 'info';
$data['data'] = $data;
$data['url'] = $url;
$this->ajaxReturn($data);
在客戶端就可以接收傳遞的$data數(shù)據(jù),可以通過(guò)ajaxReturn方法傳遞任意數(shù)據(jù)到客戶端。如果不指定返回格式的話,默認(rèn)為JSON格式返回,也可以指定數(shù)據(jù)格式返回:
$this->ajaxReturn($data,'XML');
頁(yè)面跳轉(zhuǎn)方法success和error如果在ajax請(qǐng)求方式下面會(huì)自動(dòng)調(diào)用ajaxReturn方法,例如:
$this->success('發(fā)布成功',$url);
等效于使用:
$data['info'] = '發(fā)布成功';
$data['url'] = $url;
$data['status'] = 1;
$this->ajaxReturn($data);
在客戶端就可以接收返回的包含info、url和status值的data數(shù)據(jù)。
你無(wú)需擔(dān)心客戶端怎么發(fā)送ajax請(qǐng)求給ThinkPHP,ThinkPHP可以自動(dòng)識(shí)別大部分類庫(kù)的ajax請(qǐng)求,包括JqueryAjax,但某些Flash上傳組件可能無(wú)法準(zhǔn)確識(shí)別,請(qǐng)確保在請(qǐng)求的URL地址中傳入ajax=1參數(shù),這樣就能讓ThinkPHP識(shí)別為Ajax操作。
頁(yè)面請(qǐng)求類型
如果需要根據(jù)當(dāng)前的頁(yè)面請(qǐng)求類型來(lái)做出不同的處理,可以使用系統(tǒng)提供的幾個(gè)常量:
REQUEST_METHOD | 當(dāng)前請(qǐng)求類型 |
IS_GET | 是否GET請(qǐng)求 |
IS_POST | 是否POST請(qǐng)求 |
IS_PUT | 是否PUT請(qǐng)求 |
IS_DELETE | 是否DELETE請(qǐng)求 |
IS_AJAX | 是否AJAX請(qǐng)求 |
舉例如下:
class UserAction extends Action{
public function update(){
if (IS_POST){
$User = M('User');
$User->create();
$User->save();
$this->success('保存完成');
}else{
$this->error('非法請(qǐng)求');
}
}
}
偽靜態(tài)
默認(rèn)情況下,ThinkPHP可以支持所有的靜態(tài)后綴,并且會(huì)記錄當(dāng)前的偽靜態(tài)后綴到常量__EXT__,但不會(huì)影響正常的頁(yè)面訪問(wèn)。
例如:
http://serverName/User/3.html
http://serverName/User/3.shtml
http://serverName/User/3.xml
http://serverName/User/3.pdf
都可以正常訪問(wèn),如果要獲取當(dāng)前訪問(wèn)的偽靜態(tài)后綴,通過(guò)常量__EXT__獲取即可。
如果希望統(tǒng)一偽靜態(tài)后綴,可以設(shè)置:
'URL_HTML_SUFFIX'=>'html'
現(xiàn)在則只能訪問(wèn)
http://serverName/User/3.html
也可以支持允許多個(gè)后綴,例如:
'URL_HTML_SUFFIX'=>'html|shtml|xml' // 多個(gè)用 | 分割
這樣,當(dāng)訪問(wèn)http://serverName/User/3.pdf的時(shí)候會(huì)報(bào)系統(tǒng)錯(cuò)誤。
是實(shí)際應(yīng)用中,我們可以根據(jù)當(dāng)前的URL訪問(wèn)后綴來(lái)做出不同的輸出處理。
多層控制器
3.1版本開(kāi)始增加了多層業(yè)務(wù)控制器的支持,給中大型應(yīng)用提供了方便,例如我們可以分為業(yè)務(wù)控制器和事件控制器:
Action/UserAction //用于用戶的業(yè)務(wù)邏輯控制和調(diào)度
Event/UserEvent //用于用戶的事件響應(yīng)操作
UserAction負(fù)責(zé)外部交互響應(yīng),通過(guò)URL請(qǐng)求響應(yīng),例如 http://serverName/User/index,而UserEvent 負(fù)責(zé)內(nèi)部的事件響應(yīng),并且只能在內(nèi)部調(diào)用
A('User','Event');
所以是和外部隔離的。多層控制器的劃分也不是強(qiáng)制的,可以根據(jù)項(xiàng)目的需要自由分層。控制器分層里面可以根據(jù)需要調(diào)用分層模型,也可以調(diào)用不同的目錄的視圖模板。
總結(jié)
本篇涉及到的ThinkPHP的控制器特性包括空模塊和空操作、前置和后置操作、參數(shù)綁定、偽靜態(tài)、跳轉(zhuǎn)和重定向、ajax返回、請(qǐng)求類型,而新版的多層控制器的特性更是值得回味。