ThinkPHP3.1控制器高级特性


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

Action参数绑定

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


空模块和空操作

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

前置和后置操作

如果当前访问的操作是存在的,系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,前置和后置操作的方法名是在要执行的方法前面加 _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/>';
    }
}
如果我们访问
http://serverName/index.php
结果会输出
before
index
after
对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
需要注意的是,如果在操作方法里面使用了exit或者error方法的话 有可能不会再执行后置方法了。

跳转和重定向

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

AJAX返回

目前的很多WEB应用中大量运用了ajax操作,系统也提供了一个用于ajax数据返回的方法ajaxReturn方法,用法:
$this->ajaxReturn(返回数据[,返回数据格式]);
目前已经支持的ajax返回数据格式包括:XML JSON JSONP EVAL。
下面是一个简单的例子:
$data['status'] = 1;
$data['info'] = 'info';
$data['data'] = $data;
$data['url'] = $url;
$this->ajaxReturn($data);
在客户端就可以接收传递的$data数据,可以通过ajaxReturn方法传递任意数据到客户端。如果不指定返回格式的话,默认为JSON格式返回,也可以指定数据格式返回:
$this->ajaxReturn($data,'XML');
页面跳转方法success和error如果在ajax请求方式下面会自动调用ajaxReturn方法,例如:
$this->success('发布成功',$url);
等效于使用:
$data['info'] = '发布成功';
$data['url']    = $url;
$data['status'] = 1;
$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请求
举例如下:
class UserAction extends Action{
    public function update(){
        if (IS_POST){
            $User = M('User');
            $User->create();
            $User->save();
            $this->success('保存完成');
        }else{
            $this->error('非法请求');
        }
    }
}

伪静态

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

多层控制器

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

总结

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