在网上找关于Yii的过滤器内容实在太少,而且大多数讲得不得要领。只好读源码来理解。现把笔记整理如下:
正常情况下,访问某个action时,yii会通过调用CController的 runActionWithFilters 方法来执行action。核心代码如下:
1 | //CController.php CController是所有controller的基类 |
即:将当前controller对象 + action对象 + $filters 一起传递给CFilterChain的create方法,创建一个过滤器的链[filter-chain]–$filterChain。然后执行该 $filterChain 的 run() 方法。
在此并没有看到执行 action 部分的代码,因为执行action的代码在 $filterChain 的 run() 方法中实现。
过滤器链-FilterChain
上述用到的变量$filterChain 是一个 CFilterChain 对象,本质上是一个List 对象(CList),一个链式结构。
该对象包含一个数组,数组元素是一个个filter对象(CFilter)。另外包含一个游标,用来保存当前指向的filter下标。
下面是CFilterChain的 run() 方法
1 | public function run() |
该 run() 方法完成两件事:
- 如果有未执行的
filter则执行filter $filter->filter($this) - 执行完最后一个
filter后执行action()
因此,每个 filter 对象 必须有filter() 方法。每个 filter 在完成业务逻辑后,要继续调用 $filterChain 的 run() 方法。直到最后一个 filter 通过后,执行 action。
Yii中的filter
当前controller中,各个action需要用到的filter都在controller的filters()方法中定义。即:runActionWithFilters()方法中的$filters变量的值通过调用当前controller的filters()方法获得。
Yii中可调用的filter分两类,在controller中分别以字符串和数组的方式调用。两类filter在controller中的调用方法如下:
1 | public function filters(){ |
在创建CFilterChain对象时,会将filters()方法中定义的不同filter分别以不同的方式生成。以字符串调用的会生成CFilterInline对象;以数组调用的,必须是已定义的类,继承自CFilter的类,会被yii以创建组件的方式创建为对象,本质上为使用new关键字。
CInlineFilter
这是第一类,以字符串方式调用的Filter,会生成CInlineFilter对象。如常见的postOnly,accessControl等。这类filter的 filter()方法实现很简单,本质上是调用当前controller中以filter开头的方法,如:
1 | //CInlineFilter.php |
该方式要求对应的filter方法中必须有调用$filterChain->run()的逻辑操作,如 postOnly 的实现:
1 | //CController.php |
Filter类
这是第二类以数组方式调用的Filter。本质上为继承自CFilter的类。只要该类实现的preFilter方法即可。父类(CFilter)中的run方法会调用 $filterChain->run(),如:
1 | //CFilter.php |
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 | public function filters(){ |
如上述所述,三个filter都是CInlineFilter,本质上是调用CController中定义的对应的filter方法:filterPostOnly filterAjaxOnly filterAccessControl。