文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>第三章 掌握Controller 第二节 action详解

第三章 掌握Controller 第二节 action详解

时间:2007-04-11  来源:xiaoshengcaicai

Catalyst项目里面, action的属性决定了这个action可以处理什么样的URL.根据它们的属性, 大概可以划分成以下几种类型:

  1.  path类型  (包含 Local, Path, Global)
  2.  regex类型 (包含 LocalRegex, Regex)
  3.  自定义的private属性的action
  4.  Catalyst内置的private属性的action (这些action包括auto, begin, end, index,     default)
  5.  其他(暂时不在本教程讨论范围内,比如新版本的Catalyst里面的Chained属性)
有一点要注意,在选择哪一个action来处理某个URL的过程中, 主要看2个因素:
1. action所处的Controller的namespace
2. action本身(包括属性的定义,也可能包括action的名字)


1. path类型  (包含 Local, Path, Global)

先看下面的例子:

package X::Controller::A::B;
sub list : Local {
}
sub g_list : Global {
}
sub p_list : path('blist') {
}
sub p2_list : path('/alist') {
}
sub p3_list : path('blist/clist') {
}
1;

在上面的例子里面,Controller的namespace = 'a/b'.

Local属性的action是最为常见的,例子里面的list能匹配的URL为:
'a/b'  + '/' +  'list' =  'a/b/list'

Global属性的action跟Local属性的类似,只是它无视所在Controller的namespace而已, 
例子里面的g_list可以匹配的URL为:
'/' + 'g_list' = '/g_list'

所谓path属性, 是Local属性跟Global属性的结合而已,
当path里面的参数第一个字符是 / 时,表示它是一个Global类型的, 否则,它就是一个Local类型的.
Path属性的action所能匹配的URL跟action的名字无关,而是跟path里面的参数有关.
例子里面:
p_list这个action其实 等同于:  sub blist : Local ,  所以它能匹配的URL为:
'a/b'  + '/' +  'blist' =  'a/b/blist'

p2_list这个action其实 等同于:  sub alist : Global,  所以它能匹配的URL为:
'/' +  'alist' =  '/alist'

p3_list这个action能匹配的URL为:
'a/b'  + '/' +  'blist/clist' =  'a/b/blist/clist'

这时候可能你有疑问, 如果我们定义了多个path类型的action, 这些action都可以映射同一个URL,那么究竟最后会调用哪个action呢?
首先,在实际开发的时候, 一定要尽量避免这种情况的发生, 出现这种情况的话,可以说就是BUG.
因为在这种情况下, 只有最后定义的那个path类型的action可以映射URL, 其他action都白定义了.看这个例子:

package X::Controller::A::B;
sub list : Local {
}
sub p_list : path('list') {
}
sub p2_list : path('list') {
}
1;

这3个action都可以映射URL: a/b/list, 实际上只有最后一个action: p2_list可以实际映射, 前面定义的2个action(list, p_list)会被无情的抛弃.

注意:

一般我们发送get请求的时候,如果要在URL里面附上我们这次请求的参数(称为query_param),那么这个请求的参数是类似这样传递的: http://xxxx/?p=1&s=2 , ?后面的就是我们这次请求的参数, 而catalsyt不仅仅支持这种传统的方法来传递参数(这些请求参数可以通过$c->request->query_param来取到), 它还支持通过argument的方式来传递参数, 比如:
客户端请求http://a/b/list/xiao/sheng
但 是我们没有定义过a/b/list/xiao/sheng这个action,我们只定义过a/b/list这个action,那么catalyst会把这 个请求也交给a/b/list这个action来处理,而xiao,跟sheng这2个字符串就变成URL里面附加的请求参数, 你可以通过$c->request->arguments来得到这2个字符串。那么也就是说:

path类型的action, 比如上面例子里面的list : Local, 我们说它可以匹配 'a/b/list', 并不是说它只能匹配a/b/list, 它是有可能会匹配到a/b/list/xiao/sheng这样的url的。 等所有类型的action都讲完后,我会把如何根据一个URL找到一个action来跟它匹配剖析给大家看。


2. regex类型  (包含 LocalRegex, Regex)

LocalRegex('...') 类似于 Path('..'),只是 LocalRegex更为灵活, 它的参数可以是一个正则表达式.
Regex('...') 类似于 Path('/..'),只是 Regex更为灵活, 它的参数可以是一个正则表达式.

比如
package X::Controller::A::B;
sub a : Path('list'){
}
sub b : LocalRegex('^list') {
}
sub a2 : Path('/list2') {
}
sub b2 : Regex('^list2') {
}

sub x : Regex('xiao') {
}

1;

a这个action它可以匹配的URL为:
a/b/list

b这个action它可以匹配的URL为:
a/b/^list
也就是 a/b/list*,即URL中以a/b/list开头

a2这个action它可以匹配的URL为:
list

b2这个action它可以匹配的URL为:
list2
也就是 list2* , 即URL中以list2开头

x这个action它可以匹配的URL为:
xiao
也就是 *xiao* , 即URL中含有xiao字符串

需要注意, 在为一个URL寻找一个action来进行分发时, path类型的action的优先级高于regex类型的action.
关于regex属性的action,你可以从$c->req->captures 里面得到正则表达式里面匹配到的$1,$2..比如 Regex('^list(\d+)') 可以匹配 list5这样的url,匹配到的5 保存在$c->req->captures->[0]里面。
3. 自定义的private属性的action

private属性的action不能直接映射为URL, 只能用于action内部的调用.
一般来说,为了处理客户端请求, 编写path类型或者regex类型的action已经足够了, 但是在某些情况下,编写private 属性的action, 可以实现代码重用,见下面的例子:

package X::Controller::A::B;
sub c1 : Local{
    my ($self, $c) = @_;
    $c->forward('init_data');
    #continue
    $c->res->body("continute");
}
sub c2 : Local {
    my ($self, $c) = @_;
    $c->detach('init_data');
    #not return
    $c->res->body("last");
}
sub init_data : Private {
    my ($self, $c) = @_;
    #init data code
}
1;

c1, c2是2个可以对外映射URL的action, 而init_data则不可以,
但是c1, c2内部可以通过forward或者detach来调用到init_data,
forward就相当于函数调用,(只不过这个函数调用外部包了一个eval,所以action内部就算die掉也只是抛出一个异常, 不会导致整个系统的崩溃), c1内部执行完init_data这个action后,会继续返回c1继续执行c1下面的代码,
detach跟forward唯一的区别就是detach执行完不再返回, c2执行完detach之后不返回, 也就是说
$c->res->body("last"); 这行代码不会被执行到.

private 属性的action, 我认为它的最大优点在于它可以跨组件调用, 也就是Controller A可以调用Controller B里面的一个private属性的action.这跟我们以前写的cgi不一样, cgi里面是不可以直接调用另外一个cgi文件里面定义的函数的.

从这点上, Catalyst提倡的 donot repeat yourself 可见一斑.

这种跨组件调用可以见下面例子

package X::Controller::A;
sub init_data : Private {
    my ($self, $c) = @_;
    #init data code
}
1;

package X::Controller::B;
sub c1 : Local{
    my ($self, $c) = @_;
    $c->forward('/a/init_data');
    #continue
    $c->res->body("continute");
}

1;


关于forward跟detach,你会在后面章节看到他们更详细的说明.

4. Catalyst内置的private属性的action (这些action包括auto, begin, end, index,     default)

先来谈谈 begin 跟end.
假设我们现在有这样的Controller结构:
X::Controller::Root;       ( namespace 为'')
X::Controller::A;          ( namespace 为默认的'a')
X::Controller::B;          ( namespace 为默认的'b')
X::Controller::A:A2;       ( namespace 为默认的'a/a2')
X::Controller::A:A2::A3;   ( namespace 为默认的'a/a2/a3').

假设现在客户端请求的URL为 a/a2/a3/list
刚好X::Controller::A:A2::A3里面定义了一个名为list,属性为Local的action,
那么Catalyst就会把URL分发给X::Controller::A:A2::A3::list这个action进行处理,
在list这个action开始执行之前, Catalyst会检查有没有在a/a2/a3这个namespace下定义过
名 字为begin, 属性为private的action,如果定义过,那么先执行a/a2/a3/begin这个action,之后再执行list这个action,如果没 有定义过, 那么会沿着a/a2/a3这个命名空间往上一层去找, 也就是检查有没有定义过a/a2/begin这个action,如果有则执行, 然后再执行list,如果没有则继续往上找,直到找到或者namespace为空也没找到.
也就是说它按照下面的顺序来查找有没有定义过begin, 有则先执行begin再执行list:
a/a2/a3/begin
a/a2/begin
a/begin
begin

用一句话来概括就是:
根据URL找到可以匹配的action后, 在执行该action之前,会在该action所处的命名空间的每个层次上,从下往上寻找一个名为begin,属性为private的action,找到则执行,执行完不再往上找, 接下来执行匹配的action.

在上面例子里面, 如果同时定义了a/a2/a3/begin跟a/begin, 在执行a/a2/a3/begin之后, 接下来就去执行list了, 不会继续往上找到a/begin来执行.

在执行完list这个action之后, Catalyst会去寻找和执行一个名为end,属性为private的action, 它寻找的顺序是这样的:
a/a2/a3/end
a/a2/end
a/end
end
看得出来寻找end跟寻找begin是一样的,只是一个是在执行list之后,一个是在执行list之前。对于end, 用一句话概括就是:
根据URL找到可以匹配的action后, 在执行该action之后,会在该action所处的命名空间的每个层次上,从下往上寻找一个名为end,属性为private的action,找到则执行,执行完不再往上找.

