Commit 34893c9e by Qiang Xue

Merge pull request #4472 from yii2-chinesization/master

translations for this week
parents 37354c0c 20c565f0
......@@ -39,7 +39,7 @@ Yii 2.0 权威指南
* **已定稿** [小部件(Widget)](structure-widgets.md)
* **已定稿** [模块(Module)](structure-modules.md)
* **编撰中** [前端资源(Asset)](structure-assets.md)
* **待定中** [扩展(extensions)](structure-extensions.md)
* **已定稿** [扩展(extensions)](structure-extensions.md)
请求处理
--------
......@@ -144,16 +144,6 @@ RESTful Web 服务
* **待定中** [验收测试](test-acceptance.md)
* **编撰中** [测试夹具](test-fixtures.md)
扩展 Yii
--------
* **编撰中** [创建扩展](extend-creating-extensions.md)
* **编撰中** [定制核心代码](extend-customizing-core.md)
* **编撰中** [使用第三方库](extend-using-libs.md)
* **待定中** [在第三方系统使用 Yii](extend-embedding-in-others.md)
* **待定中** [Yii 1.1 和 2.0 共用](extend-using-v1-v2.md)
* **编撰中** [使用依赖包管理器 Composer](extend-using-composer.md)
高级专题
--------
......@@ -167,6 +157,7 @@ RESTful Web 服务
* **编撰中** [性能优化](tutorial-performance-tuning.md)
* **待定中** [共享主机环境](tutorial-shared-hosting.md)
* **编撰中** [模板引擎](tutorial-template-engines.md)
* **已定稿** [集成第三方代码](tutorial-yii-integration.md)
小部件
------
......
数据缓存
============
数据缓存是指将一些 PHP 变量存储到缓存中,使用时再从缓存中取回。它也是更高级缓存特性的基础,例如
[查询缓存](#query-caching)[分页缓存](caching-page.md).
数据缓存是指将一些 PHP 变量存储到缓存中,使用时再从缓存中取回。它也是更高级缓存特性的基础,例如[查询缓存](#query-caching)[内容缓存](caching-content.md)
如下代码是一个典型的数据缓存使用模式。其中 `$cache` 代表一个 [缓存组件](#cache-components):
如下代码是一个典型的数据缓存使用模式。其中 `$cache` 指向[缓存组件](#cache-components)
```php
// 尝试从缓存中取回 $data
......@@ -24,10 +23,9 @@ if ($data === false) {
## 缓存组件 <a name="cache-components"></a>
数据缓存需要称作“*缓存组件*”的东西提供支持,它代表着各种缓存存储器,例如内存,文件,数据库。
数据缓存需要**缓存组件**提供支持,它代表各种缓存存储器,例如内存,文件,数据库。
缓存组件通常注册为应用程序组件,这样它们就可以接受全局性配置和调用。如下代码演示了如何配置 `cache`
应用程序组件使用两个 [memcached](http://memcached.org/) 服务器:
缓存组件通常注册为应用程序组件,这样它们就可以在全局进行配置与访问。如下代码演示了如何配置应用程序组件 `cache` 使用两个 [memcached](http://memcached.org/) 服务器:
```php
'components' => [
......@@ -49,10 +47,9 @@ if ($data === false) {
],
```
然后就可以通过 `Yii::$app->cache` 访问上面的缓存组件了。
然后就可以通过 `Yii::$app->cache` 访问上面的缓存组件了。
由于所有缓存组件都支持同样的一系列 API
,你并不需要修改使用缓存的那些代码就能直接替换为其他低层缓存组件,只需在应用程序配置中重新配置一下就可以。例如,你可以将上述配置修改为使用 [[yii\caching\ApcCache|APC cache]]:
由于所有缓存组件都支持同样的一系列 API ,并不需要修改使用缓存的业务代码就能直接替换为其他底层缓存组件,只需在应用配置中重新配置一下就可以。例如,你可以将上述配置修改为使用 [[yii\caching\ApcCache|APC cache]]:
```php
......@@ -71,19 +68,14 @@ if ($data === false) {
Yii 支持一系列缓存存储器,概况如下:
* [[yii\caching\ApcCache]]: 使用 PHP [APC](http://php.net/manual/en/book.apc.php) 扩展。这个选项可以认为是集中式应用程序环境中(例如:单一服务器,没有独立的负载均衡器等)最快的缓存方案。
* [[yii\caching\DbCache]]: 使用一个数据库的表存储缓存数据。要使用这个缓存,你必须创建一个 [[yii\caching\DbCache::cacheTable]] 对应的表。
* [[yii\caching\DummyCache]]: 仅作为一个缓存占位符,不实现任何真正的缓存功能。 这个组件的目的是为了简化那些需要查询缓存有效性的代码。例如,在开发中如果服务器没有实际的缓存支持,你就可以用它配置一个缓存组件。 一个真正的缓存服务启用后,就可以切换为使用相应的缓存组件。两种条件下你都可以使用同样的代码
`Yii::$app->cache->get($key)` 尝试从缓存中取回数据而不用担心 `Yii::$app->cache` 可能是 `null`
* [[yii\caching\FileCache]]: 使用标准文件存储缓存数据。这个特别适用于缓存大块数据,例如一个网页的内容。
* [[yii\caching\MemCache]]: 使用 PHP [memcache](http://php.net/manual/en/book.memcache.php)
[memcached](http://php.net/manual/en/book.memcached.php) 扩展。这个选项可以认为是分布式应用程序环境中(例如:多台服务器,有负载均衡等)最快的缓存方案。
* [[yii\caching\DbCache]]: 使用一个数据库的表存储缓存数据。要使用这个缓存,你必须创建一个与 [[yii\caching\DbCache::cacheTable]] 对应的表。
* [[yii\caching\DummyCache]]: 仅作为一个缓存占位符,不实现任何真正的缓存功能。这个组件的目的是为了简化那些需要查询缓存有效性的代码。例如,在开发中如果服务器没有实际的缓存支持,用它配置一个缓存组件。一个真正的缓存服务启用后,可以再切换为使用相应的缓存组件。两种条件下你都可以使用同样的代码 `Yii::$app->cache->get($key)` 尝试从缓存中取回数据而不用担心 `Yii::$app->cache` 可能是 `null`
* [[yii\caching\FileCache]]: 使用标准文件存储缓存数据。这个特别适用于缓存大块数据,例如一个整页的内容。
* [[yii\caching\MemCache]]: 使用 PHP [memcache](http://php.net/manual/en/book.memcache.php)[memcached](http://php.net/manual/en/book.memcached.php) 扩展。这个选项被看作分布式应用环境中(例如:多台服务器,有负载均衡等)最快的缓存方案。
* [[yii\redis\Cache]]: 实现了一个基于 [Redis](http://redis.io/) 键值对存储器的缓存组件(需要 redis 2.6.12 及以上版本的支持 )。
* [[yii\caching\WinCache]]: 使用 PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)
([另可参考](http://php.net/manual/en/book.wincache.php)) 扩展.
* [[yii\caching\XCache]]: 使用 PHP [XCache](http://xcache.lighttpd.net/) 扩展。
* [[yii\caching\ZendDataCache]]: 使用
[Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm)
作为底层缓存介质。
* [[yii\caching\WinCache]]: 使用 PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)[另可参考](http://php.net/manual/en/book.wincache.php))扩展.
* [[yii\caching\XCache]]: 使用 PHP [XCache](http://xcache.lighttpd.net/)扩展。
* [[yii\caching\ZendDataCache]]: 使用 [Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) 作为底层缓存媒介。
> Tip: 你可以在同一个应用程序中使用不同的缓存存储器。一个常见的策略是使用基于内存的缓存存储器存储小而常用的数据(例如:统计数据),使用基于文件或数据库的缓存存储器存储大而不太常用的数据(例如:网页内容)。
......@@ -95,7 +87,7 @@ Yii 支持一系列缓存存储器,概况如下:
* [[yii\caching\Cache::get()|get()]]: 通过一个指定的键(key)从缓存中取回一项数据。如果该项数据不存在于缓存中或者已经过期/失效,则返回值 false。
* [[yii\caching\Cache::set()|set()]]: 将一项数据指定一个键,存放到缓存中。
* [[yii\caching\Cache::add()|add()]]: 将一项数据指定一个键,如果缓存中未找到该键,则将其存放到缓存中。
* [[yii\caching\Cache::add()|add()]]: 如果缓存中未找到该键,则将指定数据存放到缓存中。
* [[yii\caching\Cache::mget()|mget()]]: 通过指定的多个键从缓存中取回多项数据。
* [[yii\caching\Cache::mset()|mset()]]: 将多项数据存储到缓存中,每项数据对应一个键。
* [[yii\caching\Cache::madd()|madd()]]: 将多项数据存储到缓存中,每项数据对应一个键。如果某个键已经存在于缓存中,则该项数据会被跳过。
......@@ -103,10 +95,9 @@ Yii 支持一系列缓存存储器,概况如下:
* [[yii\caching\Cache::delete()|delete()]]: 通过一个键,删除缓存中对应的值。
* [[yii\caching\Cache::flush()|flush()]]: 删除缓存中的所有数据。
有些缓存存储器如 MemCache,APC 支持以批量模式取回缓存值,这样可以节省取回缓存数据的开支。 [[yii\caching\Cache::mget()|mget()]] 和
[[yii\caching\Cache::madd()|madd()]] API提供对该特性的支持。如果底层缓存存储器不支持该特性,Yii 也会模拟实现。
有些缓存存储器如 MemCache,APC 支持以批量模式取回缓存值,这样可以节省取回缓存数据的开支。 [[yii\caching\Cache::mget()|mget()]] 和 [[yii\caching\Cache::madd()|madd()]] API提供对该特性的支持。如果底层缓存存储器不支持该特性,Yii 也会模拟实现。
由于 [[yii\caching\Cache]] 实现了 `ArrayAccess`,缓存组件也可以像数组那样使用,下面是几个例子:
由于 [[yii\caching\Cache]] 实现了 PHP `ArrayAccess` 接口,缓存组件也可以像数组那样使用,下面是几个例子:
```php
$cache['var1'] = $value1; // 等价于: $cache->set('var1', $value1);
......@@ -116,7 +107,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2');
### 缓存键 <a name="cache-keys"></a>
存储在缓存中的每项数据都通过键作唯一识别。当你在缓存中存储一项数据时,必须为它指定一个键,稍后从缓存中取回数据时,也需要提供相应的键。
存储在缓存中的每项数据都通过键作唯一识别。当你在缓存中存储一项数据时,必须为它指定一个键,稍后从缓存中取回数据时,也需要提供相应的键。
你可以使用一个字符串或者任意值作为一个缓存键。当键不是一个字符串时,它将会自动被序列化为一个字符串。
......@@ -133,8 +124,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2');
如你所见,该键包含了可唯一指定一个数据库表所需的所有必要信息。
当同一个缓存存储器被用于多个不同的应用程序时,你应该为每个应用程序指定一个唯一的缓存键前缀以避免缓存键冲突。
这可以通过配置 [[yii\caching\Cache::keyPrefix]] 属性实现。例如,在应用程序配置中,你可以编写如下代码:
当同一个缓存存储器被用于多个不同的应用时,应该为每个应用指定一个唯一的缓存键前缀以避免缓存键冲突。可以通过配置 [[yii\caching\Cache::keyPrefix]] 属性实现。例如,在应用配置中可以编写如下代码:
```php
'components' => [
......@@ -150,9 +140,7 @@ $value2 = $cache['var2']; // 等价于: $value2 = $cache->get('var2');
### 缓存过期 <a name="cache-expiration"></a>
缓存中的一项数据会一直留在缓存中,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除)。要改变此特性,你可以在调用 [[yii\caching\Cache::set()|set()]]
存储一项数据时提供一个过期时间参数。该参数表明这项数据在缓存中可保持有效多少秒。当你调用
[[yii\caching\Cache::get()|get()]] 取回数据时,如果它已经过了超时时间,该方法将返回 false,表明在缓存中找不到这项数据。例如:
默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除)。要改变此特性,你可以在调用 [[yii\caching\Cache::set()|set()]] 存储一项数据时提供一个过期时间参数。该参数代表这项数据在缓存中可保持有效多少秒。当你调用 [[yii\caching\Cache::get()|get()]] 取回数据时,如果它已经过了超时时间,该方法将返回 false,表明在缓存中找不到这项数据。例如:
```php
// 将数据在缓存中保留 45 秒
......@@ -169,12 +157,9 @@ if ($data === false) {
### 缓存依赖 <a name="cache-dependencies"></a>
除了超时设置,缓存数据还可能受称作*缓存依赖*的影响而失效。例如,[[yii\caching\FileDependency]] 表示对一个文件修改时间的依赖。
这个依赖条件发生变化也就意味着相应的文件已经被修改。因此,缓存中任何过期的文件内容都应该被置为失效状态且对 [[yii\caching\Cache::get()|get()]]
的调用都应该返回 false。
除了超时设置,缓存数据还可能受到**缓存依赖**的影响而失效。例如,[[yii\caching\FileDependency]] 代表对一个文件修改时间的依赖。这个依赖条件发生变化也就意味着相应的文件已经被修改。因此,缓存中任何过期的文件内容都应该被置为失效状态,对 [[yii\caching\Cache::get()|get()]] 的调用都应该返回 false。
缓存依赖是以 [[yii\caching\Dependency]] 的子孙类所代表的。当你调用
[[yii\caching\Cache::set()|set()]] 在缓存中存储一项数据时,你可以同时传递一个关联的缓存依赖对象。例如:
缓存依赖用 [[yii\caching\Dependency]] 的派生类所表示。当调用 [[yii\caching\Cache::set()|set()]] 在缓存中存储一项数据时,可以同时传递一个关联的缓存依赖对象。例如:
```php
// 创建一个对 example.txt 文件修改时间的缓存依赖
......@@ -192,20 +177,19 @@ $data = $cache->get($key);
下面是可用的缓存依赖的概况:
- [[yii\caching\ChainedDependency]]: 如果链上任何一个依赖产生变化,则依赖改变。
- [[yii\caching\ChainedDependency]]: 如果依赖链上任何一个依赖产生变化,则依赖改变。
- [[yii\caching\DbDependency]]: 如果指定 SQL 语句的查询结果发生了变化,则依赖改变。
- [[yii\caching\ExpressionDependency]]: 如果指定的 PHP 表达式执行结果发生变化,则依赖改变。
- [[yii\caching\FileDependency]]: 如果文件的最后修改时间发生变化,则依赖改变。
- [[yii\caching\TagDependency]]: 为一项缓存数据添加一个或多个标签。你可以通过调用 [[yii\caching\TagDependency::invalidate()]]
一次性将具有指定标签的缓存数据全部置为失效状态。
一次性将具有指定标签的缓存数据全部置为失效状态。
## 查询缓存 <a name="query-caching"></a>
查询缓存是一个建立在数据缓存之上的特殊缓存特性。它用于缓存数据库查询的结果。
查询缓存需要一个 [[yii\db\Connection|数据库连接]] 和一个有效的 `cache` 应用程序组件。
查询缓存的基本用法如下,假设 `$db` 是一个 [[yii\db\Connection]] 实例:
查询缓存需要一个 [[yii\db\Connection|数据库连接]] 和一个有效的 `cache` 应用组件。查询缓存的基本用法如下,假设 `$db` 是一个 [[yii\db\Connection]] 实例:
```php
$duration = 60; // 缓存查询结果60秒
......@@ -218,22 +202,19 @@ $db->beginCache($duration, $dependency);
$db->endCache();
```
如你所见,`beginCache()``endCache()` 中间的任何查询结果都会被缓存起来。
如果缓存中找到了同样查询的结果,则查询会被跳过,直接从缓存中提取结果。
如你所见,`beginCache()``endCache()` 中间的任何查询结果都会被缓存起来。如果缓存中找到了同样查询的结果,则查询会被跳过,直接从缓存中提取结果。
查询缓存可以用于 [DAO](db-dao.md)[ActiveRecord](db-active-record.md)
查询缓存可以用于 [ActiveRecord](db-active-record.md)[DAO](db-dao.md)
> Info: 有些 DBMS (例如:[MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))
也支持数据库服务器端的查询缓存。你可以选择使用任一查询缓存机制。上文所述的查询缓存的好处在于你可以指定更灵活的缓存依赖因此可能更加高效。
> Info: 有些 DBMS (例如:[MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))也支持数据库服务器端的查询缓存。你可以选择使用任一查询缓存机制。上文所述的查询缓存的好处在于你可以指定更灵活的缓存依赖因此可能更加高效。
### 配置 <a name="query-caching-configs"></a>
查询缓存有两个通过 [[yii\db\Connection]] 设置的配置项:
查询缓存有两个通过 [[yii\db\Connection]] 设置的配置项
* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: 查询结果在缓存中的有效期,以秒表示。如果在调用
[[yii\db\Connection::beginCache()]] 时传递了一个显式的时值参数,则配置中的有效期时值会被覆盖。
* [[yii\db\Connection::queryCache|queryCache]]: 缓存应用程序组件的 ID。默认为 `'cache'`。只有在设置了一个有效的缓存应用程序组件时,查询缓存才会有效。
* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: 查询结果在缓存中的有效期,以秒表示。如果在调用 [[yii\db\Connection::beginCache()]] 时传递了一个显式的时值参数,则配置中的有效期时值会被覆盖。
* [[yii\db\Connection::queryCache|queryCache]]: 缓存应用组件的 ID。默认为 `'cache'`。只有在设置了一个有效的缓存应用组件时,查询缓存才会有效。
### 限制条件 <a name="query-caching-limitations"></a>
......
片段缓存
================
片段缓存指的是缓存页面内容中的某个片段。例如,一个页面显示了逐年销售额的摘要表格,可以把表格缓存下来,以消除每次请求都要重新生成表格的耗时。片段缓存是基于[数据缓存](caching-data.md)实现的。
[视图](structure-views.md)中使用以下结构启用片段缓存:
```php
if ($this->beginCache($id)) {
// ... 在此生成内容 ...
$this->endCache();
}
```
调用 [[yii\base\View::beginCache()|beginCache()]] 和 [[yii\base\View::endCache()|endcache()]] 方法包裹内容生成逻辑。如果缓存中存在该内容,[[yii\base\View::beginCache()|beginCache()]] 方法将渲染内容并返回 false,因此将跳过内容生成逻辑。否则,内容生成逻辑被执行,一直执行到 [[yii\base\View::endCache()|endCache()]] 时,生成的内容将被捕获并存储在缓存中。
[[数据缓存]](caching-data.md)一样,每个片段缓存也需要全局唯一的 `$id` 标记。
## 缓存选项 <a name="caching-options"></a>
如果要为片段缓存指定额外配置项,请通过向 [[yii\base\View::beginCache()|beginCache()]] 方法第二个参数传递配置数组。在框架内部,该数组将被用来配置一个 [[yii\widget\FragmentCache]] 小部件用以实现片段缓存功能。
### 过期时间(duration) <a name="duration"></a>
或许片段缓存中最常用的一个配置选项就是 [[yii\widgets\FragmentCache::duration|duration]] 了。它指定了内容被缓存的秒数。以下代码缓存内容最多一小时:
```php
if ($this->beginCache($id, ['duration' => 3600])) {
// ... 在此生成内容 ...
$this->endCache();
}
```
如果该选项未设置,则默认为 0,永不过期。
### 依赖 <a name="dependencies"></a>
[[数据缓存]](caching-data.md)一样,片段缓存的内容一样可以设置缓存依赖。例如一段被缓存的文章,是否重新缓存取决于它是否被修改过。
通过设置 [[yii\widgets\FragmentCache::dependency|dependency]] 选项来指定依赖,该选项的值可以是一个 [[yii\caching\Dependency]] 类的派生类,也可以是创建缓存对象的配置数组。以下代码指定了一个片段缓存,它依赖于 `update_at` 字段是否被更改过的。
```php
$dependency = [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(updated_at) FROM post',
];
if ($this->beginCache($id, ['dependency' => $dependency])) {
// ... 在此生成内容 ...
$this->endCache();
}
```
### 变化 <a name="variations"></a>
缓存的内容可能需要根据一些参数的更改而变化。例如一个 Web 应用支持多语言,同一段视图代码也许需要生成多个语言的内容。因此可以设置缓存根据应用当前语言而变化。
通过设置 [[yii\widgets\FragmentCache::variations|variations]] 选项来指定变化,该选项的值应该是一个标量,每个标量代表不同的变化系数。例如设置缓存根据当前语言而变化可以用以下代码:
```php
if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) {
// ... 在此生成内容 ...
$this->endCache();
}
```
### 开关 <a name="toggling-caching"></a>
有时你可能只想在特定条件下开启片段缓存。例如,一个显示表单的页面,可能只需要在初次请求时缓存表单(通过 GET 请求)。随后请求所显示(通过 POST 请求)的表单不该使用缓存,因为此时表单中可能包含用户输入内容。鉴于此种情况,可以使用 [[yii\widgets\FragmentCache::enabled|enabled]] 选项来指定缓存开关,如下所示:
```php
if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) {
// ... 在此生成内容 ...
$this->endCache();
}
```
## 缓存嵌套 <a name="nested-caching"></a>
片段缓存可以被嵌套使用。一个片段缓存可以被另一个包裹。例如,评论被缓存在里层,同时整个评论的片段又被缓存在外层的文章中。以下代码展示了片段缓存的嵌套使用:
```php
if ($this->beginCache($id1)) {
// ...在此生成内容...
if ($this->beginCache($id2, $options2)) {
// ...在此生成内容...
$this->endCache();
}
// ...在此生成内容...
$this->endCache();
}
```
可以为嵌套的缓存设置不同的配置项。例如,内层缓存和外层缓存使用不同的过期时间。甚至当外层缓存的数据过期失效了,内层缓存仍然可能提供有效的片段缓存数据。但是,反之则不然。如果外层片段缓存没有过期而被视为有效,此时即使内层片段缓存已经失效,它也将继续提供同样的缓存副本。因此,你必须谨慎处理缓存嵌套中的过期时间和依赖,否则外层的片段很有可能返回的是不符合你预期的失效数据。
> 译者注:外层的失效时间应该短于内层,外层的依赖条件应该低于内层,以确保最小的片段,返回的是最新的数据。
## 动态内容 <a name="dynamic-content"></a>
使用片段缓存时,可能会遇到一大段较为静态的内容中有少许动态内容的情况。例如,一个显示着菜单栏和当前用户名的页面头部。还有一种可能是缓存的内容可能包含每次请求都需要执行的 PHP 代码(例如注册资源包的代码)。这两个问题都可以使用**动态内容**功能解决。
动态内容的意思是这部分输出的内容不该被缓存,即便是它被包裹在片段缓存中。为了使内容保持动态,每次请求都执行 PHP 代码生成,即使这些代码已经被缓存了。
可以在片段缓存中调用 [[yii\base\View::renderDynamic()]] 去插入动态内容,如下所示:
```php
if ($this->beginCache($id1)) {
// ...在此生成内容...
echo $this->renderDynamic('return Yii::$app->user->identity->name;');
// ...在此生成内容...
$this->endCache();
}
```
[[yii\base\View::renderDynamic()|renderDynamic()]] 方法接受一段 PHP 代码作为参数。代码的返回值被看作是动态内容。这段代码将在每次请求时都执行,无论其外层的片段缓存是否被存储。
HTTP 缓存
============
除了前面章节讲到的服务器端缓存外, Web 应用还可以利用客户端缓存去节省相同页面内容的生成和传输时间。
通过配置 [[yii\filters\HttpCache]] 过滤器,控制器操作渲染的内容就能缓存在客户端。[[yii\filters\HttpCache|HttpCache]] 过滤器仅对 `GET``HEAD` 请求生效,它能为这些请求设置三种与缓存有关的 HTTP 头。
* [[yii\filters\HttpCache::lastModified|Last-Modified]]
* [[yii\filters\HttpCache::etagSeed|Etag]]
* [[yii\filters\HttpCache::cacheControlHeader|Cache-Control]]
## `Last-Modified` 头 <a name="last-modified"></a>
`Last-Modified` 头使用时间戳标明页面自上次客户端缓存后是否被修改过。
通过配置 [[yii\filters\HttpCache::lastModified]] 属性向客户端发送 `Last-Modified` 头。该属性的值应该为 PHP callable 类型,返回的是页面修改时的 Unix 时间戳。该 callable 的参数和返回值应该如下:
```php
/**
* @param Action $action 当前处理的操作对象
* @param array $params “params” 属性的值
* @return integer 页面修改时的 Unix 时间戳
*/
function ($action, $params)
```
以下是使用 `Last-Modified` 头的示例:
```php
public function behaviors()
{
return [
[
'class' => 'yii\filters\HttpCache',
'only' => ['index'],
'lastModified' => function ($action, $params) {
$q = new \yii\db\Query();
return $q->from('post')->max('updated_at');
},
],
];
}
```
上述代码表明 HTTP 缓存只在 `index` 操作时启用。它会基于页面最后修改时间生成一个 `Last-Modified` HTTP 头。当浏览器第一次访问 `index` 页时,服务器将会生成页面并发送至客户端浏览器。之后客户端浏览器在页面没被修改期间访问该页,服务器将不会重新生成页面,浏览器会使用之前客户端缓存下来的内容。因此服务端渲染和内容传输都将省去。
## `ETag` 头 <a name="etag"></a>
“Entity Tag”(实体标签,简称 ETag)使用一个哈希值表示页面内容。如果页面被修改过,哈希值也会随之改变。通过对比客户端的哈希值和服务器端生成的哈希值,浏览器就能判断页面是否被修改过,进而决定是否应该重新传输内容。
通过配置 [[yii\filters\HttpCache::etagSeed]] 属性向客户端发送 `ETag` 头。该属性的值应该为 PHP callable 类型,返回的是一段种子字符用来生成 ETag 哈希值。该 callable 的参数和返回值应该如下:
```php
/**
* @param Action $action 当前处理的操作对象
* @param array $params “params” 属性的值
* @return string 一段种子字符用来生成 ETag 哈希值
*/
function ($action, $params)
```
以下是使用 `ETag` 头的示例:
```php
public function behaviors()
{
return [
[
'class' => 'yii\filters\HttpCache',
'only' => ['view'],
'etagSeed' => function ($action, $params) {
$post = $this->findModel(\Yii::$app->request->get('id'));
return serialize([$post->title, $post->content]);
},
],
];
}
```
上述代码表明 HTTP 缓存只在 `view` 操作时启用。它会基于用户请求的标题和内容生成一个 `ETag` HTTP 头。当浏览器第一次访问 `view` 页时,服务器将会生成页面并发送至客户端浏览器。之后客户端浏览器标题和内容没被修改在期间访问该页,服务器将不会重新生成页面,浏览器会使用之前客户端缓存下来的内容。因此服务端渲染和内容传输都将省去。
ETag 相比 `Last-Modified` 能实现更复杂和更精确的缓存策略。例如,当站点切换到另一个主题时可以使 ETag 失效。
复杂的 Etag 生成种子可能会违背使用 `HttpCache` 的初衷而引起不必要的性能开销,因为响应每一次请求都需要重新计算 Etag。请试着找出一个最简单的表达式去触发 Etag 失效。
> 注意:为了遵循 [RFC 2616, section 13.3.4(HTTP 协议)](http://tools.ietf.org/html/rfc2616#section-13.3.4),如果同时配置了 `ETag` 和 `Last-Modified` 头,`HttpCache` 将会同时发送它们,因此它们将被同时用于客户端的缓存失效校验。
## `Cache-Control` 头 <a name="cache-control"></a>
`Cache-Control` 头指定了页面的常规缓存策略。可以通过配置 [[yii\filters\HttpCache::cacheControlHeader]] 属性发送相应的头信息。默认发送以下头:
```
Cache-Control: public, max-age=3600
```
## 会话缓存限制器 <a name="session-cache-limiter"></a>
当页面使 session 时,PHP 将会按照 PHP.INI 中所设置的 `session.cache_limiter` 值自动发送一些缓存相关的 HTTP 头。这些 HTTP 头有可能会干扰你原本设置的 `HttpCache` 或让其失效。为了避免此问题,默认情况下 `HttpCache` 禁止自动发送这些头。想改变这一行为,可以配置 [[yii\filters\HttpCache::sessionCacheLimiter]] 属性。该属性接受一个字符串值,包括 `public``private``private_no_expire`,和 `nocache`。请参考 PHP 手册中的[缓存限制器](http://www.php.net/manual/en/function.session-cache-limiter.php)了解这些值的含义。
## SEO 影响 <a name="seo-implications"></a>
搜索引擎趋向于遵循站点的缓存头。因为一些爬虫的抓取频率有限制,启用缓存头可以可以减少重复请求数量,增加爬虫抓取效率(译者:大意如此,但搜索引擎的排名规则不了解,好的缓存策略应该是可以为用户体验加分的)。
缓存
=======
缓存是提升 Web 应用性能的简便有效的方式。通过将相对静态的数据存储到缓存并在收到请求时取回缓存,
应用程序便节省了每次都要重新生成这些数据所需的时间。
缓存是提升 Web 应用性能简便有效的方式。通过将相对静态的数据存储到缓存并在收到请求时取回缓存,应用程序便节省了每次重新生成这些数据所需的时间。
缓存可以发生在 Web 应用程序的任何层级任何位置。在服务器端,在较的低层面,缓存可能用于存储基础数据,
例如从数据库中取出的最新文章列表;在较高的层面,缓存可能用于存储一段或整个 Web 页面,例如最新文章
的渲染结果。在客户端,HTTP 缓存可能用于将最近访问的页面内容存储到浏览器缓存中。
缓存可以应用在 Web 应用程序的任何层级任何位置。在服务器端,在较的低层面,缓存可能用于存储基础数据,例如从数据库中取出的最新文章列表;在较高的层面,缓存可能用于存储一段或整个 Web 页面,例如最新文章的渲染结果。在客户端,HTTP 缓存可能用于将最近访问的页面内容存储到浏览器缓存中。
Yii 支持如上所有缓存机制:
......
页面缓存
============
页面缓存指的是在服务器端缓存整个页面的内容。随后当同一个页面被请求时,内容将从缓存中取出,而不是重新生成。
页面缓存由 [[yii\filters\PageCache]] 类提供支持,该类是一个[过滤器](structure-filters.md)。它可以像这样在控制器类中使用:
```php
public function behaviors()
{
return [
[
'class' => 'yii\filters\PageCache',
'only' => ['index'],
'duration' => 60,
'variations' => [
\Yii::$app->language,
],
'dependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT COUNT(*) FROM post',
],
],
];
}
```
上述代码表示页面缓存只在 `index` 操作时启用,页面内容最多被缓存 60 秒,会随着当前应用的语言更改而变化。如果文章总数发生变化则缓存的页面会失效。
如你所见,页面缓存和[片段缓存](caching-fragment.md)极其相似。它们都支持 `duration``dependencies``variations``enabled` 配置选项。它们的主要区别是页面缓存是由[过滤器](structure-filters.md)实现,而片段缓存则是一个[小部件](structure-widgets.md)
你可以在使用页面缓存的同时,使用[片段缓存](caching-fragment.md)[动态内容](caching-fragment.md#dynamic-content)
别名(Aliases)
=======
别名(译者注:指路径/URL 别名,简称别名)用作代表文件路径和 URL,主要为了避免在代码中硬编码一些绝对路径和
URL。一个别名必须以 `@` 字符开头,以区别于传统的文件/目录路径或 URL。举栗,别名 `@yii`
指的是 Yii 框架本身的安装目录,而 `@web` 表示的是当前运行应用的根 URL(base URL)。
别名用来表示文件路径和 URL,这样就避免了在代码中硬编码一些绝对路径和 URL。一个别名必须以 `@` 字符开头,以区别于传统的文件路径和 URL。Yii 预定义了大量可用的别名。例如,别名 `@yii` 指的是 Yii 框架本身的安装目录,而 `@web` 表示的是当前运行应用的根 URL。
定义别名 <a name="defining-aliases"></a>
----------------
你可以调用 [[Yii::setAlias()]] 来给指定路径/URL 定义别名。栗子
你可以调用 [[Yii::setAlias()]] 来给文件路径或 URL 定义别名
```php
// 文件路径的别名
......@@ -19,22 +17,17 @@ Yii::setAlias('@foo', '/path/to/foo');
Yii::setAlias('@bar', 'http://www.example.com');
```
> 注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源
> 注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。
用一个别名,你能通过在后面接续斜杠 `/` 以及若干路径片段得到一个新的别名(无需调用
[[Yii::setAlias()]])。我们把通过 [[Yii::setAlias()]] 定义的别名成为根别名
*root aliases*,而用他们衍生出去的别名成为衍生别名 *derived aliases*。比如,`@foo` 就是跟别名,而 `@foo/bar/file.php`
是一个衍生别名。
可以通过在一个别名后面加斜杠 `/` 和一至多个路径分段生成新别名(无需调用 [[Yii::setAlias()]])。我们把通过 [[Yii::setAlias()]] 定义的别名称为**根别名**,而用他们衍生出去的别名成为**衍生别名**。例如,`@foo` 就是跟别名,而 `@foo/bar/file.php` 是一个衍生别名。
你还可以用别名定义新别名(根别名与衍生别名均可):
你还可以用别名定义新别名(根别名与衍生别名均可):
```php
Yii::setAlias('@foobar', '@foo/bar');
```
根别名通常在 [引导(bootstrapping)](runtime-bootstrapping.md) 阶段定义。比如你可以在
[入口脚本](structure-entry-scripts.md) 里调用 [[Yii::setAlias()]]。为了方便起见呢,[应用主体(Application)](structure-applications.md)
提供了一个名为 `aliases` 的可写属性,你可以在应用[配置文件](concept-configurations.md)中设置它,就像这样:
根别名通常在[引导](runtime-bootstrapping.md)阶段定义。比如你可以在[入口脚本](structure-entry-scripts.md)里调用 [[Yii::setAlias()]]。为了方便起见,[应用](structure-applications.md)提供了一个名为 `aliases` 的可写属性,你可以在应用[配置](concept-configurations.md)中设置它,就像这样:
```php
return [
......@@ -50,25 +43,25 @@ return [
解析别名 <a name="resolving-aliases"></a>
-----------------
你可以调用 [[Yii::getAlias()]] 命令来解析一个根别名到他所对应的文件路径或 URL。同样的页面也可以用于解析衍生别名。比如:
你可以调用 [[Yii::getAlias()]] 命令来解析根别名到对应的文件路径或 URL。同样的页面也可以用于解析衍生别名。例如:
```php
echo Yii::getAlias('@foo'); // 显示:/path/to/foo
echo Yii::getAlias('@bar'); // 显示:http://www.example.com
echo Yii::getAlias('@foo/bar/file.php'); // 显示:/path/to/foo/bar/file.php
echo Yii::getAlias('@foo'); // 输出:/path/to/foo
echo Yii::getAlias('@bar'); // 输出:http://www.example.com
echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path/to/foo/bar/file.php
```
由衍生别名所代指的路径/URL 是通过替换掉衍生别名中的根别名部分得到的。
由衍生别名所解析出的文件路径和 URL 是通过替换掉衍生别名中的根别名部分得到的。
> 注意:[[Yii::getAlias()]] 不检查结果路径/URL 所指向的资源是否真实存在。
> 注意:[[Yii::getAlias()]] 不检查结果路径/URL 所指向的资源是否真实存在。
根别名可能也会包含斜杠 `/` 字符。[[Yii::getAlias()]] 足够聪明,能知道一个别名中的哪个部分是根别名,因此能正确解析文件路径/URL。比如:
根别名可能也会包含斜杠 `/`[[Yii::getAlias()]] 足够智能到判断一个别名中的哪部分是根别名,因此能正确解析文件路径/URL。例如:
```php
Yii::setAlias('@foo', '/path/to/foo');
Yii::setAlias('@foo/bar', '/path2/bar');
Yii::getAlias('@foo/test/file.php'); // 显示:/path/to/foo/test/file.php
Yii::getAlias('@foo/bar/file.php'); // 显示:/path2/bar/file.php
echo Yii::getAlias('@foo/test/file.php'); // 输出:/path/to/foo/test/file.php
echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path2/bar/file.php
```
`@foo/bar` 未被定义为根别名,最后一行语句会显示为 `/path/to/foo/bar/file.php`
......@@ -77,8 +70,7 @@ Yii::getAlias('@foo/bar/file.php'); // 显示:/path2/bar/file.php
使用别名 <a name="using-aliases"></a>
-------------
别名在 Yii 的很多地方都会被正确识别,而无需调用 [[Yii::getAlias()]]
来把它们转换为路径/URL。比如,[[yii\caching\FileCache::cachePath]] 能同时接受文件路径或是代表文件路径的别名,多亏了 `@` 前缀,它区分开了文件路径与别名。
别名在 Yii 的很多地方都会被正确识别,无需调用 [[Yii::getAlias()]] 来把它们转换为路径/URL。例如,[[yii\caching\FileCache::cachePath]] 能同时接受文件路径或是指向文件路径的别名,因为通过 `@` 前缀能区分它们。
```php
use yii\caching\FileCache;
......@@ -88,14 +80,13 @@ $cache = new FileCache([
]);
```
请关注下 API 文档了解属性或方法参数是否支持别名。
请关注 API 文档了解特定属性或方法参数是否支持别名。
预定义的别名 <a name="predefined-aliases"></a>
------------------
Yii 预定义了一系列别名来简化频繁引用常用路径和 URL的需求。
在核心框架中已经预定义有以下别名:
Yii 预定义了一系列别名来简化常用路径和 URL的使用:
- `@yii` - `BaseYii.php` 文件所在的目录(也被称为框架安装目录)
- `@app` - 当前运行的应用 [[yii\base\Application::basePath|根路径(base path)]]
......@@ -104,15 +95,13 @@ Yii 预定义了一系列别名来简化频繁引用常用路径和 URL的需求
- `@webroot` - 当前运行应用的 Web 入口目录
- `@web` - 当前运行应用的根 URL
`@yii` 别名是在[入口脚本](structure-entry-scripts.md)里包含 `Yii.php` 文件时定义的,其他的别名都是在[配置应用](concept-configurations.md)的时候,于应用的构造内定义的。
`@yii` 别名是在[入口脚本](structure-entry-scripts.md)里包含 `Yii.php` 文件时定义的,其他的别名都是在[配置应用](concept-configurations.md)的时候,于应用的构造方法内定义的。
扩展的别名 <a name="extension-aliases"></a>
-----------------
每一个通过 Composer 安装的 [扩展](structure-extensions.md) 都自动添加了一个别名。该别名会以该扩展在 `composer.json`
文件中所声明的根命名空间为名,且他直接代指该包的根目录。比如,如果你安装有 `yiisoft/yii2-jui` 扩展,你会自动得到
`@yii/jui` 别名,它定义于[引导启动](runtime-bootstrapping.md)阶段:
每一个通过 Composer 安装的 [扩展](structure-extensions.md) 都自动添加了一个别名。该别名会以该扩展在 `composer.json` 文件中所声明的根命名空间为名,且他直接代指该包的根目录。例如,如果你安装有 `yiisoft/yii2-jui` 扩展,会自动得到 `@yii/jui` 别名,它定义于[引导启动](runtime-bootstrapping.md)阶段:
```php
Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui');
......
类自动加载(Autoloading)
=================
Yii 依靠[类自动加载机制(http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)[中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。
Yii 依靠[类自动加载机制](http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)[中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。
> 注意:为了简化叙述,本篇文档中我们只会提及类的自动加载。不过,要记得文中的描述同样也适用于接口和Trait(特质)的自动加载哦。
......
行为(Behavior)
行为
=========
行为(Behavior)是 [[yii\base\Behmixinsavior]] 或其子类的实例。 Behavior 也被称为
[Mixin(Mix In可以理解为包含若干个类的部分方法和属性的混合类,英文维基)](http://en.wikipedia.org/wiki/Mixin),允许你增强已有
[[yii\base\Component|组件]] 类的功能,而无需改变类的继承结构。当一个行为被配属到组件之上是,他会向组件“注入”他的属性与方法,就好像这些方法原本就定义在组件里一样。此外,行为能响应由组件所触发的[事件](basic-events.md),从而自定义或调整组件内默认的代码执行。
行为是 [[yii\base\Behavior]] 或其子类的实例。行为,也称为 [mixins](http://en.wikipedia.org/wiki/Mixin),可以无须改变类继承关系即可增强一个已有的 [[yii\base\Component|组件]] 类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的[事件](basic-events.md),从而自定义或调整组件正常执行的代码。
> 译者注:mixin直译为‘混入’,trait直译为‘特质’,为避免翻译上的问题,今后我们还是采用英文术语。
定义行为
-----------
使用行为 <a name="using-behaviors"></a>
---------------
要定义行为,通过继承 [[yii\base\Behavior]] 或其子类来建立一个类。如:
要使用行为,你首先需要把它配属到某个[[yii\base\Component|组件]]上。我们会在接下来的章节内讲解如何配属一个行为。
```php
namespace app\components;
行为被配属到组件之后,它的用法是很直截了当的。
use yii\base\Model;
use yii\base\Behavior;
可以通过行为所配属的组件,访问它的 *public* 成员变量或由 getter 和/或 setter 定义的[属性](concept-properties.md),就像这样:
class MyBehavior extends Behavior
{
public $prop1;
```php
// "prop1" 是一个定义在行为类中的属性
echo $component->prop1;
$component->prop1 = $value;
```
private $_prop2;
与之相似的,你也可以调用行为类的 *public* 方法,
public function getProp2()
{
return $this->_prop2;
}
```php
// foo() 是一个定义在行为类中的公共方法
$component->foo();
public function setProp2($value)
{
$this->_prop2 = $value;
}
public function foo()
{
// ...
}
}
```
如你所见,尽管 `$component` 并没有定义 `prop1``bar()`,它们依旧好像是组件自身定义的一部分一样。
以上代码定义了行为类 `app\components\MyBehavior` 并为要附加行为的组件提供了两个属性 `prop1``prop2` 和一个方法 `foo()` 。注意属性 `prop2` 是通过 getter `getProp2()` 和 setter `setProp2()` 定义的。能这样用是因为 [[yii\base\Object]] 是 [[yii\base\Behavior]] 的祖先类,此祖先类支持用 getter 和 setter 方法定义[属性](basic-properties.md)
如果两个行为都定义了一样的属性或方法,并且它们都配属到同一个组件,那么先附加上的行为在属性或方法被访问时就有优先权
> 提示:在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件
当行为配属到组件时可以关联一个行为名。此时就能使用这个名称来访问行为对象,如下所示:
处理事件
-------------
如果要让行为响应对应组件的事件触发,就应覆写 [[yii\base\Behavior::events()]] 方法,如:
```php
$behavior = $component->getBehavior('myBehavior');
namespace app\components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// 其它代码
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
public function beforeValidate($event)
{
// 处理器方法逻辑
}
}
```
也能获取组件所配属的所有行为:
[[yii\base\Behavior::events()|events()]] 方法返回事件列表和相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件和它的处理器 `beforeValidate()` 。当指定一个事件处理器时,要使用以下格式之一:
* 指向行为类的方法名的字符串,如上例所示;
* 对象或类名和方法名的数组,如 `[$object, 'methodName']`
* 匿名方法。
处理器的格式如下,其中 `$event` 指向事件参数。关于事件的更多细节请参考[事件](basic-events.md)
```php
$behaviors = $component->getBehaviors();
function ($event) {
}
```
配属行为 <a name="attaching-behaviors"></a>
-------------------
附加行为
----------
可以选择静态或动态地配属行为到 [[yii\base\Component|组件]]。在具体实践中,前者更常见。
可以静态或动态地附加行为到[[yii\base\Component|组件]]。前者在实践中更常见。
要静态配属行为,重写目标组件类的 [[yii\base\Component::behaviors()|behaviors()]] 方法即可。如:
要静态附加行为,覆写行为要附加的组件类的 [[yii\base\Component::behaviors()|behaviors()]] 方法即可。[[yii\base\Component::behaviors()|behaviors()]] 方法应该返回行为[配置](basic-configs.md)列表。每个行为配置可以是行为类名也可以是配置数组。如:
```php
namespace app\models;
......@@ -65,20 +104,20 @@ class User extends ActiveRecord
public function behaviors()
{
return [
// 匿名行为 => 行为类名
// 匿名行为,只有行为类名
MyBehavior::className(),
// 命名行为 => 行为类名
// 命名行为,只有行为类名
'myBehavior2' => MyBehavior::className(),
// 匿名行为 => 配置数组
// 匿名行为配置数组
[
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
],
// 命名行为 => 配置数组
// 命名行为配置数组
'myBehavior4' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
......@@ -89,22 +128,20 @@ class User extends ActiveRecord
}
```
[[yii\base\Component::behaviors()|behaviors()]] 方法应该返回一个包含所有行为[配置信息](concept-configurations.md)的列表。每个行为的配置信息可以是行为的类名也可以是其配置数组。
通过为行为配置信息指定相应的键名,可以给行为关联一个名称。这种行为称为**命名行为**。在上例中存在两个命名行为:`myBehavior2``myBehavior4` 。同理如果行为没有关联名称就是**匿名行为**
通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为**命名行为**。上例中,有两个命名行为:`myBehavior2``myBehavior4` 。如果行为没有指定名称就是**匿名行为**
要动态地配属行为,只需调用目标组件的 [[yii\base\Component::attachBehavior()]] 方法即可,如:
要动态附加行为,在对应组件里调用 [[yii\base\Component::attachBehavior()]] 方法即可,如:
```php
use app\components\MyBehavior;
// 配属一个行为对象
// 附加行为对象
$component->attachBehavior('myBehavior1', new MyBehavior);
// 配属行为类
// 附加行为类
$component->attachBehavior('myBehavior2', MyBehavior::className());
// 配属一个配置数组
// 附加配置数组
$component->attachBehavior('myBehavior3', [
'class' => MyBehavior::className(),
'prop1' => 'value1',
......@@ -112,16 +149,16 @@ $component->attachBehavior('myBehavior3', [
]);
```
你也可以通过 [[yii\base\Component::attachBehaviors()]] 方法一次性配属多个行为。比如
可以通过 [[yii\base\Component::attachBehaviors()]] 方法一次附加多个行为
```php
$component->attachBehaviors([
'myBehavior1' => new MyBehavior, // 一个命名行为
MyBehavior::className(), // 一个匿名行为
'myBehavior1' => new MyBehavior, // 命名行为
MyBehavior::className(), // 匿名行为
]);
```
如下所示,你也可以通过[配置数组](concept-configurations.md)配属行为。更多细节,请参考[配置(Configs)](concept-configurations.md#configuration-format)章节。
还可以通过[配置](concept-configurations.md)去附加行为:
```php
[
......@@ -135,107 +172,68 @@ $component->attachBehaviors([
]
```
详情请参考[配置](concept-configurations.md#configuration-format)章节。
拆卸行为 <a name="detaching-behaviors"></a>
-------------------
要拆卸行为,可以用行为的键名调用 [[yii\base\Component::detachBehavior()]] 方法:
使用行为
---------------
```php
$component->detachBehavior('myBehavior1');
```
使用行为,必须像前文描述的一样先把它附加到 [[yii\base\Component|component]] 类或其子类。一旦行为附加到组件,就可以直接使用它。
也可以一次性拆卸掉**所有**的行为
行为附加到组件后,可以通过组件访问一个行为的**公共**成员变量或 getter 和 setter 方法定义的[属性](concept-properties.md)
```php
$component->detachBehaviors();
// "prop1" 是定义在行为类的属性
echo $component->prop1;
$component->prop1 = $value;
```
定义行为 <a name="defining-behaviors"></a>
------------------
要定义一个行为,只需创建新类,继承 [[yii\base\Behavior]] 或其子类。比如,
类似地也可以调用行为的**公共*方法:
```php
namespace app\components;
// bar() 是定义在行为类的公共方法
$component->bar();
```
use yii\base\Model;
use yii\base\Behavior;
如你所见,尽管 `$component` 未定义 `prop1``bar()` ,它们用起来也像组件自己定义的一样。
class MyBehavior extends Behavior
{
public $prop1;
如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么**首先**附加上的行为在属性或方法被访问时有优先权。
private $_prop2;
附加行为到组件时的命名行为,可以使用这个名称来访问行为对象,如下所示:
public function getProp2()
{
return $this->_prop2;
}
public function setProp2($value)
{
$this->_prop2 = $value;
}
public function foo()
{
// ...
}
}
```php
$behavior = $component->getBehavior('myBehavior');
```
以上代码定义了行为类 `app\components\MyBehavior` 并为要附加行为的组件提供了两个属性 `prop1``prop2` 和一个方法 `foo()`。注意,属性 `prop2` 是通过 getter `getProp2()` 和 setter `setProp2()` 定义的。能这样用是因为 [[yii\base\Behavior]] 的祖先类是 [[yii\base\Object]],此祖先类支持用 getter 和 setter 方法定义[属性](concept-properties.md)
在行为类之中,你可以通过 [[yii\base\Behavior::owner]] 属性访问行为的组件。
如果你的行为需要响应其所配属的组件中触发的事件,它需要重写 [[yii\base\Behavior::events()]] 方法。像这样,
也能获取附加到这个组件的所有行为:
```php
namespace app\components;
$behaviors = $component->getBehaviors();
```
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// ...
移除行为
------------
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
要移除行为,可以调用 [[yii\base\Component::detachBehavior()]] 方法用行为相关联的名字实现:
public function beforeValidate($event)
{
// ...
}
}
```php
$component->detachBehavior('myBehavior1');
```
[[yii\base\Behavior::events()|events()]] 方法需返回一个事件列表和它们相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件和它的处理器 `beforeValidate()` 。当指定一个事件处理器时,要使用以下格式之一:
* 一条指向行为类的方法名的字符串,如上例所示;
* 一个包含对象或类名,以及方法名的数组,如 `[$object, 'methodName']`
* 一个匿名函数
事件处理器方法的特征格式如下,其中 `$event` 指向事件参数。关于事件的更多细节请参考[事件](concept-events.md)
也可以移除**全部**行为:
```php
function ($event) {
}
$component->detachBehaviors();
```
使用 `TimestampBehavior` <a name="using-timestamp-behavior"></a>
-------------------------
使用 `TimestampBehavior`
----------------------------
最后让我们来看看 [[yii\behaviors\TimestampBehavior]] 作为具体实践案例,这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 保存时自动更新它的时间戳类型的 attribute(特性)
最后[[yii\behaviors\TimestampBehavior]] 的讲解来结尾,这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 存储时自动更新它的时间戳属性
首先,配属这个行为到目标 [[yii\db\ActiveRecord|Active Record]] 类:
首先,附加这个行为到计划使用该行为的 [[yii\db\ActiveRecord|Active Record]] 类:
```php
namespace app\models\User;
......@@ -262,49 +260,51 @@ class User extends ActiveRecord
}
```
以上行为的配置指定了下面的两条规则
以上指定的行为数组
* 当记录插入时,行为应该要当前时间戳赋值给 `created_at``updated_at` 属性;
* 当记录更新时,行为应该要当前时间戳赋值给 `updated_at` 属性。
* 当记录插入时,行为当前时间戳赋值给 `created_at``updated_at` 属性;
* 当记录更新时,行为当前时间戳赋值给 `updated_at` 属性。
放置上述代码之后,如果有一个 `User` 对象且需要保存,你会发现它的 `created_at``updated_at` 属性已经自动填充了当前时间戳:
```php
保存 `User` 对象,将会发现它的 `created_at``updated_at` 属性自动填充了当前时间戳:
``php
$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at; // shows the current timestamp
echo $user->created_at; // 显示当前时间戳
```
[[yii\behaviors\TimestampBehavior|TimestampBehavior]] 同时提供一个很好用的名为 [[yii\behaviors\TimestampBehavior::touch()|touch()]] 的方法,该方法会把当前时间戳赋值给指定 attribute 并将其存入数据库:
[[yii\behaviors\TimestampBehavior|TimestampBehavior]] 行为还提供了一个有用的方法 [[yii\behaviors\TimestampBehavior::touch()|touch()]],这个方法能将当前时间戳赋值给指定属性并保存到数据库:
```php
$user->touch('login_time');
```
Traits 的对比 <a name="comparison-with-traits"></a>
----------------------
PHP traits 的比较
-------------------
尽管行为在 "注入" 属性和方法到主类方面类似于 [traits](http://www.php.net/traits) ,它们在很多方面却不相同。如上所述,它们各有利弊。它们更像是互补的而不是相互替代。
### Behavior 的优势 <a name="pros-for-behaviors"></a>
### 行为的优势
行为类像普通类支持继承。另一方面,traits 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。
Behavior 类像普通类支持继承。另一方面,Traits 可以视之为一种 PHP 提供语言级支持的复制粘贴功能,它不支持继承
行为无须修改组件类就可动态附加到组件或移除。要使用 traits,必须修改使用它的类
Behavior 无须修改组件类就可动态配属到组件或拆除。要使用 trait,就必须修改引用它的类本身
行为是可配置的而 traits 不能
Behavior 是可配置的而 traits 不行。
行为以响应事件来自定义组件的代码执行。
Behaviors 可以通过响应事件来自定义组件代码的执行
当不同行为附加到同一组件产生命名冲突时,这个冲突通过先附加行为的优先权自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决
当不同 Behavior 附加到同一组件产生命名冲突时,这个冲突会以“先附加行为的优先”的方式自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。
### traits 的优势
### Traits 的优势 <a name="pros-for-traits"></a>
traits 比起行为更高效,因为行为是对象,消耗时间和内存。
Trait 比 behaviors 性能好很多很多,因为行为本身就是对象,他需要占用双倍的时间和内存
IDE 对 traits 更友好,因为它们是语言结构
作为语言架构的一部分,IDE 对 Trait 更加友好
依赖注入容器
==============================
依赖注入(Dependency Injection,缩写 DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。
[Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI
容器的使用方法。
依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。[Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI 容器的使用方法。
依赖注入 <a name="dependency-injection"></a>
......@@ -11,16 +9,14 @@
Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。它支持如下几种类型的依赖注入:
* 构造注入;
* 构造方法注入;
* Setter 和属性注入;
* PHP 回调注入.
### 构造注入 <a name="constructor-injection"></a>
### 构造方法注入 <a name="constructor-injection"></a>
在构造器参数类型提示的帮助下,DI 容器实现了构造器注入。
当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。
例如:
在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。例如:
```php
class Foo
......@@ -39,8 +35,7 @@ $foo = new Foo($bar);
### Setter 和属性注入 <a name="setter-and-property-injection"></a>
Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会
提供给容器用于通过相应的 Setter 或属性注入依赖。例如:
Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会提供给容器用于通过相应的 Setter 或属性注入依赖。例如:
```php
use yii\base\Object;
......@@ -71,7 +66,7 @@ $container->get('Foo', [], [
### PHP 回调注入 <a name="php-callable-injection"></a>
这种情况下,容器将使用一个注册过的 PHP 回调建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如:
这种情况下,容器将使用一个注册过的 PHP 回调建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如:
```php
$container->set('Foo', function () {
......@@ -85,8 +80,7 @@ $foo = $container->get('Foo');
注册依赖关系 <a name="registering-dependencies"></a>
------------------------
你可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接
口名或者一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。
可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。
```php
$container = new \yii\di\Container;
......@@ -134,8 +128,7 @@ $container->set('pageCache', new FileCache);
> Tip: 如果依赖关系名称和依赖关系的定义相同,则不需要通过 DI 容器注册该依赖关系。
每当依赖关系需要解决时,通过 `set()` 注册的依赖关系都会产生一个新实例。你可以使用 [[yii\di\Container::setSingleton()]] 注册一个依
赖关系,它只会产生一个单例:
通过 `set()` 注册的依赖关系,在每次使用时都会产生一个新实例。可以使用 [[yii\di\Container::setSingleton()]] 注册一个单例的依赖关系:
```php
$container->setSingleton('yii\db\Connection', [
......@@ -150,12 +143,9 @@ $container->setSingleton('yii\db\Connection', [
解决依赖关系 <a name="resolving-dependencies"></a>
----------------------
依赖关系注册后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将它们实例化并注入新创建的对象。依赖关系的解决是递归的,意味着如
果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。
注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将依赖实例化并注入新创建的对象。依赖关系的解决是递归的,如果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。
你可以使用 [[yii\di\Container::get()]] 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名
可能是也可能不是通过 `set()``setSingleton()` 注册的。你可以随意地提供一个类的构造器参数列表和一个
[configuration](concept-configurations.md) 用于配置新创建的对象。例如:
可以使用 [[yii\di\Container::get()]] 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名或许是通过 `set()``setSingleton()` 注册的。你可以随意地提供一个类的构造器参数列表和一个[configuration](concept-configurations.md) 用于配置新创建的对象。例如:
```php
// "db" 是前面定义过的一个别名
......@@ -165,11 +155,9 @@ $db = $container->get('db');
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
```
代码背后,DI 容器做了比仅创建一个新对象还要多的多的工作。容器首先将检查类的构造器,找出依赖的类或接口名,然后自动递归解决这些依赖关系。
代码背后,DI 容器做了比创建对象多的多的工作。容器首先将检查类的构造方法,找出依赖的类或接口名,然后自动递归解决这些依赖关系。
如下代码展示了一个更复杂的示例。`UserLister` 类依赖一个实现了 `UserFinderInterface` 接口的对象;`UserFinder` 类实现了这个接口,并
依赖于一个 `Connection` 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖
关系并能通过一个简单的 `get('userLister')` 调用创建一个新的 `UserLister` 实例。
如下代码展示了一个更复杂的示例。`UserLister` 类依赖一个实现了 `UserFinderInterface` 接口的对象;`UserFinder` 类实现了这个接口,并依赖于一个 `Connection` 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖关系并能通过一个简单的 `get('userLister')` 调用创建一个新的 `UserLister` 实例。
```php
namespace app\models;
......@@ -231,9 +219,7 @@ $lister = new UserLister($finder);
实践中的运用 <a name="practical-usage"></a>
---------------
当你在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过
[[Yii::$container]] 访问。当你调用 [[Yii::createObject()]] 时,此方法实际上会调用这个容器的 [[yii\di\Container::get()|get()]]
方法创建一个新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了
当在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 [[Yii::$container]] 访问。当调用 [[Yii::createObject()]] 时,此方法实际上会调用这个容器的 [[yii\di\Container::get()|get()]] 方法创建新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了
[[Yii::createObject()]] 创建新对象,所以你可以通过 [[Yii::$container]] 全局性地自定义这些对象。
例如,你可以全局性自定义 [[yii\widgets\LinkPager]] 中分页按钮的默认数量:
......@@ -254,8 +240,7 @@ echo \yii\widgets\LinkPager::widget();
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
```
另一个例子是借用 DI 容器中自动构造器注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明
依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。
另一个例子是借用 DI 容器中自动构造方法注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。
```php
namespace app\controllers;
......@@ -275,8 +260,7 @@ class HotelController extends Controller
}
```
如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 `BookingInterface` 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个
依赖关系。
如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 `BookingInterface` 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个依赖关系。
```php
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
......@@ -297,9 +281,7 @@ class HotelController extends Controller
总结 <a name="summary"></a>
-------
依赖注入和[服务定位器](concept-service-locator.md) 都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。我们强烈推荐
你阅读 [Martin 的文章](http://martinfowler.com/articles/injection.html) ,对依赖注入和服务定位器有个更深入的理解。
依赖注入和[服务定位器](concept-service-locator.md)都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。强烈推荐你阅读 [Martin 的文章](http://martinfowler.com/articles/injection.html) ,对依赖注入和服务定位器有个更深入的理解。
Yii 在依赖住入(DI)容器之上实现了它的[服务定位器](concept-service-locator.md)。当一个服务定位器尝试创建一个新的对象实例时,它会把
调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。
Yii 在依赖住入(DI)容器之上实现了它的[服务定位器](concept-service-locator.md)。当一个服务定位器尝试创建一个新的对象实例时,它会把调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。
事件
======
事件可以将自定义代码“注入”到现有代码中的特定执行点。附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。例如,邮件程序对象成功发出消息时可触发 `messageSent` 事件。如想追踪成功发送的消息,可以附加相应追踪代码到 `messageSent` 事件。
Yii 引入了名为 [[yii\base\Component]] 的基类以支持事件。如果一个类需要触发事件就应该继承 [[yii\base\Component]] 或其子类。
事件处理器(Event Handlers)
--------------
事件处理器是一个[PHP 回调函数](http://www.php.net/manual/en/language.types.callable.php),当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一:
- 字符串形式指定的 PHP 全局函数,如 `'trim'`
- 对象名和方法名数组形式指定的对象方法,如 `[$object, $method]`
- 类名和方法名数组形式指定的静态类方法,如 `[$class, $method]`
- 匿名函数,如 `function ($event) { ... }`
事件处理器的格式是:
```php
function ($event) {
// $event 是 yii\base\Event 或其子类的对象
}
```
通过 `$event` 参数,事件处理器就获得了以下有关事件的信息:
- [[yii\base\Event::name|event name]]:事件名
- [[yii\base\Event::sender|event sender]]:调用 `trigger()` 方法的对象
- [[yii\base\Event::data|custom data]]:附加事件处理器时传入的数据,默认为空,后文详述
附加事件处理器
----------------
调用 [[yii\base\Component::on()]] 方法来附加处理器到事件上。如:
```php
$foo = new Foo;
// 处理器是全局函数
$foo->on(Foo::EVENT_HELLO, 'function_name');
// 处理器是对象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// 处理器是静态类方法
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 处理器是匿名函数
$foo->on(Foo::EVENT_HELLO, function ($event) {
//事件处理逻辑
});
```
附加事件处理器时可以提供额外数据作为 [[yii\base\Component::on()]] 方法的第三个参数。数据在事件被触发和处理器被调用时能被处理器使用。如:
```php
// 当事件被触发时以下代码显示 "abc"
// 因为 $event->data 包括被传递到 "on" 方法的数据
$foo->on(Foo::EVENT_HELLO, function ($event) {
echo $event->data;
}, 'abc');
```
时间处理器顺序
-----------------
可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 `$event` 参数的 [yii\base\Event::handled]] 属性为真,如下:
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true;
});
```
默认新附加的事件处理器排在已存在处理器队列的最后。因此,这个处理器将在事件被触发时最后一个调用。在处理器队列最前面插入新处理器将使该处理器最先调用,可以传递第四个参数 `$append` 为假并调用 [[yii\base\Component::on()]] 方法实现:
``php
$foo->on(Foo::EVENT_HELLO, function ($event) {
// 这个处理器将被插入到处理器队列的第一位...
}, $data, false);
```
触发事件
----------
事件通过调用 [[yii\base\Component::trigger()]] 方法触发,此方法须传递**事件名**,还可以传递一个事件对象,用来传递参数到事件处理器。如:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component
{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
```
以上代码当调用 `bar()` ,它将触发名为 `hello` 的事件。
> 提示:推荐使用类常量来表示事件名。上例中,常量 `EVENT_HELLO` 用来表示 `hello` 。这有两个好处。第一,它可以防止拼写错误并支持 IDE 的自动完成。第二,只要简单检查常量声明就能了解一个类支持哪些事件。
有时想要在触发事件时同时传递一些额外信息到事件处理器。例如,邮件程序要传递消息信息到 `messageSent` 事件的处理器以便处理器了解哪些消息被发送了。为此,可以提供一个事件对象作为 [[yii\base\Component::trigger()]] 方法的第二个参数。这个事件对象必须是 [[yii\base\Event]] 类或其子类的实例。如:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message)
{
// ...发送 $message 的逻辑...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
```
当 [[yii\base\Component::trigger()]] 方法被调用时,它将调用所有附加到命名事件(trigger 方法第一个参数)的事件处理器。
移除事件处理器
---------------
从事件移除处理器,调用 [[yii\base\Component::off()]] 方法。如:
```php
// 处理器是全局函数
$foo->off(Foo::EVENT_HELLO, 'function_name');
// 处理器是对象方法
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
// 处理器是静态类方法
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 处理器是匿名函数
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
```
注意当匿名函数附加到事件后一般不要尝试移除匿名函数,除非你在某处存储了它。以上示例中,假设匿名函数存储为变量 `$anonymousFunction` 。
移除事件的全部处理器,简单调用 [[yii\base\Component::off()]] 即可,不需要第二个参数:
```php
$foo->off(Foo::EVENT_HELLO);
```
类级别的事件处理器
-------------------
以上部分,我们叙述了在**实例级别**如何附加处理器到事件。有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件,并不是一个个附加事件处理器到每个实例,而是通过调用静态方法 [[yii\base\Event::on()]] 在**类级别**附加处理器。
例如,[活动记录](db-active-record.md)对象要在每次往数据库新增一条新记录时触发一个 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]] 事件。要追踪每个[活动记录](db-active-record.md)对象的新增记录完成情况,应如下写代码:
```php
use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted');
});
```
每当 [[yii\base\ActiveRecord|ActiveRecord]] 或其子类的实例触发 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 `$event->sender` 获取触发事件的对象。
当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。
可调用静态方法[[yii\base\Event::trigger()]]来触发一个**类级别**事件。类级别事件不与特定对象相关联。因此,它只会引起类级别事件处理器的调用。如:
```php
use yii\base\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // 显示 "app\models\Foo"
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
```
注意这种情况下 `$event->sender` 指向触发事件的类名而不是对象实例。
> 注意:因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 [[yii\base\Object]]。
移除类级别的事件处理器只需调用[[yii\base\Event::off()]],如:
```php
// 移除 $handler
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// 移除 Foo::EVENT_HELLO 事件的全部处理器
Event::off(Foo::className(), Foo::EVENT_HELLO);
```
全局事件
-------------
所谓**全局事件**实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如[应用](structure-applications.md)实例。
事件触发者不调用其自身的 `trigger()` 方法,而是调用单例的 `trigger()` 方法来触发全局事件。类似地,事件处理器被附加到单例的事件。如:
```php
use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // 显示 "app\components\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
```
全局事件的一个好处是当附加处理器到一个对象要触发的事件时,不需要产生该对象。相反,处理器附加和事件触发都通过单例(如应用实例)完成。
然而,因为全局事件的命名空间由各方共享,应合理命名全局事件,如引入一些命名空间(例:"frontend.mail.sent", "backend.mail.sent")。
\ No newline at end of file
服务定位器(Service Locator)
服务定位器
===============
服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在一个服务定位器中,每一个组件都只有一个单独的实例,并通过
ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。
服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在服务定位器中,每个组件都只有一个单独的实例,并通过ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。
在 Yii 中,服务定位器[[yii\di\ServiceLocator]] 或其子类的一个实例。
在 Yii 中,服务定位器是 [[yii\di\ServiceLocator]] 或其子类的一个实例。
最最常用的服务定位器一般是 *application(应用)* 对象,可以通过 `\Yii::$app` 访问。它所提供的服务被称为
*application components(应用组件)*,比如:`request``response``urlManager`(分别是请求、响应、Url 管理器)组件。你可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。
最常用的服务定位器是**application(应用)**对象,可以通过 `\Yii::$app` 访问。它所提供的服务被称为**application components(应用组件)**,比如:`request``response``urlManager` 组件。可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。
除了 Application 对象,每一个模块对象本身也是一个服务定位器。
除了 application 对象,每个模块对象本身也是一个服务定位器。
要使用一个服务定位器,第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]]
方法进行注册。以下的方法展示了注册组件的不同方法:
要使用服务定位器,第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]] 方法进行注册。以下的方法展示了注册组件的不同方法:
```php
use yii\di\ServiceLocator;
......@@ -48,15 +45,11 @@ $cache = $locator->get('cache');
$cache = $locator->cache;
```
如上文所示, [[yii\di\ServiceLocator]] 允许你通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时,
[[yii\di\ServiceLocator]] 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。
如上所示, [[yii\di\ServiceLocator]] 允许通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时,[[yii\di\ServiceLocator]] 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。
你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。
若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]],则会抛出一个异常。
你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]],则会抛出一个异常。
因为服务定位器,经常会在创建时附带[配置信息](concept-configurations.md),因此我们提供了一个可写的属性,名为
[[yii\di\ServiceLocator::setComponents()|components]],这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册
"db","cache" 和 "search" 三个组件:
因为服务定位器,经常会在创建时附带[配置信息](concept-configurations.md),因此我们提供了一个可写的属性,名为 [[yii\di\ServiceLocator::setComponents()|components]],这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册"db","cache" 和 "search" 三个组件:
```php
return [
// ...
......
......@@ -11,7 +11,7 @@ Oracle
MSSQL: version 2012 或更高版本,如需使用 LIMIT/OFFSET。
配置
开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的" web 应用是 config/web.php),如下所示:
开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的" Web 应用是 config/web.php),如下所示:
return [
// ...
......
输入验证
================
一般说来,程序猿永远不应该信任从最终用户直接接收到的数据,并且使用它们之前应始终先验证其可靠性。
要给 [model](structure-models.md) 填充其所需的用户输入数据,你可以调用 [[yii\base\Model::validate()]] 方法验证它们。该方法会返回一个布尔值,指明是否通过验证。若没有通过,你能通过 [[yii\base\Model::errors]] 属性获取相应的报错信息。比如,
```php
$model = new \app\models\ContactForm;
// 用用户输入来填充模型的特性
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// 若所有输入都是有效的
} else {
// 有效性验证失败:$errors 属性就是存储错误信息的数组
$errors = $model->errors;
}
```
`validate()` 方法,在幕后为执行验证操作,进行了以下步骤:
1. 通过从 [[yii\base\Model::scenarios()]] 方法返回基于当前 [[yii\base\Model::scenario|场景(scenario)]] 的特性属性列表,算出哪些特性应该进行有效性验证。这些属性被称作 *active attributes*(激活特性)。
2. 通过从 [[yii\base\Model::rules()]] 方法返回基于当前 [[yii\base\Model::scenario|场景(scenario)]] 的验证规则列表,这些规则被称作 *active rules*(激活规则)。
3. 用每个激活规则去验证每个与之关联的激活特性。若失败,则记录下对应模型特性的错误信息。
## 声明规则(Rules) <a name="declaring-rules"></a>
要让 `validate()` 方法起作用,你需要声明与需验证模型特性相关的验证规则。为此,需要重写 [[yii\base\Model::rules()]] 方法。下面的例子展示了如何声明用于验证 `ContactForm` 模型的相关验证规则:
```php
public function rules()
{
return [
// name,email,subject 和 body 特性是 `require`(必填)的
[['name', 'email', 'subject', 'body'], 'required'],
// email 特性必须是一个有效的 email 地址
['email', 'email'],
];
}
```
[[yii\base\Model::rules()|rules()]] 方法应返回一个由规则所组成的数组,每一个规则都呈现为以下这类格式的小数组:
```php
[
// required, specifies which attributes should be validated by this rule.
// For a single attribute, you can use the attribute name directly
// without having it in an array instead of an array
['attribute1', 'attribute2', ...],
// required, specifies the type of this rule.
// It can be a class name, validator alias, or a validation method name
'validator',
// optional, specifies in which scenario(s) this rule should be applied
// if not given, it means the rule applies to all scenarios
// You may also configure the "except" option if you want to apply the rule
// to all scenarios except the listed ones
'on' => ['scenario1', 'scenario2', ...],
// optional, specifies additional configurations for the validator object
'property1' => 'value1', 'property2' => 'value2', ...
]
```
For each rule you must specify at least which attributes the rule applies to and what is the type of the rule.
You can specify the rule type in one of the following forms:
* the alias of a core validator, such as `required`, `in`, `date`, etc. Please refer to
the [Core Validators](tutorial-core-validators.md) for the complete list of core validators.
* the name of a validation method in the model class, or an anonymous function. Please refer to the
[Inline Validators](#inline-validators) subsection for more details.
* the name of a validator class. Please refer to the [Standalone Validators](#standalone-validators)
subsection for more details.
A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules.
A rule may be applied in certain [scenarios](structure-models.md#scenarios) only by specifying the `on` option.
If you do not specify an `on` option, it means the rule will be applied to all scenarios.
When the `validate()` method is called, it does the following steps to perform validation:
1. Determine which attributes should be validated by checking the current [[yii\base\Model::scenario|scenario]]
against the scenarios declared in [[yii\base\Model::scenarios()]]. These attributes are the active attributes.
2. Determine which rules should be applied by checking the current [[yii\base\Model::scenario|scenario]]
against the rules declared in [[yii\base\Model::rules()]]. These rules are the active rules.
3. Use each active rule to validate each active attribute which is associated with the rule.
According to the above validation steps, an attribute will be validated if and only if it is
an active attribute declared in `scenarios()` and is associated with one or multiple active rules
declared in `rules()`.
### 自定义错误信息 <a name="customizing-error-messages"></a>
Most validators have default error messages that will be added to the model being validated when its attributes
fail the validation. For example, the [[yii\validators\RequiredValidator|required]] validator will add
a message "Username cannot be blank." to a model when the `username` attribute fails the rule using this validator.
You can customize the error message of a rule by specifying the `message` property when declaring the rule,
like the following,
```php
public function rules()
{
return [
['username', 'required', 'message' => 'Please choose a username.'],
];
}
```
Some validators may support additional error messages to more precisely describe different causes of
validation failures. For example, the [[yii\validators\NumberValidator|number]] validator supports
[[yii\validators\NumberValidator::tooBig|tooBig]] and [[yii\validators\NumberValidator::tooSmall|tooSmall]]
to describe the validation failure when the value being validated is too big and too small, respectively.
You may configure these error messages like configuring other properties of validators in a validation rule.
### 验证事件 <a name="validation-events"></a>
When [[yii\base\Model::validate()]] is called, it will call two methods that you may override to customize
the validation process:
* [[yii\base\Model::beforeValidate()]]: the default implementation will trigger a [[yii\base\Model::EVENT_BEFORE_VALIDATE]]
event. You may either override this method or respond to this event to do some preprocessing work
(e.g. normalizing data inputs) before the validation occurs. The method should return a boolean value indicating
whether the validation should proceed or not.
* [[yii\base\Model::afterValidate()]]: the default implementation will trigger a [[yii\base\Model::EVENT_AFTER_VALIDATE]]
event. You may either override this method or respond to this event to do some postprocessing work after
the validation is completed.
### 条件式验证 <a name="conditional-validation"></a>
To validate attributes only when certain conditions apply, e.g. the validation of one attribute depends
on the value of another attribute you can use the [[yii\validators\Validator::when|when]] property
to define such conditions. For example,
```php
[
['state', 'required', 'when' => function($model) {
return $model->country == 'USA';
}],
]
```
The [[yii\validators\Validator::when|when]] property takes a PHP callable with the following signature:
```php
/**
* @param Model $model the model being validated
* @param string $attribute the attribute being validated
* @return boolean whether the rule should be applied
*/
function ($model, $attribute)
```
If you also need to support client-side conditional validation, you should configure
the [[yii\validators\Validator::whenClient|whenClient]] property which takes a string representing a JavaScript
function whose return value determines whether to apply the rule or not. For example,
```php
[
['state', 'required', 'when' => function ($model) {
return $model->country == 'USA';
}, 'whenClient' => "function (attribute, value) {
return $('#country').value == 'USA';
}"],
]
```
### 数据过滤 <a name="data-filtering"></a>
User inputs often need to be filtered or preprocessed. For example, you may want to trim the spaces around the
`username` input. You may use validation rules to achieve this goal.
The following examples shows how to trim the spaces in the inputs and turn empty inputs into nulls by using
the [trim](tutorial-core-validators.md#trim) and [default](tutorial-core-validators.md#default) core validators:
```php
[
[['username', 'email'], 'trim'],
[['username', 'email'], 'default'],
]
```
You may also use the more general [filter](tutorial-core-validators.md#filter) validator to perform more complex
data filtering.
As you can see, these validation rules do not really validate the inputs. Instead, they will process the values
and save them back to the attributes being validated.
### 处理空输入 <a name="handling-empty-inputs"></a>
When input data are submitted from HTML forms, you often need to assign some default values to the inputs
if they are empty. You can do so by using the [default](tutorial-core-validators.md#default) validator. For example,
```php
[
// set "username" and "email" as null if they are empty
[['username', 'email'], 'default'],
// set "level" to be 1 if it is empty
['level', 'default', 'value' => 1],
]
```
By default, an input is considered empty if its value is an empty string, an empty array or a null.
You may customize the default empty detection logic by configuring the the [[yii\validators\Validator::isEmpty]] property
with a PHP callable. For example,
```php
[
['agree', 'required', 'isEmpty' => function ($value) {
return empty($value);
}],
]
```
> Note: Most validators do not handle empty inputs if their [[yii\base\Validator::skipOnEmpty]] property takes
the default value true. They will simply be skipped during validation if their associated attributes receive empty
inputs. Among the [core validators](tutorial-core-validators.md), only the `captcha`, `default`, `filter`,
`required`, and `trim` validators will handle empty inputs.
## Ad Hoc Validation <a name="ad-hoc-validation"></a>
Sometimes you need to do *ad hoc validation* for values that are not bound to any model.
If you only need to perform one type of validation (e.g. validating email addresses), you may call
the [[yii\validators\Validator::validate()|validate()]] method of the desired validator, like the following:
```php
$email = 'test@example.com';
$validator = new yii\validators\EmailValidator();
if ($validator->validate($email, $error)) {
echo 'Email is valid.';
} else {
echo $error;
}
```
> Note: Not all validators support such kind of validation. An example is the [unique](tutorial-core-validators.md#unique)
core validator which is designed to work with a model only.
If you need to perform multiple validations against several values, you can use [[yii\base\DynamicModel]]
which supports declaring both attributes and rules on the fly. Its usage is like the following:
```php
public function actionSearch($name, $email)
{
$model = DynamicModel::validateData(compact('name', 'email'), [
[['name', 'email'], 'string', 'max' => 128],
['email', 'email'],
]);
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
```
The [[yii\base\DynamicModel::validateData()]] method creates an instance of `DynamicModel`, defines the attributes
using the given data (`name` and `email` in this example), and then calls [[yii\base\Model::validate()]]
with the given rules.
Alternatively, you may use the following more "classic" syntax to perform ad hoc data validation:
```php
public function actionSearch($name, $email)
{
$model = new DynamicModel(compact('name', 'email'));
$model->addRule(['name', 'email'], 'string', ['max' => 128])
->addRule('email', 'email')
->validate();
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
```
After validation, you can check if the validation succeeds or not by calling the
[[yii\base\DynamicModel::hasErrors()|hasErrors()]] method, and then get the validation errors from the
[[yii\base\DynamicModel::errors|errors]] property, like you do with a normal model.
You may also access the dynamic attributes defined through the model instance, e.g.,
`$model->name` and `$model->email`.
## 创建验证器(Validators) <a name="creating-validators"></a>
Besides using the [core validators](tutorial-core-validators.md) included in the Yii releases, you may also
create your own validators. You may create inline validators or standalone validators.
### 行内验证器(Inline Validators) <a name="inline-validators"></a>
An inline validator is one defined in terms of a model method or an anonymous function. The signature of
the method/function is:
```php
/**
* @param string $attribute the attribute currently being validated
* @param array $params the additional name-value pairs given in the rule
*/
function ($attribute, $params)
```
If an attribute fails the validation, the method/function should call [[yii\base\Model::addError()]] to save
the error message in the model so that it can be retrieved back later to present to end users.
Below are some examples:
```php
use yii\base\Model;
class MyForm extends Model
{
public $country;
public $token;
public function rules()
{
return [
// an inline validator defined as the model method validateCountry()
['country', 'validateCountry'],
// an inline validator defined as an anonymous function
['token', function ($attribute, $params) {
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, 'The token must contain letters or digits.');
}
}],
];
}
public function validateCountry($attribute, $params)
{
if (!in_array($this->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, 'The country must be either "USA" or "Web".');
}
}
}
```
> Note: By default, inline validators will not be applied if their associated attributes receive empty inputs
or if they have already failed some validation rules. If you want to make sure a rule is always applied,
you may configure the [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] and/or [[yii\validators\Validator::skipOnError|skipOnError]]
properties to be false in the rule declarations. For example:
> ```php
[
['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false],
]
```
### 独立验证器(Standalone Validators) <a name="standalone-validators"></a>
A standalone validator is a class extending [[yii\validators\Validator]] or its child class. You may implement
its validation logic by overriding the [[yii\validators\Validator::validateAttribute()]] method. If an attribute
fails the validation, call [[yii\base\Model::addError()]] to save the error message in the model, like you do
with [inline validators](#inline-validators). For example,
```php
namespace app\components;
use yii\validators\Validator;
class CountryValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (!in_array($model->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, 'The country must be either "USA" or "Web".');
}
}
}
```
If you want your validator to support validating a value without a model, you should also override
[[yii\validators\Validator::validate()]]. You may also override [[yii\validators\Validator::validateValue()]]
instead of `validateAttribute()` and `validate()` because by default the latter two methods are implemented
by calling `validateValue()`.
## 客户端验证器(Client-Side Validation) <a name="client-side-validation"></a>
Client-side validation based on JavaScript is desirable when end users provide inputs via HTML forms, because
it allows users to find out input errors faster and thus provides better user experience. You may use or implement
a validator that supports client-side validation *in addition to* server-side validation.
> Info: While client-side validation is desirable, it is not a must. It main purpose is to provider users better
experience. Like input data coming from end users, you should never trust client-side validation. For this reason,
you should always perform server-side validation by calling [[yii\base\Model::validate()]], like
described in the previous subsections.
### 使用客户端验证 <a name="using-client-side-validation"></a>
Many [core validators](tutorial-core-validators.md) support client-side validation out-of-box. All you need to do
is just to use [[yii\widgets\ActiveForm]] to build your HTML forms. For example, `LoginForm` below declares two
rules: one uses the [required](tutorial-core-validators.md#required) core validator which is supported on both
client and server sides; the other uses the `validatePassword` inline validator which is only supported on the server
side.
```php
namespace app\models;
use yii\base\Model;
use app\models\User;
class LoginForm extends Model
{
public $username;
public $password;
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError('password', 'Incorrect username or password.');
}
}
}
```
The HTML form built by the following code contains two input fields `username` and `password`.
If you submit the form without entering anything, you will find the error messages requiring you
to enter something appear right away without any communication with the server.
```php
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= Html::submitButton('Login') ?>
<?php yii\widgets\ActiveForm::end(); ?>
```
Behind the scene, [[yii\widgets\ActiveForm]] will read the validation rules declared in the model
and generate appropriate JavaScript code for validators that support client-side validation. When a user
changes the value of an input field or submit the form, the client-side validation JavaScript will be triggered.
If you want to turn off client-side validation completely, you may configure the
[[yii\widgets\ActiveForm::enableClientValidation]] property to be false. You may also turn off client-side
validation of individual input fields by configuring their [[yii\widgets\ActiveField::enableClientValidation]]
property to be false.
### 实现客户端验证 <a name="implementing-client-side-validation"></a>
To create a validator that supports client-side validation, you should implement the
[[yii\validators\Validator::clientValidateAttribute()]] method which returns a piece of JavaScript code
that performs the validation on the client side. Within the JavaScript code, you may use the following
predefined variables:
- `attribute`: the name of the attribute being validated.
- `value`: the value being validated.
- `messages`: an array used to hold the validation error messages for the attribute.
In the following example, we create a `StatusValidator` which validates if an input is a valid status input
against the existing status data. The validator supports both server side and client side validation.
```php
namespace app\components;
use yii\validators\Validator;
use app\models\Status;
class StatusValidator extends Validator
{
public function init()
{
parent::init();
$this->message = 'Invalid status input.';
}
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!Status::find()->where(['id' => $value])->exists()) {
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
$statuses = json_encode(Status::find()->select('id')->asArray()->column());
$message = json_encode($this->message);
return <<<JS
if (!$.inArray(value, $statuses)) {
messages.push($message);
}
JS;
}
}
```
> Tip: The above code is given mainly to demonstrate how to support client-side validation. In practice,
you may use the [in](tutorial-core-validators.md#in) core validator to achieve the same goal. You may
write the validation rule like the following:
> ```php
[
['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()],
]
```
Yii 是什么
===========
Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。名字 Yii (读作 `易`)在中文里有“极致简单与不断变化”两重含义,也可看作 **Yes It Is**! 的缩写。
Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。名字 Yii (读作 `易`)在中文里有 “极致简单与不断演变” 两重含义,也可看作 **Yes It Is**! 的缩写。
Yii 最适合做什么?
......
......@@ -3,7 +3,7 @@
你可以通过两种方式安装 Yii:使用 [Composer](http://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](extend-creating-extensions.md)或更新 Yii 了。
> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序骨架。
> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。
通过 Composer 安装 <a name="installing-via-composer"></a>
......@@ -22,13 +22,13 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
如上命令会将 Yii 安装在一个名为 `basic` 的目录中。
如上命令会将 Yii 安装在名为 `basic` 的目录中。
> 技巧:如果你想安装 Yii 的最新开发版本,可以使用如下命令,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability):
> 技巧:如果你想安装 Yii 的最新开发版本,可以使用如下命令,它添加了一个 [stability 选项](https://getcomposer.org/doc/04-schema.md#minimum-stability)([中文版](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/04-schema.md#minimum-stability)):
>
> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic
>
> 注意,Yii 的开发版不应该用于生产环境中,它可能会破坏运行中的代码。
> 注意,Yii 的开发版(dev 版)不应该用于生产环境中,它可能会破坏运行中的代码。
通过归档文件安装 <a name="installing-from-archive-file"></a>
......@@ -38,20 +38,26 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下
1.[yiiframework.com](http://www.yiiframework.com/download/yii2-basic) 下载归档文件。
2. 将下载的文件解压缩到 Web 目录中。
3. 修改 `config/web.php` 文件,给 `cookieValidationKey` 配置项添加一个密钥(若你通过 Composer 安装,则此步骤会自动完成):
```php
// !!! 在下面插入一段密钥(若为空) - 以供 cookie validation 的需要
'cookieValidationKey' => '在此处输入你的密钥',
```
其他安装方式 <a name="other-installation-options"></a>
--------------------------
上文介绍了两种安装 Yii 的方法,安装的同时也会创建一个立即可用的 Web 应用程序。对于小的项目或学习,这是一个很好的起点。
上文介绍了两种安装 Yii 的方法安装的同时也会创建一个立即可用的 Web 应用程序对于小的项目或用于学习上手这都是一个不错的起点
但是还可以有其他的安装方式
但是其他的安装方式也存在
* 如果你只想安装核心框架,然后从头开始创建一个应用程序,可以参考[从头构建自定义模版](tutorial-start-from-scratch.md)一节的介绍。
* 如果你要开发一个更复杂的应用,更好的地适用于团队开发环境的,可以考虑安装[高级应用模版](tutorial-advanced-app.md)
* 如果你只想安装核心框架然后从零开始构建整个属于你自己的应用程序模版可以参考[从头构建自定义模版](tutorial-start-from-scratch.md)一节的介绍
* 如果你要开发一个更复杂的应用可以更好地适用于团队开发环境的可以考虑安装[高级应用模版](tutorial-advanced-app.md)
检查安装 <a name="verifying-installation"></a>
验证安装的结果 <a name="verifying-installation"></a>
--------------------------
安装完成后就可以使用浏览器通过如下 URL 访问刚安装完的 Yii 应用了
......@@ -60,11 +66,11 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下
http://localhost/basic/web/index.php
```
这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 `basic` 目录中,且该 Web 服务器正运行在你自己的电脑上 `localhost`)。你可能需要将其调整为适应自己的安装环境。
这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 `basic` 目录中,且该 Web 服务器正运行在你自己的电脑上(`localhost`)。你可能需要将其调整为适应自己的安装环境。
![Yii 安装成功](images/start-app-installed.png)
你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请检查你安装的 PHP 环境是否符合 Yii 的需求,可以通过如下任意一种方式检查是否满足最小需求:
你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请通过以下任意一种方式,检查当前 PHP 环境是否满足 Yii 最基本需求:
* 通过浏览器访问 URL `http://localhost/basic/requirements.php`
* 执行如下命令:
......@@ -74,21 +80,21 @@ http://localhost/basic/web/index.php
php requirements.php
```
你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。最重要的是需要有 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 [PDO PHP 扩展](http://www.php.net/manual/en/pdo.installation.php) 和相应的数据库驱动(例如访问 MySQL 数据库所需的 `pdo_mysql`)。
你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。主要是需要 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 [PDO PHP 扩展](http://www.php.net/manual/zh/pdo.installation.php) 和相应的数据库驱动(例如访问 MySQL 数据库所需的 `pdo_mysql`)。
配置 Web 服务器 <a name="configuring-web-servers"></a>
-----------------------
>补充:如果你现在只是要试用 Yii 而不是要将其部署到生产环境中的服务器上,本小节可以跳过。
>补充:如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。
通过上述方法安装的应用程序在 Windows,Max OS X, Linux 中的 [Apache HTTP 服务器](http://httpd.apache.org/)或 [Nginx HTTP 服务器](http://nginx.org/) 上都可以直接运行。
通过上述方法安装的应用程序在 Windows,Max OS X,Linux 中的 [Apache HTTP 服务器](http://httpd.apache.org/)或 [Nginx HTTP 服务器](http://nginx.org/) 上都可以直接运行。
在生产环境的服务器上,你可能会想配置服务器让应用程序可以通过 URL `http://www.example.com/index.php` 访问而不是 `http://www.example.com/basic/web/index.php`。这种配置需要将 Web 服务器的文档根目录指向 `basic/web` 目录。可能你还会想隐藏掉 URL 中的 `index.php`,具体细节在 [URL 解析和生成](runtime-url-handling.md) 一章中有介绍,你将学到如何配置 Apache 或 Nginx 服务器实现这些目标。
>补充:将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用程序代码和敏感数据文件。禁止对其他目录的访问是一个切实可行的安全改进。
>补充:将 `basic/web` 设置为文档根目录,可以防止终端用户访问 `basic/web` 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。
>补充:如果你的应用程序将来要运行在共享的主机环境中,没有权限修改它的 Web 服务器配置,你依然可以调整应用程序的结构提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。
>补充:如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考[共享主机环境](tutorial-shared-hosting.md) 一章。
### 推荐使用的 Apache 配置 <a name="recommended-apache-configuration"></a>
......@@ -137,7 +143,7 @@ server {
try_files $uri $uri/ /index.php?$args;
}
# 给这段解除注释,能够避免 Yii 接管不存在文件的处理过程(404)
# 若取消下面这段的注释,可避免 Yii 接管不存在文件的处理过程(404)
#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
# try_files $uri =404;
#}
......@@ -155,6 +161,6 @@ server {
}
```
使用该配置时,你还应该在 `php.ini` 文件中设置 `cgi.fix_pathinfo=0` 以避免很多不必要的 `stat()` 系统调用。
使用该配置时,你还应该在 `php.ini` 文件中设置 `cgi.fix_pathinfo=0` ,能避免掉很多不必要的 `stat()` 系统调用。
还要注意当运行一个 HTTPS 服务器时,需要添加 `fastcgi_param HTTPS on;` 一行,这样 Yii 才能正确地判断连接是否安全。
核心验证器(Core Validators)
===============
Yii 提供一系列常用的核心验证器,主要存在于 `yii\validators` 命名空间之下。为了避免使用冗长的类名,你可以直接用**昵称**来指定相应的核心验证器。比如你可以用 `required` 昵称代指 [[yii\validators\RequiredValidator]] 类:
```php
public function rules()
{
return [
[['email', 'password'], 'required'],
];
}
```
[[yii\validators\Validator::builtInValidators]] 属性声明了所有被支持的验证器昵称。
下面,我们将详细介绍每一款验证器的主要用法和属性。
## [[yii\validators\BooleanValidator|boolean(布尔型)]] <a name="boolean"></a>
```php
[
// 检查 "selected" 是否为 0 或 1,无视数据类型
['selected', 'boolean'],
// 检查 "deleted" 是否为布尔类型,即 true 或 false
['deleted', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true],
]
```
该验证器检查输入值是否为一个布尔值。
- `trueValue`: 代表**真**的值。默认为 `'1'`
- `falseValue`:代表**假**的值。默认为 `'0'`
- `strict`:是否要求待测输入必须严格匹配 `trueValue``falseValue`。默认为 `false`
> 注意:因为通过 HTML 表单传递的输入数据都是字符串类型,所以一般情况下你都需要保持
[[yii\validators\BooleanValidator::strict|strict]] 属性为假。
## [[yii\captcha\CaptchaValidator|captcha(验证码)]] <a name="captcha"></a>
```php
[
['verificationCode', 'captcha'],
]
```
该验证器通常配合 [[yii\captcha\CaptchaAction]] 以及 [[yii\captcha\Captcha]]
使用,以确保某一输入与 [[yii\captcha\Captcha|CAPTCHA]] 小部件所显示的验证代码(verification code)相同。
- `caseSensitive`:对验证代码的比对是否要求大小写敏感。默认为 false。
- `captchaAction`:指向用于渲染 CAPTCHA 图片的 [[yii\captcha\CaptchaAction|CAPTCHA action]] 的 [路由](structure-controllers.md#routes)。默认为 `'site/captcha'`
- `skipOnEmpty`:当输入为空时,是否跳过验证。默认为 false,也就是输入值为必需项。
## [[yii\validators\CompareValidator|compare(比较)]] <a name="compare"></a>
```php
[
// 检查 "password" 特性的值是否与 "password_repeat" 的值相同
['password', 'compare'],
// 检查年龄是否大于等于 30
['age', 'compare', 'compareValue' => 30, 'operator' => '>='],
]
```
该验证器比较两个特定输入值之间的关系是否与 `operator` 属性所指定的相同。
- `compareAttribute`:用于与原特性相比较的特性名称。当该验证器被用于验证某目标特性时,该属性会默认为目标属性加后缀 `_repeat`。举例来说,若目标特性为 `password`,则该属性默认为 `password_repeat`
- `compareValue`:用于与输入值相比较的常量值。当该属性与 `compareAttribute` 属性同时被指定时,该属性优先被使用。
- `operator`:比较操作符。默认为 `==`,意味着检查输入值是否与 `compareAttribute``compareValue` 的值相等。该属性支持如下操作符:
* `==`:检查两值是否相等。比对为非严格模式。
* `===`:检查两值是否全等。比对为严格模式。
* `!=`:检查两值是否不等。比对为非严格模式。
* `!==`:检查两值是否不全等。比对为严格模式。
* `>`:检查待测目标值是否大于给定被测值。
* `>=`:检查待测目标值是否大于等于给定被测值。
* `<`:检查待测目标值是否小于给定被测值。
* `<=`:检查待测目标值是否小于等于给定被测值。
## [[yii\validators\DateValidator|date(日期)]] <a name="date"></a>
```php
[
[['from', 'to'], 'date'],
]
```
该验证器检查输入值是否为适当格式的 date,time,或者 datetime。另外,它还可以帮你把输入值转换为一个 UNIX 时间戳并保存到 [[yii\validators\DateValidator::timestampAttribute|timestampAttribute]] 属性所指定的特性里。
- `format`:待测的 日期/时间 格式。请参考
[date_create_from_format() 相关的 PHP 手册](http://www.php.net/manual/zh/datetime.createfromformat.php)了解设定格式字符串的更多细节。默认值为 `'Y-m-d'`
- `timestampAttribute`:用于保存用输入时间/日期转换出来的 UNIX 时间戳的特性。
## [[yii\validators\DefaultValueValidator|default(默认值)]] <a name="default"></a>
```php
[
// 若 "age" 为空,则将其设为 null
['age', 'default', 'value' => null],
// 若 "country" 为空,则将其设为 "USA"
['country', 'default', 'value' => 'USA'],
// 若 "from" 和 "to" 为空,则分别给他们分配自今天起,3 天后和 6 天后的日期。
[['from', 'to'], 'default', 'value' => function ($model, $attribute) {
return date('Y-m-d', strtotime($attribute === 'to' ? '+3 days' '+6 days'));
}],
]
```
该验证器并不进行数据验证。而是,给为空的待测特性分配默认值。
- `value`:默认值,或一个返回默认值的 PHP Callable 对象(即回调函数)。它们会分配给检测为空的待测特性。PHP 回调方法的样式如下:
```php
function foo($model, $attribute) {
// ... compute $value ...
return $value;
}
```
> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。
## [[yii\validators\NumberValidator|double(双精度浮点型)]] <a name="double"></a>
```php
[
// 检查 "salary" 是否为浮点数
['salary', 'double'],
]
```
该验证器检查输入值是否为双精度浮点数。他等效于 [number](#number) 验证器。
- `max`:上限值(含界点)。若不设置,则验证器不检查上限。
- `min`:下限值(含界点)。若不设置,则验证器不检查下限。
## [[yii\validators\EmailValidator|email(电子邮件)]] <a name="email"></a>
```php
[
// 检查 "email" 是否为有效的邮箱地址
['email', 'email'],
]
```
该验证器检查输入值是否为有效的邮箱地址。
- `allowName`:检查是否允许带名称的电子邮件地址 (e.g. `张三 <John.san@example.com>`)。 默认为 false。
- `checkDNS`:检查邮箱域名是否存在,且有没有对应的 A 或 MX 记录。不过要知道,有的时候该项检查可能会因为临时性 DNS 故障而失败,哪怕它其实是有效的。默认为 false。
- `enableIDN`:验证过程是否应该考虑 IDN(internationalized domain names,国际化域名,也称多语种域名,比如中文域名)。默认为 false。要注意但是为使用 IDN 验证功能,请先确保安装并开启 `intl` PHP 扩展,不然会导致抛出异常。
## [[yii\validators\ExistValidator|exist(存在性)]] <a name="exist"></a>
```php
[
// a1 需要在 "a1" 特性所代表的字段内存在
['a1', 'exist'],
// a1 必需存在,但检验的是 a1 的值在字段 a2 中的存在性
['a1', 'exist', 'targetAttribute' => 'a2'],
// a1 和 a2 的值都需要存在,且它们都能收到错误提示
[['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']],
// a1 和 a2 的值都需要存在,只有 a1 能接收到错误信息
['a1', 'exist', 'targetAttribute' => ['a1', 'a2']],
// 通过同时在 a2 和 a3 字段中检查 a2 和 a1 的值来确定 a1 的存在性
['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']],
// a1 必需存在,若 a1 为数组,则其每个子元素都必须存在。
['a1', 'exist', 'allowArray' => true],
]
```
该验证器检查输入值是否在某表字段中存在。它只对[活动记录](db-active-record.md)类型的模型类特性起作用,能支持对一个或多过字段的验证。
- `targetClass`:用于查找输入值的目标 [AR](db-active-record.md) 类。若不设置,则会使用正在进行验证的当前模型类。
- `targetAttribute`:用于检查输入值存在性的 `targetClass` 的模型特性。
- 若不设置,它会直接使用待测特性名(整个参数数组的首元素)。
- 除了指定为字符串以外,你也可以用数组的形式,同时指定多个用于验证的表字段,数组的键和值都是代表字段的特性名,值表示 `targetClass` 的待测数据源字段,而键表示当前模型的待测特性名。
- 若键和值相同,你可以只指定值。(如:`['a2']` 就代表 `['a2'=>'a2']`
- `filter`:用于检查输入值存在性必然会进行数据库查询,而该属性为用于进一步筛选该查询的过滤条件。可以为代表额外查询条件的字符串或数组(关于查询条件的格式,请参考 [[yii\db\Query::where()]]);或者样式为 `function ($query)` 的匿名函数,`$query` 参数为你希望在该函数内进行修改的 [[yii\db\Query|Query]] 对象。
- `allowArray`:是否允许输入值为数组。默认为 false。若该属性为 true 且输入值为数组,则数组的每个元素都必须在目标字段中存在。值得注意的是,若用吧 `targetAttribute` 设为多元素数组来验证被测值在多字段中的存在性时,该属性不能设置为 true。
> 译者注:[exist](#exist) 和 [unique](#unique) 验证器的机理和参数都相似,有点像一体两面的阴和阳。
- 他们的区别是 exist 要求 `targetAttribute` 键所代表的的属性在其值所代表字段中找得到;而 unique 正相反,要求键所代表的的属性不能在其值所代表字段中被找到。
- 从另一个角度来理解:他们都会在验证的过程中执行数据库查询,查询的条件即为where $v=$k (假设 `targetAttribute` 的其中一对键值对为 `$k => $v`)。unique 要求查询的结果数 `$count==0`,而 exist 则要求查询的结果数 `$count>0`
- 最后别忘了,unique 验证器不存在 `allowArray` 属性哦。
## [[yii\validators\FileValidator|file(文件)]] <a name="file"></a>
```php
[
// 检查 "primaryImage" 是否为 PNG, JPG 或 GIF 格式的上传图片。
// 文件大小必须小于 1MB
['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024],
]
```
该验证器检查输入值是否为一个有效的上传文件。
- `extensions`:可接受上传的文件扩展名列表。它可以是数组,也可以是用空格或逗号分隔各个扩展名的字符串 (e.g. "gif, jpg")。
扩展名大小写不敏感。默认为 null,意味着所有扩展名都被接受。
- `mimeTypes`:可接受上传的 MIME 类型列表。它可以是数组,也可以是用空格或逗号分隔各个 MIME 的字符串 (e.g. "image/jpeg, image/png")。
Mime 类型名是大小写不敏感的。默认为 null,意味着所有 MIME 类型都被接受。
- `minSize`:上传文件所需最少多少 Byte 的大小。默认为 null,代表没有下限。
- `maxSize`:上传文件所需最多多少 Byte 的大小。默认为 null,代表没有上限。
- `maxFiles`:给定特性最多能承载多少个文件。默认为 1,代表只允许单文件上传。若值大于一,那么输入值必须为包含最多 `maxFiles` 个上传文件元素的数组。
- `checkExtensionByMimeType`:是否通过文件的 MIME 类型来判断其文件扩展。若由 MIME 判定的文件扩展与给定文件的扩展不一样,则文件会被认为无效。默认为 true,代表执行上述检测。
`FileValidator` 通常与 [[yii\web\UploadedFile]] 共同使用。请参考 [文件上传](input-file-upload.md)章节来了解有关文件上传与上传文件的检验的全部内容。
## [[yii\validators\FilterValidator|filter(滤镜)]] <a name="filter"></a>
```php
[
// trim 掉 "username" 和 "email" 输入
[['username', 'email'], 'filter', 'filter' => 'trim', 'skipOnArray' => true],
// 标准化 "phone" 输入
['phone', 'filter', 'filter' => function ($value) {
// 在此处标准化输入的电话号码
return $value;
}],
]
```
该验证器并不进行数据验证。而是,给输入值应用一个滤镜,并在检验过程之后把它赋值回特性变量。
- `filter`:用于定义滤镜的 PHP 回调函数。可以为全局函数名,匿名函数,或其他。该函数的样式必须是 `function ($value) { return $newValue; }`。该属性不能省略,必须设置。
- `skipOnArray`:是否在输入值为数组时跳过滤镜。默认为 false。请注意如果滤镜不能处理数组输入,你就应该把该属性设为 true。否则可能会导致 PHP Error 的发生。
> 技巧:如果你只是想要用 trim 处理下输入值,你可以直接用 [trim](#trim) 验证器的。
## [[yii\validators\ImageValidator|image(图片)]] <a name="image"></a>
```php
[
// 检查 "primaryImage" 是否为适当尺寸的有效图片
['primaryImage', 'image', 'extensions' => 'png, jpg',
'minWidth' => 100, 'maxWidth' => 1000,
'minHeight' => 100, 'maxHeight' => 1000,
],
]
```
该验证器检查输入值是否为代表有效的图片文件。它继承自 [file](#file) 验证器,并因此继承有其全部属性。除此之外,它还支持以下为图片检验而设的额外属性:
- `minWidth`:图片的最小宽度。默认为 null,代表无下限。
- `maxWidth`:图片的最大宽度。默认为 null,代表无上限。
- `minHeight`:图片的最小高度。 默认为 null,代表无下限。
- `maxHeight`:图片的最大高度。默认为 null,代表无上限。
## [[yii\validators\RangeValidator|in(范围)]] <a name="in"></a>
```php
[
// 检查 "level" 是否为 1、2 或 3 中的一个
['level', 'in', 'range' => [1, 2, 3]],
]
```
该验证器检查输入值是否存在于给定列表的范围之中。
- `range`:用于检查输入值的给定值列表。
- `strict`:输入值与给定值直接的比较是否为严格模式(也就是类型与值都要相同,即全等)。默认为 false。
- `not`:是否对验证的结果取反。默认为 false。当该属性被设置为 true,验证器检查输入值是否**不在**给定列表内。
- `allowArray`:是否接受输入值为数组。当该值为 true 且输入值为数组时,数组内的每一个元素都必须在给定列表内存在,否则返回验证失败。
## [[yii\validators\NumberValidator|integer(整数)]] <a name="integer"></a>
```php
[
// 检查 "age" 是否为整数
['age', 'integer'],
]
```
该验证器检查输入值是否为整形。
- `max`:上限值(含界点)。若不设置,则验证器不检查上限。
- `min`:下限值(含界点)。若不设置,则验证器不检查下限。
## [[yii\validators\RegularExpressionValidator|match(正则表达式)]] <a name="match"></a>
```php
[
// 检查 "username" 是否由字母开头,且只包含单词字符
['username', 'match', 'pattern' => '/^[a-z]\w*$/i']
]
```
该验证器检查输入值是否匹配指定正则表达式。
- `pattern`:用于检测输入值的正则表达式。该属性是必须的,若不设置则会抛出异常。
- `not`:是否对验证的结果取反。默认为 false,代表输入值匹配正则表达式时验证成功。如果设为 true,则输入值不匹配正则时返回匹配成功。
## [[yii\validators\NumberValidator|number(数字)]] <a name="number"></a>
```php
[
// 检查 "salary" 是否为数字
['salary', 'number'],
]
```
该验证器检查输入值是否为数字。他等效于 [double](#double) 验证器。
- `max`:上限值(含界点)。若不设置,则验证器不检查上限。
- `min`:下限值(含界点)。若不设置,则验证器不检查下限。
## [[yii\validators\RequiredValidator|required(必填)]] <a name="required"></a>
```php
[
// 检查 "username" 与 "password" 是否为空
[['username', 'password'], 'required'],
]
```
该验证器检查输入值是否为空,还是已经提供了。
- `requiredValue`:所期望的输入值。若没设置,意味着输入不能为空。
- `strict`:检查输入值时是否检查类型。默认为 false。当没有设置 `requiredValue` 属性时,若该属性为 true,验证器会检查输入值是否严格为 null;若该属性设为 false,该验证器会用一个更加宽松的规则检验输入值是否为空。
当设置了 `requiredValue` 属性时,若该属性为 true,输入值与 `requiredValue` 的比对会同时检查数据类型。
> 补充:如何判断待测值是否为空,被写在另外一个话题的[处理空输入](input-validation.md#handling-empty-inputs)章节。
## [[yii\validators\SafeValidator|safe(安全)]] <a name="safe"></a>
```php
[
// 标记 "description" 为安全特性
['description', 'safe'],
]
```
该验证器并不进行数据验证。而是把一个特性标记为[安全特性](structure-models.md#safe-attributes)
## [[yii\validators\StringValidator|string(字符串)]] <a name="string"></a>
```php
[
// 检查 "username" 是否为长度 4 到 24 之间的字符串
['username', 'string', 'length' => [4, 24]],
]
```
该验证器检查输入值是否为特定长度的字符串。并检查特性的值是否为某个特定长度。
- `length`:指定待测输入字符串的长度限制。该属性可以被指定为以下格式之一:
* 证书:the exact length that the string should be of;
* 单元素数组:代表输入字符串的最小长度 (e.g. `[8]`)。这会重写 `min` 属性。
* 包含两个元素的数组:代表输入字符串的最小和最大长度(e.g. `[8, 128]`)。
这会同时重写 `min``max` 属性。
- `min`:输入字符串的最小长度。若不设置,则代表不设下限。
- `max`:输入字符串的最大长度。若不设置,则代表不设上限。
- `encoding`:待测字符串的编码方式。若不设置,则使用应用自身的 [[yii\base\Application::charset|charset]] 属性值,该值默认为 `UTF-8`
## [[yii\validators\FilterValidator|trim(译为修剪/裁边)]] <a name="trim"></a>
```php
[
// trim 掉 "username" 和 "email" 两侧的多余空格
[['username', 'email'], 'trim'],
]
```
该验证器并不进行数据验证。而是,trim 掉输入值两侧的多余空格。注意若该输入值为数组,那它会忽略掉该验证器。
## [[yii\validators\UniqueValidator|unique(唯一性)]] <a name="unique"></a>
```php
[
// a1 需要在 "a1" 特性所代表的字段内唯一
['a1', 'unique'],
// a1 需要唯一,但检验的是 a1 的值在字段 a2 中的唯一性
['a1', 'unique', 'targetAttribute' => 'a2'],
// a1 和 a2 的组合需要唯一,且它们都能收到错误提示
[['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']],
// a1 和 a2 的组合需要唯一,只有 a1 能接收错误提示
['a1', 'unique', 'targetAttribute' => ['a1', 'a2']],
// 通过同时在 a2 和 a3 字段中检查 a2 和 a3 的值来确定 a1 的唯一性
['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']],
]
```
该验证器检查输入值是否在某表字段中唯一。它只对[活动记录](db-active-record.md)类型的模型类特性起作用,能支持对一个或多过字段的验证。
- `targetClass`:用于查找输入值的目标 [AR](db-active-record.md) 类。若不设置,则会使用正在进行验证的当前模型类。
- `targetAttribute`:用于检查输入值唯一性的 `targetClass` 的模型特性。
- 若不设置,它会直接使用待测特性名(整个参数数组的首元素)。
- 除了指定为字符串以外,你也可以用数组的形式,同时指定多个用于验证的表字段,数组的键和值都是代表字段的特性名,值表示 `targetClass` 的待测数据源字段,而键表示当前模型的待测特性名。
- 若键和值相同,你可以只指定值。(如:`['a2']` 就代表 `['a2'=>'a2']`
- `filter`:用于检查输入值唯一性必然会进行数据库查询,而该属性为用于进一步筛选该查询的过滤条件。可以为代表额外查询条件的字符串或数组(关于查询条件的格式,请参考 [[yii\db\Query::where()]]);或者样式为 `function ($query)` 的匿名函数,`$query` 参数为你希望在该函数内进行修改的 [[yii\db\Query|Query]] 对象。
> 译者注:[exist](#exist) 和 [unique](#unique) 验证器的机理和参数都相似,有点像一体两面的阴和阳。
- 他们的区别是 exist 要求 `targetAttribute` 键所代表的的属性在其值所代表字段中找得到;而 unique 正相反,要求键所代表的的属性不能在其值所代表字段中被找到。
- 从另一个角度来理解:他们都会在验证的过程中执行数据库查询,查询的条件即为where $v=$k (假设 `targetAttribute` 的其中一对键值对为 `$k => $v`)。unique 要求查询的结果数 `$count==0`,而 exist 则要求查询的结果数 `$count>0`
- 最后别忘了,unique 验证器不存在 `allowArray` 属性哦。
## [[yii\validators\UrlValidator|url(网址)]] <a name="url"></a>
```php
[
// 检查 "website" 是否为有效的 URL。若没有 URI 方案,则给 "website" 特性加 "http://" 前缀
['website', 'url', 'defaultScheme' => 'http'],
]
```
该验证器检查输入值是否为有效 URL。
- `validSchemes`:用于指定那些 URI 方案会被视为有效的数组。默认为 `['http', 'https']`,代表 `http``https` URLs 会被认为有效。
- `defaultScheme`:若输入值没有对应的方案前缀,会使用的默认 URI 方案前缀。默认为 null,代表不修改输入值本身。
- `enableIDN`:验证过程是否应该考虑 IDN(internationalized domain names,国际化域名,也称多语种域名,比如中文域名)。默认为 false。要注意但是为使用 IDN 验证功能,请先确保安装并开启 `intl` 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