创建过滤器

过滤器是一个类,它可以在动作之前或者之后执行。它可以被用于修改执行上下文,或者装饰输出。在我们的例子中,我们将会实现一个简单的访问过滤器,它允许用户只能在接受了用户协议之后才能看到私有的内容。

准备

按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的yii2-app-basic应用。

如何做…

  1. 创建协议表单模型:
  1. <?php
  2. namespace app\models;
  3. use yii\base\Model;
  4. class AgreementForm extends Model
  5. {
  6. public $accept;
  7. public function rules()
  8. {
  9. return [
  10. ['accept', 'required'],
  11. ['accept', 'compare', 'compareValue' => 1,
  12. 'message' => 'You must agree the rules.'],
  13. ];
  14. }
  15. public function attributeLabels()
  16. {
  17. return [
  18. 'accept' => 'I completely accept the rules.'
  19. ];
  20. }
  21. }
  1. 创建协议检查服务:
  1. <?php
  2. namespace app\services;
  3. use Yii;
  4. use yii\web\Cookie;
  5. class AgreementChecker
  6. {
  7. public function isAllowed()
  8. {
  9. return Yii::$app->request->cookies->has('agree');
  10. }
  11. public function allowAccess()
  12. {
  13. Yii::$app->response->cookies->add(new Cookie([
  14. 'name' => 'agree',
  15. 'value' => 'on',
  16. 'expire' => time() + 3600 * 24 * 90, // 90 days
  17. ]));
  18. }
  19. }

它使用了协议cookies进行了封装。

  1. 创建filter类:
  1. <?php
  2. namespace app\filters;
  3. use app\services\AgreementChecker;
  4. use Yii;
  5. use yii\base\ActionFilter;
  6. class AgreementFilter extends ActionFilter
  7. {
  8. public function beforeAction($action)
  9. {
  10. $checker = new AgreementChecker();
  11. if (!$checker->isAllowed()) {
  12. Yii::$app->response->redirect(['/content/agreement'])->send();
  13. return false;
  14. }
  15. return true;
  16. }
  17. }
  1. 创建内容控制器,并将过滤器附加到行为上:
  1. <?php
  2. namespace app\controllers;
  3. use app\filters\AgreementFilter;
  4. use app\models\AgreementForm;
  5. use app\services\AgreementChecker;
  6. use Yii;
  7. use yii\web\Controller;
  8. class ContentController extends Controller
  9. {
  10. public function behaviors()
  11. {
  12. return [
  13. [
  14. 'class' => AgreementFilter::className(),
  15. 'only' => ['index'],
  16. ],
  17. ];
  18. }
  19. public function actionIndex()
  20. {
  21. return $this->render('index');
  22. }
  23. public function actionAgreement()
  24. {
  25. $model = new AgreementForm();
  26. if ($model->load(Yii::$app->request->post()) &&
  27. $model->validate()) {
  28. $checker = new AgreementChecker();
  29. $checker->allowAccess();
  30. return $this->redirect(['index']);
  31. } else {
  32. return $this->render('agreement', [
  33. 'model' => $model,
  34. ]);
  35. }
  36. }
  37. }
  1. 添加私有内容到views/content/index.php
  1. <?php
  2. use yii\helpers\Html;
  3. /* @var $this yii\web\View */
  4. $this->title = 'Content';
  5. $this->params['breadcrumbs'][] = $this->title;
  6. ?>
  7. <div class="site-about">
  8. <h1><?= Html::encode($this->title) ?></h1>
  9. <div class="well">
  10. This is our private page.
  11. </div>
  12. </div>
  1. 给表单添加views/content/agreement.php视图:
  1. <?php
  2. use yii\helpers\Html;
  3. use yii\bootstrap\ActiveForm;
  4. /* @var $this yii\web\View */
  5. /* @var $form yii\bootstrap\ActiveForm */
  6. /* @var $model app\models\AgreementForm */
  7. $this->title = 'User agreement';
  8. $this->params['breadcrumbs'][] = $this->title;
  9. ?>
  10. <div class="site-login">
  11. <h1><?= Html::encode($this->title) ?></h1>
  12. <p>Please agree with our rules:</p>
  13. <?php $form = ActiveForm::begin(); ?>
  14. <?= $form->field($model, 'accept')->checkbox() ?>
  15. <div class="form-group">
  16. <?= Html::submitButton('Accept', ['class' => 'btn btn-success']) ?>
  17. <?= Html::a('Cancel', ['/site/index'], ['class' => 'btn btn-danger']) ?>
  18. </div>
  19. <?php ActiveForm::end(); ?>
  20. </div>
  1. 添加主菜单项到views/layouts/main.php
  1. echo Nav::widget([
  2. 'options' => ['class' => 'navbar-nav navbar-right'],
  3. 'items' => [
  4. ['label' => 'Home', 'url' => ['/site/index']],
  5. ['label' => 'Content', 'url' => ['/content/index']],
  6. ['label' => 'About', 'url' => ['/site/about']],
  7. //...
  8. ],
  9. ]);
  1. 尝试打开内容页。过滤器会将你重定向到协议页上:

创建过滤器 - 图1

  1. 只有在接受协议之后,你才可以看到私有内容:

创建过滤器 - 图2

  1. 此外,你可以附加这个过滤器到其他控制器或者模块上。

工作原理…

过滤器应该继承了yii\base\ActionFilter类,它继承了yii\base\Behavior。如果我们想做前过滤或者后过滤,我们可以复写beforeAction或者afterAction方法。

例如,我们可以检查用户访问,并在遇到失败情况时,抛出HTTP异常。在这个小节中,如果指定的cookie的值不存在,我们将用户重定向到协议页上。

  1. class AgreementFilter extends ActionFilter
  2. {
  3. public function beforeAction($action)
  4. {
  5. $checker = new AgreementChecker();
  6. if (!$checker->isAllowed()) {
  7. Yii::$app->response->redirect(['/content/agreement'])->send();
  8. return false;
  9. }
  10. return true;
  11. }
  12. }

你可以附加过滤器到任何控制器或者模块上。为了指定必要路由的列表,只需要使用only或者except选项。例如,我们只为控制器的index动作应用我们的过滤器:

  1. public function behaviors()
  2. {
  3. return [
  4. [
  5. 'class' => AgreementFilter::className(),
  6. 'only' => ['index'],
  7. ],
  8. ];
  9. }

注意:不要忘记,对于beforeAction方法,成功的时候返回一个true。否则,这个控制器动作将不会被执行。

参考

欲了解更多关于过滤器的信息,参考http://www.yiiframework.com/doc-2.0/guide-structurefilters.html

对于内置的缓存和访问控制过滤器,参考: