欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

ThinkPHP3.1快速入门(11)控制器高级特性

shiping1 的头像
ThinkPHP的控制器层由核心控制器和业务控制器组成,核心控制器由系统内部的App类完成,负责应用(包括模块和操作)的调度控制,包括HTTP请求拦截和转发、加载配置等,业务控制器则由用户定义的Action类或者其他控制器类完成。
我们通过前面的学习,已经了解了基本的控制器用法,这一篇我们来讲述下控制器的一些特性和高级用法,来探索ThinkPHP控制器的神秘外衣。[-more-]

Action参数绑定

在前面的内容中,我们涉及的所有操作方法都是没有任何参数的,其实从3.1版本开始,可以支持参数绑定功能。Action参数绑定的原理是把URL中的参数(不包括分组、模块和操作名)和控制器的操作方法中的参数(按变量名)进行绑定。
例如,我们给Blog模块定义了两个操作方法read和archive方法,并且给read操作需要指定一个id参数,archive方法指定年份(year)和月份(month)两个参数。为了演示方便,我们省去了具体操作方法的业务代码,仅仅用echo 输出当前的参数。
  1. class BlogAction extends Action{
  2.     public function read($id){
  3.         echo 'id='.$id;
  4.     }
  5.   
  6.     public function archive($year='2012',$month='01'){
  7.         echo 'year='.$year.'&month='.$month;
  8.     }
  9.  }
复制代码
 
URL的访问地址分别是:
  1. http://serverName/index.php/Blog/read/id/5
  2. http://serverName/index.php/Blog/archive/year/2012/month/03
复制代码
 
两个URL地址中的id参数和year和month参数会自动和read操作方法以及archive操作方法的同名参数绑定。
输出的结果依次是:
  1. id=5
  2. year=2012&month=03
复制代码
 
Action参数绑定的参数必须和URL中传入的参数名称一致,但是参数顺序不需要一致。也就是说
  1. http://serverName/index.php/Blog/archive/month/03/year/2012
复制代码
 
和上面的访问结果是一致的,URL中的参数顺序和操作方法中的参数顺序都可以随意调整,关键是确保参数名称一致即可。
如果用户访问的URL地址是(至于为什么会这么访问暂且不提):
  1. http://serverName/index.php/Blog/read/
复制代码
 
那么会抛出下面的异常提示:
  1. 参数错误:id
复制代码
 
报错的原因很简单,因为在执行read操作方法的时候,id参数是必须传入参数的,但是方法无法从URL地址中获取正确的id参数信息。由于我们不能相信用户的任何输入,因此建议你给read方法的id参数添加默认值,例如:
  1. public function read($id=0){
  2.     echo 'id='.$id;
  3.  }
复制代码
 
这样,当我们访问
  1. http://serverName/index.php/Blog/read/
复制代码
 
的时候 就会输出
  1. id=0
复制代码
 
当我们访问
  1. http://serverName/index.php/Blog/archive/
复制代码
 
的时候,输出:
  1.  year=2012&month=01
复制代码
 
参数绑定功能不受路由影响,从路由中匹配和URL传入的参数一样有效,并且绑定的参数如果需要特殊处理和过滤的话,需要另行处理。


空模块和空操作

空操作是指系统在找不到指定的操作方法的时候,会定位到空操作(_empty)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。
例如,下面我们用空操作功能来实现一个城市切换的功能。
我们只需要给CityAction类定义一个_empty (空操作)方法:
  1. <?php
  2.  class CityAction extends Action{
  3.     public function _empty($name){
  4.        //把所有城市的操作解析到city方法
  5.        $this->city($name);
  6.     }
  7.             
  8.     //注意 city方法 是 protected 方法
  9.     protected function city($name){
  10.         //和$name这个城市相关的处理
  11.         echo '当前城市' . $name;
  12.      }
  13.  }
复制代码
 
接下来,我们就可以在浏览器里面输入
  1. http://serverName/index.php/City/beijing/
  2. http://serverName/index.php/City/shanghai/
  3. http://serverName/index.php/City/shenzhen/
复制代码
 
