0%

Yii中的过滤器-Filter

在网上找关于Yii的过滤器内容实在太少,而且大多数讲得不得要领。只好读源码来理解。现把笔记整理如下:

正常情况下,访问某个action时,yii会通过调用CControllerrunActionWithFilters 方法来执行action。核心代码如下:

1
2
3
4
5
//CController.php CController是所有controller的基类
//$filters来自于当前 controller 中的 filters 方法

$filterChain = CFilterChain::create($this,$action,$filters);
$filterChain->run();

即:将当前controller对象 + action对象 + $filters 一起传递给CFilterChaincreate方法,创建一个过滤器的链[filter-chain]–$filterChain。然后执行该 $filterChainrun() 方法。

在此并没有看到执行 action 部分的代码,因为执行action的代码在 $filterChainrun() 方法中实现。

过滤器链-FilterChain

上述用到的变量$filterChain 是一个 CFilterChain 对象,本质上是一个List 对象(CList),一个链式结构。

该对象包含一个数组,数组元素是一个个filter对象(CFilter)。另外包含一个游标,用来保存当前指向的filter下标。

下面是CFilterChainrun() 方法

1
2
3
4
5
6
7
8
9
10
public function run()
{
if($this->offsetExists($this->filterIndex)) //如果当前游标有指向的 filter
{
$filter=$this->itemAt($this->filterIndex++); //获取当前 filter 对象, 并将游标 +1
$filter->filter($this); //执行该filter对象的filter方法,执行时需将当前的$filterChain作为参数
}
else
$this->controller->runAction($this->action); //当前游标的指向没有 filter对象 时执行action
}

run() 方法完成两件事:

  1. 如果有未执行的filter则执行filter $filter->filter($this)
  2. 执行完最后一个filter后执行action()

因此,每个 filter 对象 必须有filter() 方法。每个 filter 在完成业务逻辑后,要继续调用 $filterChainrun() 方法。直到最后一个 filter 通过后,执行 action

Yii中的filter

当前controller中,各个action需要用到的filter都在controllerfilters()方法中定义。即:runActionWithFilters()方法中的$filters变量的值通过调用当前controllerfilters()方法获得。

Yii中可调用的filter分两类,在controller中分别以字符串和数组的方式调用。两类filtercontroller中的调用方法如下:

1
2
3
4
5
6
public function filters(){
return array(
'postOnly - refresh', //第一种filter,以字符串定义
array('path.to.filterClass - index', 'param1'=>'value1'), //第二种filter 以数组定义
);
}

在创建CFilterChain对象时,会将filters()方法中定义的不同filter分别以不同的方式生成。以字符串调用的会生成CFilterInline对象;以数组调用的,必须是已定义的类,继承自CFilter的类,会被yii以创建组件的方式创建为对象,本质上为使用new关键字。

CInlineFilter

这是第一类,以字符串方式调用的Filter,会生成CInlineFilter对象。如常见的postOnlyaccessControl等。这类filterfilter()方法实现很简单,本质上是调用当前controller中以filter开头的方法,如:

1
2
3
4
5
6
//CInlineFilter.php
public function filter($filterChain)
{
$method='filter'.$this->name;
$filterChain->controller->$method($filterChain);
}

该方式要求对应的filter方法中必须有调用$filterChain->run()的逻辑操作,如 postOnly 的实现:

1
2
3
4
5
6
7
8
//CController.php
public function filterPostOnly($filterChain)
{
if(Yii::app()->getRequest()->getIsPostRequest())
$filterChain->run(); //在这里完成filterChain的继续
else
throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); //不通过则报错,不再执行后面的filter
}

Filter类

这是第二类以数组方式调用的Filter。本质上为继承自CFilter的类。只要该类实现的preFilter方法即可。父类(CFilter)中的run方法会调用 $filterChain->run(),如:

1
2
3
4
5
6
7
8
9
//CFilter.php
public function filter($filterChain)
{
if($this->preFilter($filterChain))
{
$filterChain->run();
$this->postFilter($filterChain);
}
}

preFilter和postFilter

每个Filter类都包含preFilter和postFilter。FilterChain中的filter执行顺序一般为:

1
filter1.preFilter -> filter2.preFilter -> ... filterN.preFilter -> actioin -> filterN.postFilter -> ... filter2.postFilter -> filter1.postFilter

因此为了在filter中进行访问控制,一般都在preFilter()方法中写过滤逻辑。如果写到postFilter(),等到执行的时候action已经执行完毕了。

常用filter及定义方式

最常用到的filter就是yii自带的三个CInlineFilter: postOnly ajaxOnly accessControl,调用的操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
public function filters(){
return array(
//"-"表示:除了refresh的action外,本`controller`中所有操作只能以post请求进行
'postOnly - refresh',

//"+"表示:disable 和enable 操作只能以ajax方式进行
'ajaxOnly + disable,enable',

//没有"-"和"+"表示:所有操作都要通过 accessControl 过滤器
'accessControl',
);
}

如上述所述,三个filter都是CInlineFilter,本质上是调用CController中定义的对应的filter方法:filterPostOnly filterAjaxOnly filterAccessControl