Yii過濾器簡介
過濾器是一段代碼,可被配置在控制器動作執行之前或之後執行。例如, 訪問控制過濾器將被執行以確保在執行請求的動作之前用戶已通過身份驗證;性能過濾器可用於測量控制器執行所用的時間。
一個動作可以有多個過濾器。過濾器執行順序為它們出現在過濾器列表中的順序。過濾器可以阻止動作及後面其他過濾器的執行。
過濾器有兩種寫法:
無論哪種過濾器,都必須在控制器中重寫控制器的public function filters()方法,設置哪個過濾器對哪個動作起作用。
基於方法的過濾器
編寫基於方法的過濾器,要經過三步:
在控制器中編寫動作(Action);
在控制器中編寫過濾器函數,函數名必須以filter為前綴,如:function filterAccessControl();
重寫父類CController的filters()方法,定義過濾器與動作的關系;
實例:
<?php class UserController extends CController{ ** * 第一步:創建動作 */ function actionAdd(){ echo "actionAdd"; } /** * 第二步:創建基於方法的過濾器 */ public function filterAddFilter($filterChain) { echo "基於方法的過濾器UserController.filterAdd<br>"; $filterChain->run(); } /** * 第三步:重寫父類CController的filters()方法,定義過濾器與動作的關系 * @see CController::filters() */ public function filters(){ return array( //定義過濾器與動作的關聯關系 'addFilter + add', // array( // 'application.filters.TestFilter', // ), ); } }
自定義過濾器類
自定義過濾器類,需要單獨寫一個過濾器類,並繼承CFilter類,重寫CFilter類下的部分方法。大家可以看一下CFilter類的代碼,該類代碼不多,還是很容易看懂的。
自定義過濾器實例:
<?php class TestFilter extends CFilter{ /** * Performs the pre-action filtering. * @param CFilterChain $filterChain the filter chain that the filter is on. * @return boolean whether the filtering process should continue and the action * should be executed. */ protected function preFilter($filterChain) { echo "--->TestFilter.preFilter.<br>"; return true; } /** * Performs the post-action filtering. * @param CFilterChain $filterChain the filter chain that the filter is on. */ protected function postFilter($filterChain) { echo "--->TestFilter.postFilter.<br>"; } }
在控制器中注冊該自定義過濾器與動作的綁定關系:
/** * 第三步:重寫父類CController的filters()方法,定義過濾器與動作的關系 * @see CController::filters() */ ublic function filters(){ return array( //定義過濾器與動作的關聯關系 'addFilter + add', array( 'application.filters.TestFilter', ), );
我自定義了一個過濾器:TestFilter,繼承了CFilter類,重寫了CFilter類的兩個主要方法:preFilter(前控制器,在動作執行前運行)和postFilter(後控制器,在動作執行後運行)。
兩種控制器的執行順序
假設我將上面編寫的自定義過濾器類與動作actionAdd綁定,那麼,自定義過濾器繼承自父類CFilter兩個方法:preFilter和postFilter,與綁定的actionAdd之間的執行順序是怎樣的呢?
經過試驗,執行順序為:CFilter::preFilter--------->UserController::actionAdd--------->CFilter::postFilter。
也就是說,在動作執行前後都可以執行過濾操作。
那麼文章開頭說“過濾器可以阻止動作及後面其他過濾器的執行”是怎麼做到的呢?
看了CFilter::preFilter的官方注釋就知道了:
@return boolean whether the filtering process should continue and the action should be executed。
CFilter::preFilter函數默認return
true;即,默認執行後面的動作和後過濾器。如果在自定義過濾器類中,重寫CFilter::preFilter方法,並return
false;就可以阻止後面的動作和過濾器執行了!
使用過濾器
過濾器本質上是一類特殊的 行為,所以使用過濾器和 使用 行為一樣。 可以在控制器類中覆蓋它的 yii\base\Controller::behaviors() 方法來申明過濾器,如下所示:
public function behaviors() { 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並不能唯一指定到具體動作。.
當一個動作有多個過濾器時,根據以下規則先後執行:
預過濾
後過濾
創建過濾器
繼承 yii\base\ActionFilter 類並覆蓋 yii\base\ActionFilter::beforeAction() 和/或 yii\base\ActionFilter::afterAction() 方法來創建動作的過濾器,前者在動作執行之前執行,後者在動作執行之後執行。 yii\base\ActionFilter::beforeAction() 返回值決定動作是否應該執行, 如果為false,之後的過濾器和動作不會繼續執行。
下面的例子申明一個記錄動作執行時間日志的過濾器。
namespace app\components; use Yii; use yii\base\ActionFilter; class ActionTimeFilter extends ActionFilter { private $_startTime; public function beforeAction($action) { $this->_startTime = microtime(true); return parent::beforeAction($action); } public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; Yii::trace("Action '{$action->uniqueId}' spent $time second."); return parent::afterAction($action, $result); } }
核心過濾器
Yii提供了一組常用過濾器,在yii\filters命名空間下,接下來我們簡要介紹這些過濾器。
1.yii\filters\AccessControl
AccessControl提供基於yii\filters\AccessControl::rules規則的訪問控制。 特別是在動作執行之前,訪問控制會檢測所有規則並找到第一個符合上下文的變量(比如用戶IP地址、登錄狀態等等)的規則, 來決定允許還是拒絕請求動作的執行,如果沒有規則符合,訪問就會被拒絕。
如下示例表示表示允許已認證用戶訪問create 和 update 動作,拒絕其他用戶訪問這兩個動作。
use yii\filters\AccessControl; public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['create', 'update'], 'rules' => [ // 允許認證用戶 [ 'allow' => true, 'roles' => ['@'], ], // 默認禁止其他用戶 ], ], ]; }
2.認證方法過濾器
認證方法過濾器通過HTTP Basic Auth或OAuth 2 來認證一個用戶,認證方法過濾器類在 yii\filters\auth 命名空間下。
如下示例表示可使用yii\filters\auth\HttpBasicAuth來認證一個用戶,它使用基於HTTP基礎認證方法的令牌。 注意為了可運行,yii\web\User::identityClass 類必須 實現 yii\web\IdentityInterface::findIdentityByAccessToken()方法。
use yii\filters\auth\HttpBasicAuth; public function behaviors() { return [ 'basicAuth' => [ 'class' => HttpBasicAuth::className(), ], ]; }
認證方法過濾器通常在實現RESTful API中使用。
3.yii\filters\ContentNegotiator
ContentNegotiator支持響應內容格式處理和語言處理。 通過檢查 GET 參數和 Accept HTTP頭部來決定響應內容格式和語言。
如下示例,配置ContentNegotiator支持JSON和XML響應格式和英語(美國)和德語。
use yii\filters\ContentNegotiator; use yii\web\Response; public function behaviors() { return [ [ 'class' => ContentNegotiator::className(), 'formats' => [ 'application/json' => Response::FORMAT_JSON, 'application/xml' => Response::FORMAT_XML, ], 'languages' => [ 'en-US', 'de', ], ], ]; }
在應用主體生命周期過程中檢測響應格式和語言簡單很多, 因此ContentNegotiator設計可被引導啟動組件調用的過濾器。 如下例所示可以將它配置在應用主體配置。
use yii\filters\ContentNegotiator; use yii\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 和 Etag HTTP頭實現客戶端緩存。例如:
use yii\filters\HttpCache; public function behaviors() { 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表的記錄數發生變化。它也會根據不同應用語言保存不同的頁面版本。
use yii\filters\PageCache; use yii\caching\DbDependency; public function behaviors() { return [ 'pageCache' => [ 'class' => PageCache::className(), 'only' => ['index'], 'duration' => 60, 'dependency' => [ 'class' => DbDependency::className(), 'sql' => 'SELECT COUNT(*) FROM post', ], 'variations' => [ \Yii::$app->language, ] ], ]; }
6.yii\filters\RateLimiter
RateLimiter 根據 漏桶算法 來實現速率限制。
7.yii\filters\VerbFilter
VerbFilter檢查請求動作的HTTP請求方式是否允許執行,如果不允許,會拋出HTTP 405異常。 如下示例,VerbFilter指定CRUD動作所允許的請求方式。
use yii\filters\VerbFilter; public function behaviors() { 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's AJAX 調用可使用 XMLHttpRequest 機制,由於同源安全策略該跨域請求會被網頁浏覽器禁止. CORS定義浏覽器和服務器交互時哪些跨域請求允許和禁止。
yii\filters\Cors 應在 授權 / 認證 過濾器之前定義,以保證CORS頭部被發送。
use yii\filters\Cors; use yii\helpers\ArrayHelper; public function behaviors() { return ArrayHelper::merge([ [ 'class' => Cors::className(), ], ], parent::behaviors()); }
Cors 可轉為使用 cors 屬性。
例如,允許來源為 http://www.myserver.net 和方式為 GET, HEAD 和 OPTIONS 的CORS如下:
use yii\filters\Cors; use yii\helpers\ArrayHelper; public function behaviors() { return ArrayHelper::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參數如下所示:
use yii\filters\Cors; use yii\helpers\ArrayHelper; public function behaviors() { return ArrayHelper::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()); }