由于CityAction并没有定义beijing、shanghai或者shenzhen操作方法,因此系统会定位到空操作方法 _empty中去解析,_empty方法的参数就是当前URL里面的操作名,因此会看到依次输出的结果是:
  1. 当前城市:beijing
  2.  当前城市:shanghai
  3.  当前城市:shenzhen
复制代码
 
空模块的概念是指当系统找不到指定的模块名称的时候,系统会尝试定位空模块(EmptyAction),利用这个机制我们可以用来定制错误页面和进行URL的优化。现在我们把前面的需求进一步,把URL由原来的
  1. http://serverName/index.php/City/shanghai/
复制代码
 
变成
  1. http://serverName/index.php/shanghai/
复制代码
 
这样更加简单的方式,如果按照传统的模式,我们必须给每个城市定义一个Action类,然后在每个Action类的index方法里面进行处理。 可是如果使用空模块功能,这个问题就可以迎刃而解了。 我们可以给项目定义一个EmptyAction类
  1. <?php
  2.  class EmptyAction extends Action{
  3.     public function index(){
  4.         //根据当前模块名来判断要执行那个城市的操作
  5.         $cityName = MODULE_NAME;
  6.         $this->city($cityName);
  7.     }
  8.    //注意 city方法 本身是 protected 方法
  9.    protected function city($name){
  10.        //和$name这个城市相关的处理
  11.        echo '当前城市' . $name;
  12.     }
  13.  }
复制代码
 
接下来,我们就可以在浏览器里面输入
  1. http://serverName/index.php/beijing/
  2. http://serverName/index.php/shanghai/
  3. http://serverName/index.php/shenzhen/
复制代码
 
由于系统并不存在beijing、shanghai或者shenzhen模块,因此会定位到空模块(EmptyAction)的默认操作(index)去执行,会看到依次输出的结果是:
  1. 当前城市:beijing
  2.  当前城市:shanghai
  3.  当前城市:shenzhen
复制代码
 
空模块和空操作还可以同时使用,用以完成更加复杂的操作。

前置和后置操作

如果当前访问的操作是存在的,系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,前置和后置操作的方法名是在要执行的方法前面加 _before_和_after_,例如:
  1. class IndexAction extends Action{
  2.     //前置操作方法
  3.     public function _before_index(){
  4.         echo 'before<br/>';
  5.     }
  6.     public function index(){
  7.         echo 'index<br/>';
  8.     }
  9.     //后置操作方法
  10.     public function _after_index(){
  11.         echo 'after<br/>';
  12.     }
  13.  }
复制代码
 
如果我们访问
  1. http://serverName/index.php
复制代码
 
结果会输出
  1. before
  2. index
  3. after
复制代码
 
对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
需要注意的是,如果在操作方法里面使用了exit或者error方法的话 有可能不会再执行后置方法了。

跳转和重定向

系统的Action类内置了两个页面跳转方法error和success,分别用于错误(提示)跳转和成功(提示)跳转。两个方法都会输出一个提示信息页面,然后自动跳转到指定的地址。如果当前请求是ajax方式的话,则会自动进行ajax数据返回。下面是一个简单的例子:
  1. $User = M('User'); //实例化User对象
  2. $result = $User->add($data); 
  3.  if($result){
  4.     //设置成功后跳转页面的地址,默认的返回页面是$_SERVER['HTTP_REFERER']
  5.     $this->success('新增成功', '/User/list');
  6.  } else {
  7.     //错误页面的默认跳转页面是返回前一页,通常不需要设置
  8.     $this->error('新增失败');
  9.  }
复制代码
 
Success和error方法都有对应的模板,并且是可以设置的,默认的设置是系统模板:
  1. //默认错误跳转对应的模板文件
  2.  'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl',
  3.  //默认成功跳转对应的模板文件
  4.  'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.tpl',
复制代码
 
我们可以在项目配置文件中修改为使用项目内部的模板文件
  1. //默认错误跳转对应的模板文件
  2.  'TMPL_ACTION_ERROR' => 'Public:error',
  3.  //默认成功跳转对应的模板文件
  4.  'TMPL_ACTION_SUCCESS' => 'Public:success',
复制代码
 
如果你的操作不需要任何提示页面,也可以直接使用页面重定向功能。
系统提供了redirect方法实现页面的重定向功能。
例如:
  1. //重定向到New模块的Category操作
  2. $this->redirect('New/category', array('cate_id' => 2), 5, '页面跳转中...');
复制代码
 
上面的用法是停留5秒后跳转到New模块的category操作,并且显示页面跳转中字样,重定向后会改变当前的URL地址。
redirect方法的第一个参数和第二个参数的配合来完成实际的URL地址的组装,用法和U函数的用法基本一致。
如果你仅仅是想重定向要一个指定的URL地址,而不是到某个模块的操作方法,可以直接使用redirect函数重定向,例如:
  1. //重定向到指定的URL地址
  2. redirect('/New/category/cate_id/2', 5, '页面跳转中...');
复制代码
 
Redirect方法的第一个参数是要跳转的实际URL地址。

AJAX返回

目前的很多WEB应用中大量运用了ajax操作,系统也提供了一个用于ajax数据返回的方法ajaxReturn方法,用法:
  1. $this->ajaxReturn(返回数据[,返回数据格式]);
复制代码
 
目前已经支持的ajax返回数据格式包括:XML JSON JSONP EVAL。
下面是一个简单的例子:
  1. $data['status'] = 1;
  2. $data['info'] = 'info';
  3. $data['data'] = $data;
  4. $data['url'] = $url;
  5. $this->ajaxReturn($data);
复制代码
 
在客户端就可以接收传递的$data数据,可以通过ajaxReturn方法传递任意数据到客户端。如果不指定返回格式的话,默认为JSON格式返回,也可以指定数据格式返回:
  1. $this->ajaxReturn($data,'XML');
复制代码
 
页面跳转方法success和error如果在ajax请求方式下面会自动调用ajaxReturn方法,例如:
  1. $this->success('发布成功',$url);
复制代码
 
等效于使用:
  1. $data['info'] = '发布成功';
  2. $data['url']    = $url;
  3. $data['status'] = 1;
  4. $this->ajaxReturn($data);
复制代码
 
在客户端就可以接收返回的包含info、url和status值的data数据。

你无需担心客户端怎么发送ajax请求给ThinkPHP,ThinkPHP可以自动识别大部分类库的ajax请求,包括JqueryAjax,但某些Flash上传组件可能无法准确识别,请确保在请求的URL地址中传入ajax=1参数,这样就能让ThinkPHP识别为Ajax操作。

页面请求类型

如果需要根据当前的页面请求类型来做出不同的处理,可以使用系统提供的几个常量:
REQUEST_METHOD当前请求类型
IS_GET是否GET请求
IS_POST是否POST请求
IS_PUT是否PUT请求
IS_DELETE是否DELETE请求
IS_AJAX是否AJAX请求
举例如下:
  1. class UserAction extends Action{
  2.     public function update(){
  3.         if (IS_POST){
  4.             $User = M('User');
  5.             $User->create();
  6.             $User->save();
  7.             $this->success('保存完成');
  8.         }else{
  9.             $this->error('非法请求');
  10.         }
  11.     }
  12.  }
复制代码
 

伪静态

默认情况下,ThinkPHP可以支持所有的静态后缀,并且会记录当前的伪静态后缀到常量__EXT__,但不会影响正常的页面访问。
例如:
  1. http://serverName/User/3.html
  2. http://serverName/User/3.shtml
  3. http://serverName/User/3.xml
  4. http://serverName/User/3.pdf
复制代码
 
都可以正常访问,如果要获取当前访问的伪静态后缀,通过常量__EXT__获取即可。
如果希望统一伪静态后缀,可以设置:
  1. 'URL_HTML_SUFFIX'=>'html'
复制代码
 
现在则只能访问
  1. http://serverName/User/3.html
复制代码
 
也可以支持允许多个后缀,例如:
  1. 'URL_HTML_SUFFIX'=>'html|shtml|xml' // 多个用 | 分割
复制代码
 
