Commit 6b460c98 by Qiang Xue

Merge pull request #5616 from yii2-chinesization/master

Update old files and add some new translations
parents acba9e5b f6446042
......@@ -12,7 +12,6 @@
```php
namespace app\components;
use yii\base\Model;
use yii\base\Behavior;
class MyBehavior extends Behavior
......@@ -188,14 +187,14 @@ echo $component->prop1;
$component->prop1 = $value;
```
类似地也可以调用行为的**公共*方法:
类似地也可以调用行为的**公共**方法:
```php
// bar() 是定义在行为类的公共方法
$component->bar();
// foo() 是定义在行为类的公共方法
$component->foo();
```
如你所见,尽管 `$component` 未定义 `prop1``bar()` ,它们用起来也像组件自己定义的一样。
如你所见,尽管 `$component` 未定义 `prop1``foo()` ,它们用起来也像组件自己定义的一样。
如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么**首先**附加上的行为在属性或方法被访问时有优先权。
......
......@@ -17,7 +17,7 @@ $config = [
$db = Yii::createObject($config);
```
[[Yii::createObject()]] 方法接受一个配置并根据配置中指定的类名创建对象。对象实例化后,剩余的参数被用来初始化对象的属性,事件处理和行为。
[[Yii::createObject()]] 方法接受一个配置数组并根据数组中指定的类名创建对象。对象实例化后,剩余的参数被用来初始化对象的属性,事件处理和行为。
对于已存在的对象,可以使用 [[Yii::configure()]] 方法根据配置去初始化其属性,就像这样:
......@@ -46,7 +46,8 @@ Yii::configure($object, $config);
* `class` 元素指定了将要创建的对象的完全限定类名。
* `propertyName` 元素指定了对象属性的初始值。键名是属性名,值是该属性对应的初始值。只有公共成员变量以及通过 getter/setter 定义的[属性](concept-properties.md)可以被配置。
* `on eventName` 元素指定了附加到对象[事件](concept-events.md)上的句柄是什么。请注意,数组的键名由 `on ` 前缀加事件名组成。请参考[事件](concept-events.md)章节了解事件句柄格式。
* `as behaviorName` 元素指定了附加到对象的[行为](concept-behaviors.md)。请注意,数组的键名由 `as ` 前缀加行为名组成。`$behaviorConfig` 表示创建行为的配置信息,格式与我们现在总体叙述的配置格式一样。
* `as behaviorName` 元素指定了附加到对象的[行为](concept-behaviors.md)。请注意,数组的键名由 `as ` 前缀加行为名组成。`$behaviorConfig`
值表示创建行为的配置信息,格式与我们之前描述的配置格式一样。
下面是一个配置了初始化属性值,事件句柄和行为的示例:
......@@ -117,7 +118,7 @@ $config = [
### 小部件的配置 <a name="widget-configurations"></a>
使用[小部件](structure-widgets.md)时,常常需要配置以便自定义其属性。 [[yii\base\Widget::widget()]] 和 [[yii\base\Widget::beginWidget()]] 方法都可以用来创建小部件。它们可以接受配置数组:
使用[小部件](structure-widgets.md)时,常常需要配置以便自定义其属性。 [[yii\base\Widget::widget()]] 和 [[yii\base\Widget::begin()]] 方法都可以用来创建小部件。它们可以接受配置数组:
```php
use yii\widgets\Menu;
......
......@@ -174,7 +174,7 @@ $foo->off(Foo::EVENT_HELLO);
以上部分,我们叙述了在**实例级别**如何附加处理器到事件。有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件,并不是一个个附加事件处理器到每个实例,而是通过调用静态方法 [[yii\base\Event::on()]] 在**类级别**附加处理器。
例如,[活动记录](db-active-record.md)对象要在每次往数据库新增一条新记录时触发一个 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT]] 事件。要追踪每个[活动记录](db-active-record.md)对象的新增记录完成情况,应如下写代码:
例如,[活动记录](db-active-record.md)对象要在每次往数据库新增一条新记录时触发一个 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件。要追踪每个[活动记录](db-active-record.md)对象的新增记录完成情况,应如下写代码:
```php
use Yii;
......@@ -186,7 +186,7 @@ Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function
});
```
每当 [[yii\base\ActiveRecord|ActiveRecord]] 或其子类的实例触发 [[yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 `$event->sender` 获取触发事件的对象。
每当 [[yii\db\BaseActiveRecord|ActiveRecord]] 或其子类的实例触发 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 `$event->sender` 获取触发事件的对象。
当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。
......
属性(Property)
==========
在 PHP 中,类的成员变量也被称为**属性(properties)**。它们是类定义的一部分,用来表现一个实例的状态(也就是区分类的不同实例)。在具体实践中,常常会想用一个稍微特殊些的方法实现属性的读写。例如,要对 `label` 属性执行 trim 操作,可以用以下代码实现:
在 PHP 中,类的成员变量也被称为**属性(properties)**。它们是类定义的一部分,用来表现一个实例的状态(也就是区分类的不同实例)。在具体实践中,常常会想用一个稍微特殊些的方法实现属性的读写。例如,如果有需求每次都要对 `label` 属性执行 trim 操作,就可以用以下代码实现:
```php
$object->label = trim($label);
......
Active Record(活动记录)
Active Record
=============
> 注意:该章节还在开发中。
[Active Record](http://zh.wikipedia.org/wiki/Active_Record) (活动记录,以下简称AR)提供了一个面向对象的接口,
用以访问数据库中的数据。一个 AR 类关联一张数据表,
每个 AR 对象对应表中的一行,对象的属性(即 AR 的特性Attribute)映射到数据行的对应列。
......@@ -94,7 +96,7 @@ $customer->save();
----------------------
AR 用一个 [[yii\db\Connection|DB connection]] 对象与数据库交换数据。
使用 `db` 组件作为其连接对象。详见[数据库基础](database-basics.md)章节,
默认的,它使用 `db` 组件作为其连接对象。详见[数据库基础](database-basics.md)章节,
你可以在应用程序配置文件中设置下 `db` 组件,就像这样,
......@@ -164,22 +166,28 @@ $sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();
```
> 小贴士:在上面的代码中,`Customer::STATUS_ACTIVE` 是一个在 `Customer` 类里定义的常量。(译者注:这种常量的值一般都是tinyint)相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。
> 小技巧:在上面的代码中,`Customer::STATUS_ACTIVE` 是一个在 `Customer` 类里定义的常量。(译者注:这种常量的值一般都是tinyint)相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。
`find()` 方法也支持用一种简化的用法,让你直接通过主键的值或者一系列其他字段值的数组来获取 AR 对象。
主要的不同点在于,
它并不返回 [[yii\db\ActiveQuery]] 对象,而是基于输入的字段值,直接返回一个 AR 对象
而无需调用 `one()` 方法。
有两个快捷方法:`findOne``findAll()` 用来返回一个或者一组`ActiveRecord`实例。前者返回第一个匹配到的实例,后者返回所有。
例如:
```php
// 返回ID为1的客户:
$customer = Customer::find(1);
// 返回 id 为 1 的客户
$customer = Customer::findOne(1);
// 返回ID为1的活跃客户:
$customer = Customer::find([
// 返回 id 为 1 且状态为 *active* 的客户
$customer = Customer::findOne([
'id' => 1,
'status' => Customer::STATUS_ACTIVE,
]);
// 返回id为1、2、3的一组客户
$customers = Customer::findAll([1, 2, 3]);
// 返回所有状态为 "deleted" 的客户
$customer = Customer::findAll([
'status' => Customer::STATUS_DELETED,
]);
```
### 以数组形式获取数据
......@@ -241,19 +249,22 @@ AR 銝瘜隞亙銝 AR 蝐餅
```php
// 插入新客户的记录
$customer = new Customer();
$customer->name = '詹姆斯';
$customer->email = '007@example.com';
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save(); // 等同于 $customer->insert();
// 更新现有客户记录
$customer = Customer::find($id);
$customer->email = '邦德@demo.com';
$customer = Customer::findOne($id);
$customer->email = 'james@example.com';
$customer->save(); // 等同于 $customer->update();
// 删除已有客户记录
$customer = Customer::find($id);
$customer = Customer::findOne($id);
$customer->delete();
// 删除多个年龄大于20,性别为男(Male)的客户记录
Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);
// 所有客户的age(年龄)字段加1:
Customer::updateAllCounters(['age' => 1]);
```
......@@ -267,40 +278,33 @@ Customer::updateAllCounters(['age' => 1]);
### 数据输入与有效性验证
Because Active Record extends from [[yii\base\Model]], it supports the same data input and validation features
as described in [Model](model.md). For example, you may declare validation rules by overwriting the
[[yii\base\Model::rules()|rules()]] method; you may massively assign user input data to an Active Record instance;
and you may call [[yii\base\Model::validate()|validate()]] to trigger data validation.
由于AR继承自[[yii\base\Model]],所以它同样也支持[Model](model.md)的数据输入、验证等特性。例如,你可以声明一个rules方法用来覆盖掉[[yii\base\Model::rules()|rules()]]里的;你也可以给AR实例批量赋值;你也可以通过调用[[yii\base\Model::validate()|validate()]]执行数据验证。
When you call `save()`, `insert()` or `update()`, these methods will automatically call [[yii\base\Model::validate()|validate()]].
If the validation fails, the corresponding data saving operation will be cancelled.
当你调用 `save()``insert()``update()` 这三个方法时,会自动调用[[yii\base\Model::validate()|validate()]]方法。如果验证失败,数据将不会保存进数据库。
The following example shows how to use an Active Record to collect/validate user input and save them into database:
下面的例子演示了如何使用AR 获取/验证用户输入的数据并将他们保存进数据库:
```php
// creating a new record
// 新建一条记录
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// the user input has been collected, validated and saved
// 获取用户输入的数据,验证并保存
}
// updating a record whose primary key is $id
$model = Customer::find($id);
// 更新主键为$id的AR
$model = Customer::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// the user input has been collected, validated and saved
// 获取用户输入的数据,验证并保存
}
```
### 读取默认值
Your table columns may be defined with default values. Sometimes, you may want to pre-populate your
Web form for an Active Record with these values. To do so, call the `loadDefaultValues()` method before
rendering the form:
你的表列也许定义了默认值。有时候,你可能需要在使用web表单的时候给AR预设一些值。如果你需要这样做,可以在显示表单内容前通过调用`loadDefaultValues()`方法来实现:
```php
$customer = new Customer();
$customer->loadDefaultValues();
......@@ -308,25 +312,40 @@ $customer->loadDefaultValues();
```
AR的生命周期
-----------------------------
理解AR的生命周期对于你操作数据库非常重要。生命周期通常都会有些典型的事件存在。对于开发AR的behaviors来说非常有用。
《《《待整理暂停线,下面的是以前翻译的,跟强哥前两天更新的不一样,还没有完全整理。
当你实例化一个新的AR对象时,我们将获得如下的生命周期:
1. constructor
2. [[yii\db\ActiveRecord::init()|init()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] 事件
当你通过 [[yii\db\ActiveRecord::find()|find()]] 方法查询数据时,每个AR实例都将有以下生命周期:
1. constructor
2. [[yii\db\ActiveRecord::init()|init()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] 事件
3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] 事件
当通过 [[yii\db\ActiveRecord::save()|save()]] 方法写入或者更新数据时, 我们将获得如下生命周期:
1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件
2. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] 事件
3. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] 或 [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] 事件
4. 执行实际的数据写入或更新
5. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 或 [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] 事件
数据输入和有效性验证
-------------------------
最后,当调用 [[yii\db\ActiveRecord::delete()|delete()]] 删除数据时, 我们将获得如下生命周期:
AR 继承了 [[yii\base\Model]] 的数据有效性验证和数据输入能力。有效性验证的方法会在数据保存时被调用。
数据的有效性验证会在 `save()` 方法执行时自动完成,如果验证失败,数据保存操作将取消。
1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] 事件
2. 执行实际的数据删除
3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: 会触发一个 [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] 事件
更多细节请参看本指南的 [Model](model.md) 部分。
查询关联的数据
------------------------
-----------------------------
使用 AR 方法也可以查询数据表的关联数据(如,选出表A的数据可以拉出表B的关联数据)。
有了 AR,
返回的关联数据连接就像连接关联主表的 AR 对象的属性一样。
......@@ -337,19 +356,21 @@ AR 蝏扳鈭 [[yii\base\Model]] 折颲
定义关联关系使用一个可以返回 [[yii\db\ActiveQuery]] 对象的 getter 方法,
[[yii\db\ActiveQuery]]对象有关联上下文的相关信息,因此可以只查询关联数据。
例如:
```php
class Customer extends \yii\db\ActiveRecord
{
public function getOrders()
{
// 客户和订单通过 Order.customer_id -> id 关联的一对多关系
// 客户和订单通过 Order.customer_id -> id 关联建立一对多关系
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
}
class Order extends \yii\db\ActiveRecord
{
// 订单和客户通过 Customer.id -> customer_id 关联的一对一关系
// 订单和客户通过 Customer.id -> customer_id 关联建立一对一关系
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
......@@ -373,7 +394,7 @@ class Order extends \yii\db\ActiveRecord
```php
// 取得客户的订单
$customer = Customer::find(1);
$customer = Customer::findOne(1);
$orders = $customer->orders; // $orders 是 Order 对象数组
```
......@@ -422,22 +443,12 @@ $orders = $customer->getBigOrders(200)->all();
(如果查询结果为空则返回空数组)。
中间表关联
--------------------------
有时,两个表通过中间表关联,定义这样的关联关系,
可以通过调用 [[yii\db\ActiveQuery::via()|via()]] 方法或 [[yii\db\ActiveQuery::viaTable()|viaTable()]] 方法来定制 [[yii\db\ActiveQuery]] 对象
举例而言,如果 `order` 表和 `item` 表通过中间表 `order_item`关联起来,
可以在 `Order` 类声明 `items` 关联关系取代中间表:
《《《待处理标识符:上面两句狗屁不通的话需要参照原文修订下。
中间关联表
-----------------
有时,两个表通过中间表关联,定义这样的关联关系, 可以通过调用 [[yii\db\ActiveQuery::via()|via()]] 方法或 [[yii\db\ActiveQuery::viaTable()|viaTable()]] 方法来定制 [[yii\db\ActiveQuery]] 对象 。
举例而言,如果 `order` 表和 `item` 表通过中间表 `order_item` 关联起来, 可以在 `Order` 类声明 `items` 关联关系取代中间表:
```php
class Order extends \yii\db\ActiveRecord
......@@ -450,9 +461,7 @@ class Order extends \yii\db\ActiveRecord
}
```
两个方法是相似的,除了
[[yii\db\ActiveQuery::via()|via()]] 方法的第一个参数是使用 AR 类中定义的关联名。
以上方法取代了中间表,等价于:
两个方法是相似的,除了 [[yii\db\ActiveQuery::via()|via()]] 方法的第一个参数是使用 AR 类中定义的关联名。 以上方法取代了中间表,等价于:
```php
class Order extends \yii\db\ActiveRecord
......@@ -470,19 +479,14 @@ class Order extends \yii\db\ActiveRecord
}
```
[pivot table]: http://en.wikipedia.org/wiki/Pivot_table "Pivot table(既中间表,英文,维基百科)"
延迟加载和即时加载(又称惰性加载与贪婪加载)
----------------------
-----------------
如前所述,当你第一次连接关联对象时, AR 将执行一个数据库查询
来检索请求数据并填充到关联对象的相应属性。
如果再次连接相同的关联对象,不再执行任何查询语句,这种数据库查询的执行方法称为“延迟加载”。如:
如前所述,当你第一次连接关联对象时, AR 将执行一个数据库查询 来检索请求数据并填充到关联对象的相应属性。 如果再次连接相同的关联对象,不再执行任何查询语句,这种数据库查询的执行方法称为“延迟加载”。如:
```php
// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::find(1);
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// 没有 SQL 语句被执行
......@@ -502,11 +506,9 @@ foreach ($customers as $customer) {
}
```
假设数据库查出的客户超过100个,以上代码将执行多少条 SQL 语句?
101 条!第一条 SQL 查询语句取回100个客户,然后,
每个客户要执行一条 SQL 查询语句以取回该客户的所有订单。
假设数据库查出的客户超过100个,以上代码将执行多少条 SQL 语句? 101 条!第一条 SQL 查询语句取回100个客户,然后, 每个客户要执行一条 SQL 查询语句以取回该客户的所有订单。
为解决以上性能问题,可以通过调用 [[yii\db\ActiveQuery::with()]] 方法使用*即时加载*解决。
为解决以上性能问题,可以通过调用 [[yii\db\ActiveQuery::with()]] 方法使用即时加载解决。
```php
// SQL executed: SELECT * FROM customer LIMIT 100;
......@@ -522,13 +524,8 @@ foreach ($customers as $customer) {
```
如你所见,同样的任务只需要两个 SQL 语句。
> 须知:通常,即时加载 N 个关联关系而通过 `via()` 或者 `viaTable()` 定义了 M 个关联关系,
将有 1+M+N 条 SQL 查询语句被执行:一个查询取回主表行数,
一个查询给每一个 (M) 中间表,一个查询给每个 (N) 关联表。
> 注意:当用即时加载定制 `select()` 时,确保连接
到关联模型的列都被包括了,否则,关联模型不会载入。如:
>须知:通常,即时加载 N 个关联关系而通过 via() 或者 viaTable() 定义了 M 个关联关系, 将有 1+M+N 条 SQL 查询语句被执行:一个查询取回主表行数, 一个查询给每一个 (M) 中间表,一个查询给每个 (N) 关联表。
注意:当用即时加载定制 select() 时,确保连接 到关联模型的列都被包括了,否则,关联模型不会载入。如:
```php
$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
......@@ -536,16 +533,15 @@ $orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
```
有时候,你想自由的自定义关联查询,
延迟加载和即时加载都可以实现,如:
有时候,你想自由的自定义关联查询,延迟加载和即时加载都可以实现,如:
```php
$customer = Customer::find(1);
// lazy loading: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
$customer = Customer::findOne(1);
// 延迟加载: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
$orders = $customer->getOrders()->where('subtotal>100')->all();
// eager loading: SELECT * FROM customer LIMIT 100
// SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
// 时加载: SELECT * FROM customer LIMIT 100
// SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
$customers = Customer::find()->limit(100)->with([
'orders' => function($query) {
$query->andWhere('subtotal>100');
......@@ -553,12 +549,10 @@ $customers = Customer::find()->limit(100)->with([
])->all();
```
逆关系
-----------------
关联关系通常成对定义,如, `Customer` 可以有个名为 `orders` 关联项,
`Order` 也有个名为`customer` 的关联项:
关联关系通常成对定义,如:Customer 可以有个名为 orders 关联项, 而 Order 也有个名为customer 的关联项:
```php
class Customer extends ActiveRecord
......@@ -580,25 +574,22 @@ class Order extends ActiveRecord
}
```
如果我们执行以下查询,可以发现订单的 `customer`
找到这些订单的客户对象并不是同一个。连接 `customer->orders` 将触发一条 SQL 语句
而连接一个订单的 `customer` 将触发另一条 SQL 语句。
如果我们执行以下查询,可以发现订单的 customer 和 找到这些订单的客户对象并不是同一个。连接 customer->orders 将触发一条 SQL 语句 而连接一个订单的 customer 将触发另一条 SQL 语句。
```php
// SELECT * FROM customer WHERE id=1
$customer = Customer::find(1);
// echoes "not equal"
$customer = Customer::findOne(1);
// 输出 "不相同"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
echo 'equal';
echo '相同';
} else {
echo 'not equal';
echo '相同';
}
```
为避免多余执行的后一条语句,我们可以为 `customer``orders` 关联关系定义相反的关联关系,
通过调用 [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] 方法可以实现。
为避免多余执行的后一条语句,我们可以为 customer或 orders 关联关系定义相反的关联关系,通过调用 [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] 方法可以实现。
```php
class Customer extends ActiveRecord
......@@ -615,18 +606,17 @@ class Customer extends ActiveRecord
```php
// SELECT * FROM customer WHERE id=1
$customer = Customer::find(1);
$customer = Customer::findOne(1);
// 输出相同
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
echo 'equal';
echo '相同';
} else {
echo 'not equal';
echo '相同';
}
```
以上我们展示了如何在延迟加载中使用相对关联关系,
相对关系也可以用在即时加载中:
以上我们展示了如何在延迟加载中使用相对关联关系, 相对关系也可以用在即时加载中:
```php
// SELECT * FROM customer
......@@ -634,15 +624,13 @@ if ($customer->orders[0]->customer === $customer) {
$customers = Customer::find()->with('orders')->all();
// 输出相同
if ($customers[0]->orders[0]->customer === $customers[0]) {
echo 'equal';
echo '相同';
} else {
echo 'not equal';
echo '相同';
}
```
> 注意:相对关系不能在包含中间表的关联关系中定义。
> 即是,如果你的关系是通过[[yii\db\ActiveQuery::via()|via()]] 或 [[yii\db\ActiveQuery::viaTable()|viaTable()]]方法定义的,
> 就不能调用[[yii\db\ActiveQuery::inverseOf()]]方法了。
>注意:相对关系不能在包含中间表的关联关系中定义。 即是,如果你的关系是通过[[yii\db\ActiveQuery::via()|via()]] 或 [[yii\db\ActiveQuery::viaTable()|viaTable()]]方法定义的, 就不能调用[[yii\db\ActiveQuery::inverseOf()]]方法了。
JOIN 类型关联查询
......@@ -661,7 +649,6 @@ $orders = Order::find()->innerJoinWith('books')->all();
```
以上,方法 [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] 是访问 `INNER JOIN` 类型的 [[yii\db\ActiveQuery::joinWith()|joinWith()]] 的快捷方式。
可以连接一个或多个关联关系,可以自由使用查询条件到关联查询,
也可以嵌套连接关联查询。如:
......@@ -743,50 +730,40 @@ $users = User::find()->joinWith('books')->all();
```php
// SELECT * FROM user WHERE id=10
$user = User::find(10);
$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;
```
关联表操作
--------------------------
-----------------
ActiveRecord 提供下列两个方法来建立或移除
两个 ActiveRecord 对象之间的关系。
AR 提供了下面两个方法用来建立和解除两个关联对象之间的关系:
- [[yii\db\ActiveRecord::link()|link()]]
- [[yii\db\ActiveRecord::unlink()|unlink()]]
* [[yii\db\ActiveRecord::link()|link()]]
* [[yii\db\ActiveRecord::unlink()|unlink()]]
如,给定一个客户和一个新订单,我们可以使用以下代码
把订单和客户关联起来:
例如,给定一个customer和order对象,我们可以通过下面的代码使得customer对象拥有order对象:
```php
$customer = Customer::find(1);
$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);
```
上面调用的 [[yii\db\ActiveRecord::link()|link()]] 会设置 order 的 `customer_id` 为主键
$customer 的值,然后调用 [[yii\db\ActiveRecord::save()|save()]] 方法保存订单到数据库。
[[yii\db\ActiveRecord::link()|link()]] 调用上述将设置 customer_id 的顺序是 $customer 的主键值,然后调用 [[yii\db\ActiveRecord::save()|save()]] 要将顺序保存到数据库中。
作用域
------
--------------
当调用[[yii\db\ActiveRecord::find()|find()]]或[[yii\db\ActiveRecord::findBySql()|findBySql()]]方法,
将返回[[yii\db\ActiveQuery|ActiveQuery]] 实例。
你也可以调用其他方法,如 [[yii\db\ActiveQuery::where()|where()]], [[yii\db\ActiveQuery::orderBy()|orderBy()]],
以更细化查询条件。
当你调用[[yii\db\ActiveRecord::find()|find()]] 或 [[yii\db\ActiveRecord::findBySql()|findBySql()]]方法时,将会返回一个[[yii\db\ActiveQuery|ActiveQuery]]实例。之后,你可以调用其他查询方法,如 [[yii\db\ActiveQuery::where()|where()]],[[yii\db\ActiveQuery::orderBy()|orderBy()]], 进一步的指定查询条件。
有可能需要不同地方多次调用同一个查询方法集合,这种情况,
可以考虑定义一个所谓的作用域(*scopes*),作用域本质上也是一个方法,定义在一个自定的查询类中,这个类
调用了一系列的查询方法来修正查询对象,使用作用域方法如同调用一个普通查询方法一样。
有时候你可能需要在不同的地方使用相同的查询方法。如果出现这种情况,你应该考虑定义所谓的作用域。作用域是本质上要求一组的查询方法来修改查询对象的自定义查询类中定义的方法。 之后你就可以像使用普通方法一样使用作用域。
定义一个作用域方法需要两个步骤,首先为模型创建一个自定的查询类并在此类定义必须的作用域方法。
如,为 `Comment` 模型创建 `CommentQuery` 类,
定义`active()`作用域方法如下:
只需两步即可定义一个作用域。首先给你的model创建一个自定义的查询类,在此类中定义的所需的范围方法。例如,给Comment模型创建一个 CommentQuery类,然后在CommentQuery类中定义一个active()的方法为作用域,像下面的代码:
```php
namespace app\models;
......@@ -803,14 +780,13 @@ class CommentQuery extends ActiveQuery
}
```
点是:
点:
1. 类必须继承自 `yii\db\ActiveQuery`其子类。
2.方法必须是公开的并返回 `$this` 以便方法链成立。可以接收参数。
3.确认 [[yii\db\ActiveQuery]] 方法对修改查询条件非常有用。
1. 类必须继承 yii\db\ActiveQuery (或者是其他的 ActiveQuery ,比如 yii\mongodb\ActiveQuery)。
2. 必须是一个public类型的方法且必须返回 $this 实现链式操作。可以传入参数。
3. 检查 [[yii\db\ActiveQuery]] 对于修改查询条件是非常有用的方法。
其次,覆写 [[yii\db\ActiveRecord::createQuery()]] 方法以便可以使用自定的查询类而不是默认的 [[yii\db\ActiveQuery|ActiveQuery]] 类。
以下是示例:
其次,覆盖[[yii\db\ActiveRecord::find()]] 方法使其返回自定义的查询对象而不是常规的[[yii\db\ActiveQuery|ActiveQuery]]。对于上述例子,你需要编写如下代码:
```php
namespace app\models;
......@@ -819,22 +795,25 @@ use yii\db\ActiveRecord;
class Comment extends ActiveRecord
{
public static function createQuery($config = [])
/**
* @inheritdoc
* @return CommentQuery
*/
public static function find()
{
$config['modelClass'] = get_called_class();
return new CommentQuery($config);
return new CommentQuery(get_called_class());
}
}
```
就这样。现在你可以使用自定的作用域方法了:
就这,现在你可以使用自定义的作用域方法了:
```php
$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
```
当定义关联关系时也可以使用作用域,如:
你也能在定义的关联里使用作用域方法,比如:
```php
class Post extends \yii\db\ActiveRecord
......@@ -847,7 +826,7 @@ class Post extends \yii\db\ActiveRecord
}
```
或当执行关联查询时使用作用域传输:
或者在执行关联查询的时候使用(on-the-fly 是啥?):
```php
$posts = Post::find()->with([
......@@ -857,66 +836,30 @@ $posts = Post::find()->with([
])->all();
```
### 让 IDE 更好地支持
为了让现代 IDE 自动完成更智能,你需要为一些模型和查询方法覆写返回类型,
如下:
```php
/**
* @method \app\models\CommentQuery|static|null find($q = null) static
* @method \app\models\CommentQuery findBySql($sql, $params = []) static
*/
class Comment extends ActiveRecord
{
// ...
}
```
```php
/**
* @method \app\models\Comment|array|null one($db = null)
* @method \app\models\Comment[]|array all($db = null)
*/
class CommentQuery extends ActiveQuery
{
// ...
}
```
### 默认作用域
如果你以前曾用过 Yii 1.1,你已经了解一个缺省作用域的概念。缺省作用域就是对所有的数据库查询生效的作用域。
你可以通过覆写 [[yii\db\ActiveRecord::createQuery()]] 方法来自定义缺省作用域,如
如果你之前用过 Yii 1.1 就应该知道默认作用域的概念。一个默认的作用域可以作用于所有查询。你可以很容易的通过重写[[yii\db\ActiveRecord::find()]]方法来定义一个默认作用域,例如:
```php
public static function createQuery($config = [])
public static function find()
{
$config['modelClass'] = get_called_class();
return (new ActiveQuery($config))->where(['deleted' => false]);
return parent::find()->where(['deleted' => false]);
}
```
注意现在你的所有查询都不能使用[[yii\db\ActiveQuery::where()|where()]]方法,
只能使用[[yii\db\ActiveQuery::where()|where()]]和[[yii\db\ActiveQuery::orWhere()|orWhere()]]方法,
以避免覆写了缺省条件。
注意,你之后所有的查询都不能用 [[yii\db\ActiveQuery::where()|where()]],但是可以用 [[yii\db\ActiveQuery::andWhere()|andWhere()]] 和 [[yii\db\ActiveQuery::orWhere()|orWhere()]],他们不会覆盖掉默认作用域。(译者注:如果你要使用默认作用域,就不能在 xxx::find()后使用where()方法,你必须使用andXXX()或者orXXX()系的方法,否则默认作用域不会起效果,至于原因,打开where()方法的代码一看便知)
处理
------------------------
事务操作
------------------
一些 DB 操作是相关的且被同时执行
执行几个相关联的数据库操作的时候
TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226
TODO: FIXME: WIP, TBD, [https://github.com/yiisoft/yii2/issues/226](https://github.com/yiisoft/yii2/issues/226)
,
[[yii\db\ActiveRecord::afterSave()|afterSave()]], [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]] 和 [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]生命周期方法.
开发者的解决方案是通过数据库事务包覆写[[yii\db\ActiveRecord::save()|save()]]方法
甚至在控制器功能方法中使用事务,这个解决方式严格来说不是最佳实践
(违背了 “小控制器大模型”的基本规则)。
, [[yii\db\ActiveRecord::afterSave()|afterSave()]], [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]] and/or [[yii\db\ActiveRecord::afterDelete()|afterDelete()]] 生命周期周期方法(life cycle methods 我觉得这句翻译成“模板方法”会不会更好点?)。开发者可以通过重写[[yii\db\ActiveRecord::save()|save()]]方法然后在控制器里使用事务操作,严格地说是似乎不是一个好的做法 (召回"瘦控制器 / 肥模型"基本规则)。
以下就是这些方式(**不要** 使用,除非你确定你真的需要这么做)。模型:
这些方法在这里(如果你不明白自己实际在干什么,请不要使用他们),Models:
```php
class Feature extends \yii\db\ActiveRecord
......@@ -925,7 +868,7 @@ class Feature extends \yii\db\ActiveRecord
public function getProduct()
{
return $this->hasOne(Product::className(), ['product_id' => 'id']);
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
}
......@@ -935,7 +878,7 @@ class Product extends \yii\db\ActiveRecord
public function getFeatures()
{
return $this->hasMany(Feature::className(), ['id' => 'product_id']);
return $this->hasMany(Feature::className(), ['product_id' => 'id']);
}
}
```
......@@ -943,7 +886,6 @@ class Product extends \yii\db\ActiveRecord
重写 [[yii\db\ActiveRecord::save()|save()]] 方法:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
......@@ -952,8 +894,9 @@ class ProductController extends \yii\web\Controller
}
}
```
(译者注:我觉得上面应该是原手册里的bug)
制器层面使用事务处理
控制器层使用事务:
```php
class ProductController extends \yii\web\Controller
......@@ -965,7 +908,7 @@ class ProductController extends \yii\web\Controller
}
```
代替以上弱相关的方法,可以使用原子级场景和操作特性。
作为这些脆弱方法的替代,你应该使用原子操作方案特性。
```php
class Feature extends \yii\db\ActiveRecord
......@@ -1023,6 +966,18 @@ class Product extends \yii\db\ActiveRecord
}
```
Controller里的代码将变得很简洁:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
控制器非常简洁:
```php
......@@ -1036,17 +991,17 @@ class ProductController extends \yii\web\Controller
```
乐观锁(Optimistic Locks)
----------------
-------------------
TODO
被污染属性
----------------
-------------------
TODO
另见
--------
-------------------
- [模型(Model)](model.md)
- [[yii\db\ActiveRecord]]
- [[yii\db\ActiveRecord]]
\ No newline at end of file
......@@ -11,7 +11,7 @@
安装
------------
Yii 2.0 完全拥抱 [Composer](https://getcomposer.org/),它是事实上的 PHP 依赖管理工具。核心框架以及扩展的安装都通过 Composer 来处理。想要了解更多如何安装 Yii 2.0 请参阅本指南的 [安装 Yii](start-installation.md) 章节。如果你想创建新扩展,或者把你已有的 Yii 1.1 的扩展改写成兼容 2.0 的版本,你可以参考 [创建扩展](extend-creating-extensions.md) 章节。
Yii 2.0 完全拥抱 [Composer](https://getcomposer.org/),它是事实上的 PHP 依赖管理工具。核心框架以及扩展的安装都通过 Composer 来处理。想要了解更多如何安装 Yii 2.0 请参阅本指南的 [安装 Yii](start-installation.md) 章节。如果你想创建新扩展,或者把你已有的 Yii 1.1 的扩展改写成兼容 2.0 的版本,你可以参考 [创建扩展](structure-extensions.md#creating-extensions) 章节。
PHP 需求
......@@ -158,7 +158,7 @@ public function scenarios()
控制器(Controller)
-----------
Yii 2.0 使用 [[yii\web\Controller]] 作为控制器的基类,类似于 1.1 的 `CWebController`。使用 [[yii\base\Action]] 作为操作类的基类。
Yii 2.0 使用 [[yii\web\Controller]] 作为控制器的基类,它类似于 1.1 的 `CController`。使用 [[yii\base\Action]] 作为操作类的基类。
这些变化最明显的影响是,当你在写控制器操作的代码时,应该返回(return)要渲染的内容而不是输出(echo)它:
......
......@@ -7,7 +7,7 @@ Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 W
Yii 最适合做什么?
---------------------
Yii 是一个通用的 Web 编程框架,即可以用于开发各种基于 PHP 的 Web 应用。因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用,如门户网站、社区、内容管理系统(CMS)、电子商务项目和 RESTful Web 服务等。
Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用,如门户网站、社区、内容管理系统(CMS)、电子商务项目和 RESTful Web 服务等。
Yii 和其他框架相比呢?
-------------------------------------------
......@@ -15,9 +15,11 @@ Yii 和其他框架相比呢?
如果你有其它框架使用经验,那么你会很开心看到 Yii 所做的努力:
- 和其他 PHP 框架类似,Yii 实现了 MVC(Model-View-Controller)设计模式并基于该模式组织代码。
- Yii 的代码简洁优雅,这是它的编程哲学。它永远不会为了严格遵照某种设计模式而对代码进行过度的设计。
- Yii 是一个全栈框架,提供了大量久经考验,开箱即用的特性:对关系型和 NoSQL 数据库都提供了查询生成器和 ActiveRecord;RESTful API 的开发支持;多层缓存支持,等等。
- Yii 非常易于扩展。你可以自定义或替换几乎任何一处核心代码。你还会受益于它坚实可靠的扩展架构,使用、再开发或再发布扩展。
- Yii 的代码简洁优雅,这是它的编程哲学。它永远不会为了刻板地遵照某种设计模式而对代码进行过度的设计。
- Yii 是一个全栈框架,提供了大量久经考验,开箱即用的特性:对关系型和 NoSQL 数据库都提供了查询生成器和
ActiveRecord;RESTful API 的开发支持;多层缓存支持,等等。
- Yii 非常易于扩展。你可以自定义或替换几乎任何一处核心代码。你还会受益于 Yii
坚实可靠的扩展架构,使用、再开发或再发布扩展。
- 高性能始终是 Yii 的首要目标之一。
Yii 不是一场独角戏,它由一个[强大的开发者团队](http://www.yiiframework.com/about/)提供支持,也有一个庞大的专家社区,持续不断地对 Yii 的开发作出贡献。Yii 开发者团队始终对 Web 开发趋势和其他框架及项目中的最佳实践和特性保持密切关注,那些有意义的最佳实践及特性会被不定期的整合进核心框架中,并提供简单优雅的接口。
......
运行机制概述
========
每一次 Yii 应用开始处理 HTTP 请求时,它都会进行一个近似的流程。
1. 用户提交指向 [入口脚本](structure-entry-scripts.md) `web/index.php` 的请求。
2. 入口脚本会加载 [配置数组](concept-configurations.md) 并创建一个
[应用](structure-applications.md) 实例用于处理该请求。
3. 应用会通过 [request(请求)](runtime-requests.md) 应用组件解析被请求的 [路由](runtime-routing.md)
4. 应用创建一个 [controller(控制器)](structure-controllers.md) 实例具体处理请求。
5. 控制器会创建一个 [action(动作)](structure-controllers.md) 实例并为该动作执行相关的 Filters(访问过滤器)。
6. 如果任何一个过滤器验证失败,该动作会被取消。
7. 如果全部的过滤器都通过,该动作就会被执行。
8. 动作会加载一个数据模型,一般是从数据库中加载。
9. 动作会渲染一个 View(视图),并为其提供所需的数据模型。
10. 渲染得到的结果会返回给 [response(响应)](runtime-responses.md) 应用组件。
11. 响应组件会把渲染结果发回给用户的浏览器。
下面的示意图展示了应用是如何处理一个请求的。
![Request Lifecycle](images/application-lifecycle.png)
在这个版块中,我们会更加详细地描述某些步骤的具体运作。
......@@ -104,7 +104,7 @@ class SiteController extends Controller
> 补充:表达式 `Yii::$app` 代表[应用](structure-applications.md)实例,它是一个全局可访问的单例。同时它也是一个[服务定位器](concept-service-locator.md),能提供 `request`,`response`,`db` 等等特定功能的组件。在上面的代码里就是使用 `request` 组件来访问应用实例收到的 `$_POST` 数据。
用户提交表单后,操作将会渲染一个名为 `entry-confirm` 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误,`entry` 视图将会渲染输出,连同表单一起输出的还有验证错误的详细信息。
用户成功提交表单后,操作将会渲染一个名为 `entry-confirm` 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误,`entry` 视图将会渲染输出,连同表单一起输出的还有验证错误的详细信息。
> 注意:在这个简单例子里我们只是呈现了有效数据的确认页面。实践中你应该考虑使用 [[yii\web\Controller::refresh()|refresh()]] 或 [[yii\web\Controller::redirect()|redirect()]] 去避免[表单重复提交问题](http://en.wikipedia.org/wiki/Post/Redirect/Get)。
......
安装 Yii
==============
你可以通过两种方式安装 Yii:使用 [Composer](http://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](extend-creating-extensions.md)或更新 Yii 了。
你可以通过两种方式安装 Yii:使用 [Composer](http://getcomposer.org/) 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的[扩展](structure-extensions.md)或更新 Yii 了。
> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。
......@@ -18,15 +18,24 @@
如果遇到任何问题或者想更深入地学习 Composer,请参考 [Composer 文档(英文)](https://getcomposer.org/doc/)[Composer 中文](https://github.com/5-say/composer-doc-cn)
如果你已经安装有 Composer 请确保使用的是最新版本,你可以用 `composer self-update` 命令更新 Composer 为最新版本。
Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下命令即可安装 Yii :
composer global require "fxp/composer-asset-plugin:1.0.0-beta3"
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
第一条命令安装 [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/),它是通过 Composer 管理 bower 和 npm 包所必须的,此命令全局应用,只需执行一次即可。
第二条命令会将 Yii 安装在名为 `basic` 的目录中。
第一条命令安装 [Composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/),它是通过 Composer 管理 bower 和 npm 包所必须的,此命令全局生效,一劳永逸。
第二条命令会将 Yii 安装在名为 `basic` 的目录中,你也可以随便选择其他名称。
> 注意:在安装过程中 Composer 可能会询问你 GitHub 账户的登录信息,因为可能在使用中超过了 GitHub API
(对匿名用户的)使用限制。因为 Composer 需要为所有扩展包从 GitHub
中获取大量信息,所以超限非常正常。(译者注:也意味着作为程序猿没有 GitHub 账号,就真不能愉快地玩耍了)登陆 GitHub
之后可以得到更高的 API 限额,这样 Composer 才能正常运行。更多细节请参考 [Composer
文档](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens)(该段 Composer
中文文档[期待您的参与](https://github.com/5-say/composer-doc-cn/blob/master/cn-introduction/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens))。
> 技巧:如果你想安装 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)):
> 技巧:如果你想安装 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
>
......@@ -36,9 +45,9 @@ Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下
通过归档文件安装 <a name="installing-from-archive-file"></a>
-------------------------------
通过归档文件安装 Yii 包括个步骤:
通过归档文件安装 Yii 包括个步骤:
1.[yiiframework.com](http://www.yiiframework.com/download/yii2-basic) 下载归档文件。
1.[yiiframework.com](http://www.yiiframework.com/download/) 下载归档文件。
2. 将下载的文件解压缩到 Web 目录中。
3. 修改 `config/web.php` 文件,给 `cookieValidationKey` 配置项添加一个密钥(若你通过 Composer 安装,则此步骤会自动完成):
......@@ -108,8 +117,8 @@ http://localhost/basic/web/index.php
DocumentRoot "path/to/basic/web"
<Directory "path/to/basic/web">
# 开启 mod_rewrite 用于美化 URL 功能的支持(译者注:对应 pretty URL 选项)
RewriteEngine on
# 如果请求的是真实存在的文件或目录,直接访问
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
......
......@@ -12,9 +12,11 @@
- 书籍
* [扩展](http://www.yiiframework.com/extensions/):Yii 拥有数以千计用户提供的扩展,这些扩展能非常方便的插入到应用中,使你的应用开发过程更加方便快捷。
* 社区
- [官方论坛](http://www.yiiframework.com/forum/)
- [GitHub](https://github.com/yiisoft/yii2)
- [Facebook](https://www.facebook.com/groups/yiitalk/)
- [Twitter](https://twitter.com/yiiframework)
- [LinkedIn](https://www.linkedin.com/groups/yii-framework-1483367)
- 官方论坛:<http://www.yiiframework.com/forum/>
- IRC 聊天室:Freenode 网络上的 #yii 频道 (<irc://irc.freenode.net/yii>)(使用英文哦,无需反馈上游的问题可以加
QQ-Yii2中国交流群)
- GitHub:<https://github.com/yiisoft/yii2>
- Facebook:<https://www.facebook.com/groups/yiitalk/>
- Twitter:<https://twitter.com/yiiframework>
- LinkedIn:<https://www.linkedin.com/groups/yii-framework-1483367>
过滤器
=======
过滤器是 [控制器 动作](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