行为
行为是 yii\base\Behavior 或其子类的实例。行为,也称为 mixins,可以无须改变类继承关系即可增强一个已有的 yii\base\Component 类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的事件,从而自定义或调整组件正常执行的代码。
定义行为
要定义行为,通过继承 yii\base\Behavior 或其子类来建立一个类。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > namespace </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >components</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >base</span>\<span style= "box-sizing: border-box;" >Behavior</span>;
<span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > class </span> <span style= "box-sizing: border-box;" >MyBehavior</span> <span style= "box-sizing: border-box;" > extends </span> <span style= "box-sizing: border-box;" >Behavior</span></span>{
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" > $prop1 </span>;
<span style= "box-sizing: border-box;" > private </span> <span style= "box-sizing: border-box;" > $_prop2 </span>;
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >getProp2</span><span style= "box-sizing: border-box;" >()</span>
</span>{
<span style= "box-sizing: border-box;" > return </span> <span style= "box-sizing: border-box;" > $this </span>->_prop2;
}
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >setProp2</span><span style= "box-sizing: border-box;" >(<span style= "box-sizing: border-box;" > $value </span>)</span>
</span>{
<span style= "box-sizing: border-box;" > $this </span>->_prop2 = <span style= "box-sizing: border-box;" > $value </span>;
}
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >foo</span><span style= "box-sizing: border-box;" >()</span>
</span>{
<span style= "box-sizing: border-box;" >
}
}
</code>
|
以上代码定义了行为类 app\components\MyBehavior
并为要附加行为的组件提供了两个属性 prop1
、 prop2
和一个方法 foo()
。注意属性 prop2
是通过 getter getProp2()
和 setter setProp2()
定义的。能这样用是因为 yii\base\Object 是 yii\base\Behavior 的祖先类,此祖先类支持用 getter 和 setter 方法定义属性
提示:在行为内部可以通过 yii\base\Behavior::owner 属性访问行为已附加的组件。
处理事件
如果要让行为响应对应组件的事件触发,就应覆写 yii\base\Behavior::events() 方法,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > namespace </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >components</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >db</span>\<span style= "box-sizing: border-box;" >ActiveRecord</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >base</span>\<span style= "box-sizing: border-box;" >Behavior</span>;
<span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > class </span> <span style= "box-sizing: border-box;" >MyBehavior</span> <span style= "box-sizing: border-box;" > extends </span> <span style= "box-sizing: border-box;" >Behavior</span></span>{
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >events</span><span style= "box-sizing: border-box;" >()</span>
</span>{
<span style= "box-sizing: border-box;" > return </span> [
ActiveRecord::EVENT_BEFORE_VALIDATE => <span style= "box-sizing: border-box;" > 'beforeValidate' </span>,
];
}
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >beforeValidate</span><span style= "box-sizing: border-box;" >(<span style= "box-sizing: border-box;" > $event </span>)</span>
</span>{
<span style= "box-sizing: border-box;" >
}
}
</code>
|
yii\base\Behavior::events() 方法返回事件列表和相应的处理器。上例声明了 yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE 事件和它的处理器 beforeValidate()
。当指定一个事件处理器时,要使用以下格式之一:
- 指向行为类的方法名的字符串,如上例所示;
- 对象或类名和方法名的数组,如
[$object, 'methodName']
;
- 匿名方法。
处理器的格式如下,其中 $event
指向事件参数。关于事件的更多细节请参考事件:
1 2 3 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >(<span style= "box-sizing: border-box;" > $event </span>)</span> </span>{
}
</code>
|
附加行为
可以静态或动态地附加行为到yii\base\Component。前者在实践中更常见。
要静态附加行为,覆写行为要附加的组件类的 yii\base\Component::behaviors() 方法即可。yii\base\Component::behaviors() 方法应该返回行为配置列表。每个行为配置可以是行为类名也可以是配置数组。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > namespace </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >models</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >db</span>\<span style= "box-sizing: border-box;" >ActiveRecord</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >components</span>\<span style= "box-sizing: border-box;" >MyBehavior</span>;
<span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > class </span> <span style= "box-sizing: border-box;" >User</span> <span style= "box-sizing: border-box;" > extends </span> <span style= "box-sizing: border-box;" >ActiveRecord</span></span>{
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >behaviors</span><span style= "box-sizing: border-box;" >()</span>
</span>{
<span style= "box-sizing: border-box;" > return </span> [
<span style= "box-sizing: border-box;" >
MyBehavior::className(),
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > 'myBehavior2' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" >
[
<span style= "box-sizing: border-box;" > 'class' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" > 'prop1' </span> => <span style= "box-sizing: border-box;" > 'value1' </span>,
<span style= "box-sizing: border-box;" > 'prop2' </span> => <span style= "box-sizing: border-box;" > 'value2' </span>,
],
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > 'myBehavior4' </span> => [
<span style= "box-sizing: border-box;" > 'class' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" > 'prop1' </span> => <span style= "box-sizing: border-box;" > 'value1' </span>,
<span style= "box-sizing: border-box;" > 'prop2' </span> => <span style= "box-sizing: border-box;" > 'value2' </span>,
]
];
}
}
</code>
|
通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为命名行为。上例中,有两个命名行为:myBehavior2
和myBehavior4
。如果行为没有指定名称就是匿名行为。
要动态附加行为,在对应组件里调用 yii\base\Component::attachBehavior() 方法即可,如:
1 2 3 4 5 6 7 8 9 10 11 12 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >components</span>\<span style= "box-sizing: border-box;" >MyBehavior</span>;
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > 'class' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" > 'prop1' </span> => <span style= "box-sizing: border-box;" > 'value1' </span>,
<span style= "box-sizing: border-box;" > 'prop2' </span> => <span style= "box-sizing: border-box;" > 'value2' </span>,
]);
</code>
|
可以通过 yii\base\Component::attachBehaviors() 方法一次附加多个行为:
1 2 3 4 5 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $component </span>->attachBehaviors([
<span style= "box-sizing: border-box;" > 'myBehavior1' </span> => <span style= "box-sizing: border-box;" > new </span> MyBehavior, <span style= "box-sizing: border-box;" >
MyBehavior::className(), <span style= "box-sizing: border-box;" >
]);
</code>
|
还可以通过配置去附加行为:
1 2 3 4 5 6 7 8 9 10 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" >[
<span style= "box-sizing: border-box;" > 'as myBehavior2' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" > 'as myBehavior3' </span> => [
<span style= "box-sizing: border-box;" > 'class' </span> => MyBehavior::className(),
<span style= "box-sizing: border-box;" > 'prop1' </span> => <span style= "box-sizing: border-box;" > 'value1' </span>,
<span style= "box-sizing: border-box;" > 'prop2' </span> => <span style= "box-sizing: border-box;" > 'value2' </span>,
],
]
</code>
|
详情请参考配置章节。
使用行为
使用行为,必须像前文描述的一样先把它附加到 yii\base\Component 类或其子类。一旦行为附加到组件,就可以直接使用它。
行为附加到组件后,可以通过组件访问一个行为的公共成员变量或 getter 和 setter 方法定义的属性:
1 2 3 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > $component </span>->prop1 = <span style= "box-sizing: border-box;" > $value </span>;
</code>
|
类似地也可以调用行为的公共方法:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" >
</code>
|
如你所见,尽管 $component
未定义 prop1
和 foo()
,它们用起来也像组件自己定义的一样。
如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么首先附加上的行为在属性或方法被访问时有优先权。
附加行为到组件时的命名行为,可以使用这个名称来访问行为对象,如下所示:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $behavior </span> = <span style= "box-sizing: border-box;" > $component </span>->getBehavior(<span style= "box-sizing: border-box;" > 'myBehavior' </span>);
</code>
|
也能获取附加到这个组件的所有行为:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $behaviors </span> = <span style= "box-sizing: border-box;" > $component </span>->getBehaviors();
</code>
|
移除行为
要移除行为,可以调用 yii\base\Component::detachBehavior() 方法用行为相关联的名字实现:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $component </span>->detachBehavior(<span style= "box-sizing: border-box;" > 'myBehavior1' </span>);
</code>
|
也可以移除全部行为:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $component </span>->detachBehaviors();
</code>
|
使用 TimestampBehavior
最后以 yii\behaviors\TimestampBehavior 的讲解来结尾,这个行为支持在 yii\db\ActiveRecord 存储时自动更新它的时间戳属性。
首先,附加这个行为到计划使用该行为的 yii\db\ActiveRecord 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > namespace </span> <span style= "box-sizing: border-box;" >app</span>\<span style= "box-sizing: border-box;" >models</span>\<span style= "box-sizing: border-box;" >User</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >db</span>\<span style= "box-sizing: border-box;" >ActiveRecord</span>;
<span style= "box-sizing: border-box;" > use </span> <span style= "box-sizing: border-box;" >yii</span>\<span style= "box-sizing: border-box;" >behaviors</span>\<span style= "box-sizing: border-box;" >TimestampBehavior</span>;
<span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > class </span> <span style= "box-sizing: border-box;" >User</span> <span style= "box-sizing: border-box;" > extends </span> <span style= "box-sizing: border-box;" >ActiveRecord</span></span>{
<span style= "box-sizing: border-box;" >
<span style= "box-sizing: border-box;" > public </span> <span style= "box-sizing: border-box;" ><span style= "box-sizing: border-box;" > function </span> <span style= "box-sizing: border-box;" >behaviors</span><span style= "box-sizing: border-box;" >()</span>
</span>{
<span style= "box-sizing: border-box;" > return </span> [
[
<span style= "box-sizing: border-box;" > 'class' </span> => TimestampBehavior::className(),
<span style= "box-sizing: border-box;" > 'attributes' </span> => [
ActiveRecord::EVENT_BEFORE_INSERT => [<span style= "box-sizing: border-box;" > 'created_at' </span>, <span style= "box-sizing: border-box;" > 'updated_at' </span>],
ActiveRecord::EVENT_BEFORE_UPDATE => [<span style= "box-sizing: border-box;" > 'updated_at' </span>],
],
],
];
}
}
</code>
|
以上指定的行为数组:
- 当记录插入时,行为将当前时间戳赋值给
created_at
和 updated_at
属性;
- 当记录更新时,行为将当前时间戳赋值给
updated_at
属性。
保存 User
对象,将会发现它的 created_at
和 updated_at
属性自动填充了当前时间戳:
1 2 3 4 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $user </span> = <span style= "box-sizing: border-box;" > new </span> User;
<span style= "box-sizing: border-box;" > $user </span>->email = <span style= "box-sizing: border-box;" > 'test@example.com' </span>;
<span style= "box-sizing: border-box;" > $user </span>->save();
<span style= "box-sizing: border-box;" > echo </span> <span style= "box-sizing: border-box;" > $user </span>->created_at; <span style= "box-sizing: border-box;" >
|
yii\behaviors\TimestampBehavior 行为还提供了一个有用的方法 yii\behaviors\TimestampBehavior::touch(),这个方法能将当前时间戳赋值给指定属性并保存到数据库:
1 2 | <code style= "box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;" ><span style= "box-sizing: border-box;" > $user </span>->touch(<span style= "box-sizing: border-box;" > 'login_time' </span>);
</code>
|
与 PHP traits 的比较
尽管行为在 "注入" 属性和方法到主类方面类似于 traits ,它们在很多方面却不相同。如上所述,它们各有利弊。它们更像是互补的而不是相互替代。
行为的优势
行为类像普通类支持继承。另一方面,traits 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。
行为无须修改组件类就可动态附加到组件或移除。要使用 traits,必须修改使用它的类。
行为是可配置的而 traits 不能。
行为以响应事件来自定义组件的代码执行。
当不同行为附加到同一组件产生命名冲突时,这个冲突通过先附加行为的优先权自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。
traits 的优势
traits 比起行为更高效,因为行为是对象,消耗时间和内存。
IDE 对 traits 更友好,因为它们是语言结构。