这样,当访问http://serverName/User/3.pdf的时候会报系统错误。
是实际应用中,我们可以根据当前的URL访问后缀来做出不同的输出处理。

多层控制器

3.1版本开始增加了多层业务控制器的支持,给中大型应用提供了方便,例如我们可以分为业务控制器和事件控制器:
  1. Action/UserAction //用于用户的业务逻辑控制和调度
  2.  Event/UserEvent //用于用户的事件响应操作
复制代码
 
UserAction负责外部交互响应,通过URL请求响应,例如 http://serverName/User/index,而UserEvent 负责内部的事件响应,并且只能在内部调用
  1.  A('User','Event');
复制代码
 
所以是和外部隔离的。多层控制器的划分也不是强制的,可以根据项目的需要自由分层。控制器分层里面可以根据需要调用分层模型,也可以调用不同的目录的视图模板。

总结

本篇涉及到的ThinkPHP的控制器特性包括空模块和空操作、前置和后置操作、参数绑定、伪静态、跳转和重定向、ajax返回、请求类型,而新版的多层控制器的特性更是值得回味。

来自  
http://www.thinkphp.cn/info/170.html

lushi241407月14日
$this->success('新增成功', '/User/list');后面的跳转地址应该是一个完整的url才行吧
electroApache05月15日
哈哈,之前的问题解答了,看来坚持是米有错的。
longtianze2014年12月28日
多层控制器少了必要说明,应该说明如何做多层控制器设置
回复文艺の张2015年02月11日
是的,都已经说了是大型应用,按道理应该是很高级的功能,却没有详解,不太好吧这样
夢想家_Dream2014年11月12日
我根据这个URL: http://localhost/xcjy/admin/index.php/tuji/add/id/263找,在对应的TujiAction.class.php文件中怎么找不带对应的操作名add呢,很奇怪啊,这是别人写的代码, 我现在需要改。。。
回复文艺の张2015年02月11日
控制器名我忘了在url中要不要区分大小写 如果区分你这样肯定是错的 因为tuji
跟Tuji是两个不同的控制器,其中一个没有add方法完全有可能,我只是猜测,仅供参考
茂名搜房网2014年08月27日
好!
淞¢、听静2014年07月22日
学习中....
leeyi2014年05月31日
我很喜欢这个功能
windrunner2014年04月01日
dispatch_jump.tpl中 $message,$JumpUrl 的定义在哪儿???
回复iLoveProgram2014年04月14日
dispatch_jump.tpl,这个文件(默认)指的是系统模板('./ThinkPHP/Tpl')下的dispatch_jump.tpl,用工具(记事本等)打开,查看到:
<div class="system-message">
<present name="message">
<h1>:)</h1>
<p class="success"><?php echo($message); ?></p>
<else/>
<h1>:(</h1>
<p class="error"><?php echo($error); ?></p>
</present>
<p class="detail"></p>
<p class="jump">
页面自动 <a id="href" href="<?php echo($jumpUrl); ?>">跳转</a> 等待时间: <b id="wait"><?php echo($waitSecond); ?></b>
</p>
</div>
在(比如)IndexAction类中的insert方法,$this->success("成功");
那么验证的时候,如果成功的话,则输出<p class="success"><?php echo($message); ?></p>
$jumpUrl指的是,你原先跳转过来的URL。
回复文艺の张2015年02月11日
回复 iLoveProgram : 这才是一个好程序员的表达方式,赞一个
、一汀烟雨2014年03月30日
啃..
javcof2014年03月03日
ThinkPHP3.1快速入门(11)控制器高级特性 Pass
qufo2014年01月07日
参数绑定没有过滤呀,还推荐使吗?
tree1002013年11月19日
看着看着就困了!
回复fudongdong6902013年12月18日
困+1
回复chazz2014年04月01日
已经睡着+
回复wlkwulikun2014年07月02日
好困啊
回复fudongdong6902013年12月18日
困+1
回复chazz2014年04月01日
已经睡着+
回复wlkwulikun2014年07月02日
好困啊
Flow2013年10月10日
粗略看一遍,等我锅铲瓢盆认识完了做菜给大家吃
tp849137402013年09月09日
学习了
tp849137402013年09月09日
学习了
tp849137402013年09月09日
学习了
uuaide2013年08月02日
那个before after意义何在? 要想在前面后面执行,把两个方法放在主方法里面的前面后后面不是同样可以调用
回复smy2013年10月09日
清晰代码层次
回复fengxiaoning2013年11月11日
回复 smy : 这位仁兄说的很有道理!
回复smy2013年10月09日
清晰代码层次
回复fengxiaoning2013年11月11日
回复 smy : 这位仁兄说的很有道理!
jkb1232013年05月27日
ttt
chrislee2013年04月11日
在Win7下,ajaxReturn响应jQuery Ajax请求出错,同样的代码XP下正常。
回复ebwaaa2013年05月04日
跟系统没关系 应该是浏览器问题 XP是IE6 吗
回复ebwaaa2013年05月04日
跟系统没关系 应该是浏览器问题 XP是IE6 吗
chrislee2013年04月10日
能有个完整例子更好。直接去啃应用中的源代码太费时间。
qufo2014年01月07日
参数绑定没有过滤呀,还推荐使吗?
tree1002013年11月19日
看着看着就困了!
Flow2013年10月10日
粗略看一遍,等我锅铲瓢盆认识完了做菜给大家吃
tp849137402013年09月09日
学习了
tp849137402013年09月09日
学习了
tp849137402013年09月09日
学习了
uuaide2013年08月02日
那个before after意义何在? 要想在前面后面执行,把两个方法放在主方法里面的前面后后面不是同样可以调用
jkb1232013年05月27日
ttt
chrislee2013年04月11日
在Win7下,ajaxReturn响应jQuery Ajax请求出错,同样的代码XP下正常。
chrislee2013年04月10日
能有个完整例子更好。直接去啃应用中的源代码太费时间。
卧听海啸2013年01月13日
前置和后置操作里面不能使用$this->assign();吗,我使用后,解析出来的php文件报错,对应的变量不存在!
回复lisgo2013年02月25日
对,我也碰到这个问题,感觉2.1里面很好的,到了3.1就很费劲。
dog123212012年12月23日
请问redirect的模板如何更改呢?
d4rkl0rd2012年12月06日
url参数绑定难道不能做成xxx/2012/12/21 自动绑定到$year,$month,$day吗?非要year/2012/month/12/day/21 这样来?
回复thinkphp2012年12月22日
只要你做了路由 一样可以支持
半个书生2012年10月23日
发现一个问题, 例如UserAction 这个下面只有 index 的操作, 如果我在对应的模板目录下 添加一个 add.html 那么 UserAction 就有的 add 操作了。 这个也是空操作的一个特性?
回复半个书生2012年10月23日
看源码了,这个也是空操作的一个特性。
if(method_exists($this,'_empty')) {
// 如果定义了_empty操作 则调用
$this->_empty($method,$args);
}elseif(file_exists_case(C('TEMPLATE_NAME'))){
// 检查是否存在默认模版 如果有直接输出模
回复深深的呼吸1232012年11月18日
这叫什么空操作,你知道用户一定要按你的模板名来输入??空操作是访问任何不存在的方法都有相应的处理。
回复thinkphp2012年12月22日
这个不是空操作的特性,是TP的一个简化控制器定义的一个特性而已
半个书生2012年10月22日
多层控制器 可以讲的更详细一点儿,这个功能非常实用,十分期待
回复jiavgker2013年01月08日
是啊,最好有个小例子。感觉后续的教程大多是拷贝手册上的内容,而非教程。
aoyoo2012年10月20日
大哥什么时候能够出3.1版本的PDF使用手册啊?很期待啊!!!
回复amao1852012年10月23日
PDF的我有,某位兄弟做的。 我的离线CHM版的已经完成90%了,只剩滚动条问题没解决了。
yangweijie2012年10月20日
有的flash上传组件发送的tp可能判断不出是ajax,可以在url中传ajax=1的参数,让tp识别为Ajax操作
回复thinkphp2012年10月20日
嗯 ,已经添加。

普通分类: