PHP的Yii框架中过滤器相关的使用总结
Yii过滤器简介
过滤器是一段代码,可被配置在控制器动作执行之前或之后执行。例如,访问控制过滤器将被执行以确保在执行请求的动作之前用户已通过身份验证;性能过滤器可用于测量控制器执行所用的时间。
一个动作可以有多个过滤器。过滤器执行顺序为它们出现在过滤器列表中的顺序。过滤器可以阻止动作及后面其他过滤器的执行。
过滤器有两种写法:
- 基于方法的过滤器
- 基于自定义过滤器类的过滤器
无论哪种过滤器,都必须在控制器中重写控制器的publicfunctionfilters()方法,设置哪个过滤器对哪个动作起作用。
基于方法的过滤器
编写基于方法的过滤器,要经过三步:
在控制器中编写动作(Action);
在控制器中编写过滤器函数,函数名必须以filter为前缀,如:functionfilterAccessControl();
重写父类CController的filters()方法,定义过滤器与动作的关系;
实例:
<?php classUserControllerextendsCController{ ** *第一步:创建动作 */ functionactionAdd(){ echo"actionAdd"; } /** *第二步:创建基于方法的过滤器 */ publicfunctionfilterAddFilter($filterChain){ echo"基于方法的过滤器UserController.filterAdd<br>"; $filterChain->run(); } /** *第三步:重写父类CController的filters()方法,定义过滤器与动作的关系 *@seeCController::filters() */ publicfunctionfilters(){ returnarray( //定义过滤器与动作的关联关系 'addFilter+add', //array( //'application.filters.TestFilter', //), ); } }
自定义过滤器类
自定义过滤器类,需要单独写一个过滤器类,并继承CFilter类,重写CFilter类下的部分方法。大家可以看一下CFilter类的代码,该类代码不多,还是很容易看懂的。
自定义过滤器实例:
<?php classTestFilterextendsCFilter{ /** *Performsthepre-actionfiltering. *@paramCFilterChain$filterChainthefilterchainthatthefilterison. *@returnbooleanwhetherthefilteringprocessshouldcontinueandtheaction *shouldbeexecuted. */ protectedfunctionpreFilter($filterChain) { echo"--->TestFilter.preFilter.<br>"; returntrue; } /** *Performsthepost-actionfiltering. *@paramCFilterChain$filterChainthefilterchainthatthefilterison. */ protectedfunctionpostFilter($filterChain) { echo"--->TestFilter.postFilter.<br>"; } }
在控制器中注册该自定义过滤器与动作的绑定关系:
/** *第三步:重写父类CController的filters()方法,定义过滤器与动作的关系 *@seeCController::filters() */ ublicfunctionfilters(){ returnarray( //定义过滤器与动作的关联关系 'addFilter+add', array( 'application.filters.TestFilter', ), );
我自定义了一个过滤器:TestFilter,继承了CFilter类,重写了CFilter类的两个主要方法:preFilter(前控制器,在动作执行前运行)和postFilter(后控制器,在动作执行后运行)。
两种控制器的执行顺序
假设我将上面编写的自定义过滤器类与动作actionAdd绑定,那么,自定义过滤器继承自父类CFilter两个方法:preFilter和postFilter,与绑定的actionAdd之间的执行顺序是怎样的呢?
经过试验,执行顺序为:CFilter::preFilter--------->UserController::actionAdd--------->CFilter::postFilter。
也就是说,在动作执行前后都可以执行过滤操作。
那么文章开头说“过滤器可以阻止动作及后面其他过滤器的执行”是怎么做到的呢?
看了CFilter::preFilter的官方注释就知道了:
@returnbooleanwhetherthefilteringprocessshouldcontinueandtheactionshouldbeexecuted。
CFilter::preFilter函数默认return
true;即,默认执行后面的动作和后过滤器。如果在自定义过滤器类中,重写CFilter::preFilter方法,并return
false;就可以阻止后面的动作和过滤器执行了!
使用过滤器
过滤器本质上是一类特殊的行为,所以使用过滤器和使用行为一样。可以在控制器类中覆盖它的yii\base\Controller::behaviors()方法来申明过滤器,如下所示:
publicfunctionbehaviors() { return[ [ 'class'=>'yii\filters\HttpCache', 'only'=>['index','view'], 'lastModified'=>function($action,$params){ $q=new\yii\db\Query(); return$q->from('user')->max('updated_at'); }, ], ]; }
控制器类的过滤器默认应用到该类的所有动作,你可以配置yii\base\ActionFilter::only属性明确指定控制器应用到哪些动作。在上述例子中,HttpCache过滤器只应用到index和view动作。也可以配置yii\base\ActionFilter::except属性使一些动作不执行过滤器。
除了控制器外,可在模块或应用主体中申明过滤器。申明之后,过滤器会应用到所属该模块或应用主体的所有控制器动作,除非像上述一样配置过滤器的yii\base\ActionFilter::only和yii\base\ActionFilter::except属性。
补充:在模块或应用主体中申明过滤器,在yii\base\ActionFilter::only和yii\base\ActionFilter::except属性中使用路由代替动作ID,因为在模块或应用主体中只用动作ID并不能唯一指定到具体动作。.
当一个动作有多个过滤器时,根据以下规则先后执行:
预过滤
- 按顺序执行应用主体中behaviors()列出的过滤器。
- 按顺序执行模块中behaviors()列出的过滤器。
- 按顺序执行控制器中behaviors()列出的过滤器。
- 如果任意过滤器终止动作执行,后面的过滤器(包括预过滤和后过滤)不再执行。
- 成功通过预过滤后执行动作。
后过滤
- 倒序执行控制器中behaviors()列出的过滤器。
- 倒序执行模块中behaviors()列出的过滤器。
- 倒序执行应用主体中behaviors()列出的过滤器。
创建过滤器
继承yii\base\ActionFilter类并覆盖yii\base\ActionFilter::beforeAction()和/或yii\base\ActionFilter::afterAction()方法来创建动作的过滤器,前者在动作执行之前执行,后者在动作执行之后执行。yii\base\ActionFilter::beforeAction()返回值决定动作是否应该执行,如果为false,之后的过滤器和动作不会继续执行。
下面的例子申明一个记录动作执行时间日志的过滤器。
namespaceapp\components; useYii; useyii\base\ActionFilter; classActionTimeFilterextendsActionFilter { private$_startTime; publicfunctionbeforeAction($action) { $this->_startTime=microtime(true); returnparent::beforeAction($action); } publicfunctionafterAction($action,$result) { $time=microtime(true)-$this->_startTime; Yii::trace("Action'{$action->uniqueId}'spent$timesecond."); returnparent::afterAction($action,$result); } }
核心过滤器
Yii提供了一组常用过滤器,在yii\filters命名空间下,接下来我们简要介绍这些过滤器。
1.yii\filters\AccessControl
AccessControl提供基于yii\filters\AccessControl::rules规则的访问控制。特别是在动作执行之前,访问控制会检测所有规则并找到第一个符合上下文的变量(比如用户IP地址、登录状态等等)的规则,来决定允许还是拒绝请求动作的执行,如果没有规则符合,访问就会被拒绝。
如下示例表示表示允许已认证用户访问create和update动作,拒绝其他用户访问这两个动作。
useyii\filters\AccessControl; publicfunctionbehaviors() { return[ 'access'=>[ 'class'=>AccessControl::className(), 'only'=>['create','update'], 'rules'=>[ //允许认证用户 [ 'allow'=>true, 'roles'=>['@'], ], //默认禁止其他用户 ], ], ]; }
2.认证方法过滤器
认证方法过滤器通过HTTPBasicAuth或OAuth2来认证一个用户,认证方法过滤器类在yii\filters\auth命名空间下。
如下示例表示可使用yii\filters\auth\HttpBasicAuth来认证一个用户,它使用基于HTTP基础认证方法的令牌。注意为了可运行,yii\web\User::identityClass类必须实现yii\web\IdentityInterface::findIdentityByAccessToken()方法。
useyii\filters\auth\HttpBasicAuth; publicfunctionbehaviors() { return[ 'basicAuth'=>[ 'class'=>HttpBasicAuth::className(), ], ]; }
认证方法过滤器通常在实现RESTfulAPI中使用。
3.yii\filters\ContentNegotiator
ContentNegotiator支持响应内容格式处理和语言处理。通过检查GET参数和AcceptHTTP头部来决定响应内容格式和语言。
如下示例,配置ContentNegotiator支持JSON和XML响应格式和英语(美国)和德语。
useyii\filters\ContentNegotiator; useyii\web\Response; publicfunctionbehaviors() { return[ [ 'class'=>ContentNegotiator::className(), 'formats'=>[ 'application/json'=>Response::FORMAT_JSON, 'application/xml'=>Response::FORMAT_XML, ], 'languages'=>[ 'en-US', 'de', ], ], ]; }
在应用主体生命周期过程中检测响应格式和语言简单很多,因此ContentNegotiator设计可被引导启动组件调用的过滤器。如下例所示可以将它配置在应用主体配置。
useyii\filters\ContentNegotiator; useyii\web\Response; [ 'bootstrap'=>[ [ 'class'=>ContentNegotiator::className(), 'formats'=>[ 'application/json'=>Response::FORMAT_JSON, 'application/xml'=>Response::FORMAT_XML, ], 'languages'=>[ 'en-US', 'de', ], ], ], ];
补充:如果请求中没有检测到内容格式和语言,使用formats和languages第一个配置项。
4.yii\filters\HttpCache
HttpCache利用Last-Modified和EtagHTTP头实现客户端缓存。例如:
useyii\filters\HttpCache; publicfunctionbehaviors() { return[ [ 'class'=>HttpCache::className(), 'only'=>['index'], 'lastModified'=>function($action,$params){ $q=new\yii\db\Query(); return$q->from('user')->max('updated_at'); }, ], ]; }
5.yii\filters\PageCache
PageCache实现服务器端整个页面的缓存。如下示例所示,PageCache应用在index动作,缓存整个页面60秒或post表的记录数发生变化。它也会根据不同应用语言保存不同的页面版本。
useyii\filters\PageCache; useyii\caching\DbDependency; publicfunctionbehaviors() { return[ 'pageCache'=>[ 'class'=>PageCache::className(), 'only'=>['index'], 'duration'=>60, 'dependency'=>[ 'class'=>DbDependency::className(), 'sql'=>'SELECTCOUNT(*)FROMpost', ], 'variations'=>[ \Yii::$app->language, ] ], ]; }
6.yii\filters\RateLimiter
RateLimiter根据漏桶算法来实现速率限制。
7.yii\filters\VerbFilter
VerbFilter检查请求动作的HTTP请求方式是否允许执行,如果不允许,会抛出HTTP405异常。如下示例,VerbFilter指定CRUD动作所允许的请求方式。
useyii\filters\VerbFilter; publicfunctionbehaviors() { return[ 'verbs'=>[ 'class'=>VerbFilter::className(), 'actions'=>[ 'index'=>['get'], 'view'=>['get'], 'create'=>['get','post'], 'update'=>['get','put','post'], 'delete'=>['post','delete'], ], ], ]; }
8.yii\filters\Cors
跨域资源共享CORS机制允许一个网页的许多资源(例如字体、JavaScript等)这些资源可以通过其他域名访问获取。特别是JavaScript'sAJAX调用可使用XMLHttpRequest机制,由于同源安全策略该跨域请求会被网页浏览器禁止.CORS定义浏览器和服务器交互时哪些跨域请求允许和禁止。
yii\filters\Cors应在授权/认证过滤器之前定义,以保证CORS头部被发送。
useyii\filters\Cors; useyii\helpers\ArrayHelper; publicfunctionbehaviors() { returnArrayHelper::merge([ [ 'class'=>Cors::className(), ], ],parent::behaviors()); }
Cors可转为使用cors属性。
- cors['Origin']:定义允许来源的数组,可为['*'](任何用户)或['http://www.myserver.net','http://www.myotherserver.com'].默认为['*'].
- cors['Access-Control-Request-Method']:允许动作数组如['GET','OPTIONS','HEAD'].默认为['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'].
- cors['Access-Control-Request-Headers']:允许请求头部数组,可为['*']所有类型头部或['X-Request-With']指定类型头部.默认为['*'].
- cors['Access-Control-Allow-Credentials']:定义当前请求是否使用证书,可为true,false或null(不设置).默认为null.
- cors['Access-Control-Max-Age']:定义请求的有效时间,默认为86400.
例如,允许来源为http://www.myserver.net和方式为GET,HEAD和OPTIONS的CORS如下:
useyii\filters\Cors; useyii\helpers\ArrayHelper; publicfunctionbehaviors() { returnArrayHelper::merge([ [ 'class'=>Cors::className(), 'cors'=>[ 'Origin'=>['http://www.myserver.net'], 'Access-Control-Request-Method'=>['GET','HEAD','OPTIONS'], ], ], ],parent::behaviors()); }
可以覆盖默认参数为每个动作调整CORS头部。例如,为login动作增加Access-Control-Allow-Credentials参数如下所示:
useyii\filters\Cors; useyii\helpers\ArrayHelper; publicfunctionbehaviors() { returnArrayHelper::merge([ [ 'class'=>Cors::className(), 'cors'=>[ 'Origin'=>['http://www.myserver.net'], 'Access-Control-Request-Method'=>['GET','HEAD','OPTIONS'], ], 'actions'=>[ 'login'=>[ 'Access-Control-Allow-Credentials'=>true, ] ] ], ],parent::behaviors()); }