实际上, 在上面的例子中, 当执行完begin之后,在执行list之前, catalyst还会去寻找一种名为auto,属性为private的action,在上面的例子中,它寻找的auto的顺序是:
auto
a/auto
a/a2/auto
a/a2/a3/auto

当寻找一个auto之后,catalyst会执行它,然后判断auto这个action的返回值, 如果auto返回真值,那么继续往下寻找auto, 继续执行,直到所有的auto都执行完毕且返回真值后, 才会去执行list 这个action。如果当中有某个auto返回了假值, 那么就不再往下寻找auto,也不会再去执行list,就会直接跳到寻找end的过程。
用一句话来概括的话就是:
根 据URL找到可以匹配的action后, 在执行begin之后,在执行该action之前,会在该action所处的命名空间的每个层次上,从上到下寻找一个名为auto,属性为private 的action,找到则执行,如果auto返回真值就继续往下寻找执行auto,当所有的auto都返回真值时,接下来才会去执行list,否则则直接跳 到寻找执行end的过程。

所以在找到list这个action后, catalyst实际上执行了一系列的action,这堆action就是所谓的反应链,在本例中,该反应链为:
a/a2/a3/begin or a/a2/begin or a/begin or begin
auto
a/auto
a/a2/auto
a/a2/a3/auto
a/a2/a3/list
a/a2/a3/end or a/a2/end or a/end or end

剩下还有2个很特殊的private属性的action: index跟default.


我们之前讲到, 在为一个URL寻找一个action来进行分发时, path类型的action的优 先级高于regex类型的action. 那么,是不是path类型的action的优先级是最高的呢? 不是的,有一种比path类型的action优先级更高,它就是private属性的index。下面例子:

package X::Controller::A;
sub b : Local {
}
1;

package X::Controller::A::B;
sub index : private {
}
1;

X::Controller::A::b这个action可以匹配url: a/b,
X::Controller::A::B::index 可以而且只能匹配的url是a/b,
我们之前讲到X::Controller::A::b不仅可以匹配a/b这个url,当没有定义a/b/c这个action时,它可能可以匹配a/b/c这个URL。
而index这种action它只能匹配所在Controller的namespace,也就是a/b。
当有客户请求a/b时, 优先考虑的是X::Controller::A::B::index 这个action,如果这个action没有定义,才会匹配到X::Controller::A::b。
在说到default这个action之前,先看下面的一个例子:

package X::Controller::A;
sub d : Path('') {
}
1;

d这个action它可以匹配a这样的url,如果客户请求a/xiaosheng这样的url时,并没有定义action: a/xiaosheng时,那么d还可以用来处理这个URL,xiaosheng则变成请求的一个参数,从$c->req-> arguments可以得到它。
从这里可以看到,如果客户端访问 a/*的URL时,如果没有定义过相应的a/*这个action的话, d这个action就会派上用场。
那么其实, d这个action可以改成名为default, 属性为private的action,如下:

package X::Controller::A;
sub default : Private {
}
1;


这样的话,default这个action起到的作用跟d的作用是一样,主要用来当找不到可用的action来匹配URL时,就会调用这样 一个默认的action来进行处理,一般是显示一个错误页面,提示用户访问了一不存在的页面。 default这个action跟d的区别在于, 当客户端访问a/xiaosheng时, default内部得到的$c->req->arguments并不是['xiaosheng'], 而是['a','xiaosheng']。当然还有一个区别,那就是default这种action的优先级是最低的,比regex类型的还低。

从这里我们可以知道, X::Controller::Root::default这个action里面可以设置一个全站点的一个默认页面。

5. action的继续剖析

客户端访问a/b/c时,
决定选择哪一个action来处理这个请求的过程如下:
1. $namespace=a/b/c, 到2
2. 在$namespace这个命名空间下, 有没有定义过index这种action,有则选之,无则到3
3. 在$namespace这个命名空间下, 有没有定义过path类型的action可以处理a/b/c这个URL,有则选之,无则到4
4. 在$namespace这个命名空间下, 有没有定义过regex类型的action可以处理a/b/c这个URL,有则选之,无则到5
5. 在$namespace这个命名空间下, 有没有定义过default action可以处理a/b/c这个URL,有则选之,无则到6
6. 如果$namespace已经不可再往上,那么报错,退出,否则$namespace= $namespace往上一层(比如a/b/c往上一层则是a/b),到2

由此可见,如果没有定义Root::default,那么将可能导致无法找到一个action来处理而导致出错。

排行榜 更多 +
方块枪战战场安卓版

方块枪战战场安卓版

飞行射击 下载
战斗火力射击安卓版

战斗火力射击安卓版

飞行射击 下载
空中防御战安卓版

空中防御战安卓版

飞行射击 下载