Yii2学习笔记系列15——Application Structure-Filters(应用结构之过滤器)

五 13 五月 2016

过滤器

过滤器是控制器动作执行之前或之后执行的对象,例如,访问控制过滤器在动作执行之前运行以确保只有具备权限的终端用户才能访问,内容压缩过滤器在动作执行之后发送给终端用户之前运行以压缩响应内容。

过滤器可能包含 预过滤(过滤逻辑在动作之前)或 后过滤(过滤逻辑在动作之后),也可能同时包含两者。

使用过滤器

过滤器本质上是一类特殊的行为,所以使用 过滤器 和使用 行为 一样。我们可以通过在控制器类中重写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');
            },
        ],
    ];
}

控制器类的过滤器默认应用到该类的所有action,但我们也可以通过配置yii\base\ActionFilter::only属性来明确指定控制器应用到哪些动作。在上述例子中,HttpCache过滤器只应用到到indexindex这两个动作。当然,我们也可以通过配置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,之后的过滤器和动作都不会继续执行。

如下示例代码展示了一个用来记录action执行时间的过滤器:

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命名空间下,接下来我们简要介绍一下这些过滤器。

yii\filters\AccessControl

AccessControl提供基于yii\filters\AccessControl::rules规则的访问控制,特别是动作执行之前,访问控制会检测所有规则并找到第一个符合上下文的变量(比如用户IP地址、登录状态等等)的规则,来决定是允许还是拒绝请求动作的执行,如果没有规则符合,访问就会被拒绝。如下示例表示允许已认证的用户访问createupdate动作,拒绝其他用户访问这两个动作。

use yii\filters\AccessControl;

public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::className(),
            'only' => ['create', 'update'],
            'rules' => [
                // 允许认证用户
                [
                    'allow' => true,
                    'roles' => ['@'],
                ],
                // 默认禁止其他用户
            ],
        ],
    ];
}

更多的关于访问控制的细节可以访问授权一节。

认证方法过滤器

认证方法过滤器通过各种方式来认证一个一个用户,例如HTTP Basic AuthOAuth 2.0。这些过滤器类都在命名空间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中使用,更多关于访问控制的详情请参阅RESTful认证一节。

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被设计成既能作为过滤器也能作为引导启动组件的方式。例如,我们可以在应用主体配置中这样儿配置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的第一个配置项。

yii\filters\HttpCache

HttpCache利用Last-ModifiedEtagHTTP头实现客户端缓存。例如:

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');
            },
        ],
    ];
}

更多关于HttpCache的详情请参阅HTTP缓存一节。

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,
            ]
        ],
    ];
}

更多关于PageCache的细节请参阅页面缓存一节。

yii\filters\RateLimiter

RateLimiter实现了基于漏桶算法的速率限制算法,它主要用在实现RESTful接口上。更多关于该过滤器的详情请参阅Rate Limiting一节。

yii\filters\VerbFilter

VerbFilter检查动作的HTTP请求方式是否允许执行,如果不允许,则直接抛出HTTP405错误。如下示例中,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'],
            ],
        ],
    ];
}
yii\filters\Cors

跨域资源共享CORS机制允许一个网页的许多资源(例如字体、JS脚本等)可以通过其它域名访问获取,特别是JS的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过滤器:

  • 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']:定义当前请求是否使用证书,可以为truefalsenull(不设置),默认是null
  • cors['Access-Control-Max-Age']:定义请求的有效时间,默认是86400。

例如,允许来源为http://www.myserver.net以及请求方式为GETHEADOPTIONS的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());
}

Category: PHP Develop

Comments