Commit 45cb8856 by Yang Liu

Merge pull request #28 from funson86/master

Translated structure-filters.md and structure-widgets.md. [skip ci]
parents 469c9688 e7e00bc9
过滤器
=======
过滤器是 [控制器 动作](structure-controllers.md#actions) 执行之前或之后执行的对象。
例如访问控制过滤器可在动作执行之前来控制特殊终端用户是否有权限执行动作,
内容压缩过滤器可在动作执行之后发给终端用户之前压缩响应内容。
过滤器可包含 预过滤(过滤逻辑在动作*之前*) 或 后过滤(过滤逻辑在动作*之后*),也可同时包含两者。
## 使用过滤器 <a name="using-filters"></a>
过滤器本质上是一类特殊的 [行为](concept-behaviors.md),所以使用过滤器和 [使用 行为](concept-behaviors.md#attaching-behaviors)一样。
可以在控制器类中覆盖它的 [[yii\base\Controller::behaviors()|behaviors()]] 方法来申明过滤器,如下所示:
```php
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|only]]属性明确指定控制器应用到哪些动作。
在上述例子中,`HttpCache` 过滤器只应用到`index``view`动作。
也可以配置[[yii\base\ActionFilter::except|except]]属性使一些动作不执行过滤器。
除了控制器外,可在 [模块](structure-modules.md)[应用主体](structure-applications.md) 中申明过滤器。
申明之后,过滤器会应用到所属该模块或应用主体的 *所有* 控制器动作,
除非像上述一样配置过滤器的 [[yii\base\ActionFilter::only|only]] 和 [[yii\base\ActionFilter::except|except]] 属性。
> 补充: 在模块或应用主体中申明过滤器,在[[yii\base\ActionFilter::only|only]] 和 [[yii\base\ActionFilter::except|except]]
属性中使用[路由](structure-controllers.md#routes) 代替动作ID,
因为在模块或应用主体中只用动作ID并不能唯一指定到具体动作。.
当一个动作有多个过滤器时,根据以下规则先后执行:
* 预过滤
- 按顺序执行应用主体中`behaviors()`列出的过滤器。
- 按顺序执行模块中`behaviors()`列出的过滤器。
- 按顺序执行控制器中`behaviors()`列出的过滤器。
- 如果任意过滤器终止动作执行,后面的过滤器(包括预过滤和后过滤)不再执行。
* 成功通过预过滤后执行动作。
* 后过滤
- 倒序执行控制器中`behaviors()`列出的过滤器。
- 倒序执行模块中`behaviors()`列出的过滤器。
- 倒序执行应用主体中`behaviors()`列出的过滤器。
## 创建过滤器 <a name="creating-filters"></a>
继承 [[yii\base\ActionFilter]] 类并覆盖
[[yii\base\ActionFilter::beforeAction()|beforeAction()]] 和/或 [[yii\base\ActionFilter::afterAction()|afterAction()]]
方法来创建动作的过滤器,前者在动作执行之前执行,后者在动作执行之后执行。
[[yii\base\ActionFilter::beforeAction()|beforeAction()]] 返回值决定动作是否应该执行,
如果为false,之后的过滤器和动作不会继续执行。
下面的例子申明一个记录动作执行时间日志的过滤器。
```php
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);
}
}
```
## 核心过滤器 <a name="core-filters"></a>
Yii提供了一组常用过滤器,在`yii\filters`命名空间下,接下来我们简要介绍这些过滤器。
### [[yii\filters\AccessControl|AccessControl]] <a name="access-control"></a>
AccessControl提供基于[[yii\filters\AccessControl::rules|rules]]规则的访问控制。
特别是在动作执行之前,访问控制会检测所有规则并找到第一个符合上下文的变量(比如用户IP地址、登录状态等等)的规则,
来决定允许还是拒绝请求动作的执行,如果没有规则符合,访问就会被拒绝。
如下示例表示表示允许已认证用户访问`create``update` 动作,拒绝其他用户访问这两个动作。
```php
use yii\filters\AccessControl;
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['create', 'update'],
'rules' => [
// 允许认证用户
[
'allow' => true,
'roles' => ['@'],
],
// 默认禁止其他用户
],
],
];
}
```
更多关于访问控制的详情请参阅 [授权](security-authorization.md) 一节。
### 认证方法过滤器 <a name="auth-method-filters"></a>
认证方法过滤器通过[HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication)[OAuth 2](http://oauth.net/2/)
来认证一个用户,认证方法过滤器类在 `yii\filters\auth` 命名空间下。
如下示例表示可使用[[yii\filters\auth\HttpBasicAuth]]来认证一个用户,它使用基于HTTP基础认证方法的令牌。
注意为了可运行,[[yii\web\User::identityClass|user identity class]] 类必须
实现 [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]方法。
```php
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
return [
'basicAuth' => [
'class' => HttpBasicAuth::className(),
],
];
}
```
认证方法过滤器通常在实现RESTful API中使用,更多关于访问控制的详情请参阅 RESTful [认证](rest-authentication.md) 一节。
### [[yii\filters\ContentNegotiator|ContentNegotiator]] <a name="content-negotiator"></a>
ContentNegotiator支持响应内容格式处理和语言处理。
通过检查 `GET` 参数和 `Accept` HTTP头部来决定响应内容格式和语言。
如下示例,配置ContentNegotiator支持JSON和XML响应格式和英语(美国)和德语。
```php
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',
],
],
];
}
```
[应用主体生命周期](structure-applications.md#application-lifecycle)过程中检测响应格式和语言简单很多,
因此ContentNegotiator设计可被[引导启动组件](structure-applications.md#bootstrap)调用的过滤器。
如下例所示可以将它配置在[应用主体配置](structure-applications.md#application-configurations)
```php
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]] <a name="http-cache"></a>
HttpCache利用`Last-Modified``Etag` HTTP头实现客户端缓存。例如:
```php
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 缓存](caching-http.md) 一节。
### [[yii\filters\PageCache|PageCache]] <a name="page-cache"></a>
PageCache实现服务器端整个页面的缓存。如下示例所示,PageCache应用在`index`动作,
缓存整个页面60秒或`post`表的记录数发生变化。它也会根据不同应用语言保存不同的页面版本。
```php
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详情请参阅 [页面缓存](caching-page.md) 一节。
### [[yii\filters\RateLimiter|RateLimiter]] <a name="rate-limiter"></a>
RateLimiter 根据 [漏桶算法](http://en.wikipedia.org/wiki/Leaky_bucket) 来实现速率限制。
主要用在实现RESTful APIs,更多关于该过滤器详情请参阅 [Rate Limiting](rest-rate-limiting.md) 一节。
### [[yii\filters\VerbFilter|VerbFilter]] <a name="verb-filter"></a>
VerbFilter检查请求动作的HTTP请求方式是否允许执行,如果不允许,会抛出HTTP 405异常。
如下示例,VerbFilter指定CRUD动作所允许的请求方式。
```php
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]] <a name="cors"></a>
跨域资源共享 [CORS](https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS) 机制允许一个网页的许多资源(例如字体、JavaScript等)
这些资源可以通过其他域名访问获取。
特别是JavaScript's AJAX 调用可使用 XMLHttpRequest 机制,由于同源安全策略该跨域请求会被网页浏览器禁止.
CORS定义浏览器和服务器交互时哪些跨域请求允许和禁止。
[[yii\filters\Cors|Cors filter]] 应在 授权 / 认证 过滤器之前定义,以保证CORS头部被发送。
```php
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']`: 定义当前请求是否使用证书,可为 `true`, `false``null` (不设置). 默认为 `null`.
* `cors['Access-Control-Max-Age']`: 定义请求的有效时间,默认为 `86400`.
例如,允许来源为 `http://www.myserver.net` 和方式为 `GET`, `HEAD``OPTIONS` 的CORS如下:
```php
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`参数如下所示:
```php
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());
}
```
小部件
=======
小部件是在 [视图](structure-views.md) 中使用的可重用单元,使用面向对象方式创建复杂和可配置用户界面单元。
例如,日期选择器小部件可生成一个精致的允许用户选择日期的日期选择器,
你只需要在视图中插入如下代码:
```php
<?php
use yii\jui\DatePicker;
?>
<?= DatePicker::widget(['name' => 'date']) ?>
```
Yii提供许多优秀的小部件,比如[[yii\widgets\ActiveForm|active form]], [yii\widgets\Menu|menu]],
[jQuery UI widgets](widget-jui.md), [Twitter Bootstrap widgets](widget-bootstrap.md)
接下来介绍小部件的基本知识,如果你想了解某个小部件请参考对应的类API文档。
## 使用小部件 <a name="using-widgets"></a>
小部件基本上在[views](structure-views.md)中使用,在视图中可调用 [[yii\base\Widget::widget()]] 方法使用小部件。
该方法使用 [配置](concept-configurations.md) 数组初始化小部件并返回小部件渲染后的结果。
例如如下代码插入一个日期选择器小部件,它配置为使用俄罗斯语,输入框内容为`$model``from_date`属性值。
```php
<?php
use yii\jui\DatePicker;
?>
<?= DatePicker::widget([
'model' => $model,
'attribute' => 'from_date',
'language' => 'ru',
'clientOptions' => [
'dateFormat' => 'yy-mm-dd',
],
]) ?>
```
一些小部件可在[[yii\base\Widget::begin()]] 和 [[yii\base\Widget::end()]] 调用中使用数据内容。Some widgets can take a block of content which should be enclosed between the invocation of
例如如下代码使用[[yii\widgets\ActiveForm]]小部件生成一个登录表单,
小部件会在`begin()` 和0 `end()`执行处分别生成`<form>`的开始标签和结束标签,中间的任何代码也会被渲染。
```php
<?php
use yii\widgets\ActiveForm;
use yii\helpers\Html;
?>
<?php $form = ActiveForm::begin(['id' => 'login-form']); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Login') ?>
</div>
<?php ActiveForm::end(); ?>
```
注意和调用 [[yii\base\Widget::widget()]] 返回渲染结果不同,
调用 [[yii\base\Widget::begin()]] 方法返回一个可组建小部件内容的小部件实例。
## 创建小部件 <a name="creating-widgets"></a>
## Creating Widgets <a name="creating-widgets"></a>
继承 [[yii\base\Widget]] 类并覆盖 [[yii\base\Widget::init()]] 和/或
[[yii\base\Widget::run()]] 方法可创建小部件。通常`init()` 方法处理小部件属性,
`run()` 方法包含小部件生成渲染结果的代码。
渲染结果可在`run()`方法中直接"echoed"输出或以字符串返回。
如下代码中`HelloWidget`编码并显示赋给`message` 属性的值,
如果属性没有被赋值,默认会显示"Hello World"。
```php
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
class HelloWidget extends Widget
{
public $message;
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = 'Hello World';
}
}
public function run()
{
return Html::encode($this->message);
}
}
```
使用这个小部件只需在视图中简单使用如下代码:
```php
<?php
use app\components\HelloWidget;
?>
<?= HelloWidget::widget(['message' => 'Good morning']) ?>
```
以下是另一种可在`begin()``end()`调用中使用的`HelloWidget`,HTML编码内容然后显示。
```php
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
class HelloWidget extends Widget
{
public function init()
{
parent::init();
ob_start();
}
public function run()
{
$content = ob_get_clean();
return Html::encode($content);
}
}
```
如上所示,PHP输出缓冲在`init()`启动,所有在`init()``run()`方法之间的输出内容都会被获取,并在`run()`处理和返回。
> 补充: 当你调用 [[yii\base\Widget::begin()]] 时会创建一个新的小部件实例并在构造结束时调用`init()`方法,
`end()`时会调用`run()`方法并输出返回结果。
如下代码显示如何使用这种 `HelloWidget`:
```php
<?php
use app\components\HelloWidget;
?>
<?php HelloWidget::begin(); ?>
content that may contain <tag>'s
<?php HelloWidget::end(); ?>
```
有时小部件需要渲染很多内容,一种更好的办法是将内容放入一个[视图](structure-views.md)文件,
然后调用[[yii\base\Widget::render()]]方法渲染该视图文件,例如:
```php
public function run()
{
return $this->render('hello');
}
```
小部件的视图文件默认存储在`WidgetPath/views`目录,`WidgetPath`代表小部件类文件所在的目录。
假如上述示例小部件类文件在`@app/components`下,会渲染`@app/components/views/hello.php`视图文件。 You may override
可以覆盖[[yii\base\Widget::getViewPath()]]方法自定义视图文件所在路径。
## 最佳实践 <a name="best-practices"></a>
小部件是面向对象方式来重用视图代码。
创建小部件时仍需要遵循MVC模式,通常逻辑代码在小部件类,展示内容在[视图](structure-views.md)中。
小部件设计时应是独立的,也就是说使用一个小部件时候,可以直接丢弃它而不需要额外的处理。
但是当小部件需要外部资源如CSS, JavaScript, 图片等会比较棘手,
幸运的时候Yii提供 [资源包](structure-asset-bundles.md) 来解决这个问题。
当一个小部件只包含视图代码,它和[视图](structure-views.md)很相似,
实际上,在这种情况下,唯一的区别是小部件是可以重用类,视图只是应用中使用的普通PHP脚本。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment