Yii2.0 中的一个思想就是组件化的思想, 所以, 大多数的类都直接或间接的继承自 yii\base\Component, 而组件的三大功能: 属性, 事件, 行为.
行为的目的是为了方便的扩展一个类的功能, 而不需要直接去修改这个类, 同时行为中也附带了事件的实现.
1, 整体的结构
Controller 和模型 ActiveRecord 都继承自 yii\base\Component
而 Component 继承自 yii\base\Object
Object 中和 Component 中都实现了魔术方法__get 和__set 以及__call
所以, 在控制器和模型中都可以使用属性这个特性, 可以很方便的控制属性的可读, 可写
2, 行为
可以无须改变类继承关系即可增强一个已有的组件类功能, 当行为附加到组件之后, 它将 "注入" 它的方法和属性到组件, 然后可以像访问组件内定义的方法和属性一样访问它们
3, 行为如何做到 "注入"?
注入一个行为的方式:
自定义一个行为类 MyBehavior, 继承自 yii\base\Behavior
实例化一个组件类, 组件类继承自 yii\base\Component
然后调用 attachBehavior 将行为附加到组件类
组件类可以直接使用 MyBehavior 中的属性和方法
attach 注入做了什么?
在所有组件的父类 Component 中有个 $_behaviors
保存了所有注入到当前组件的行为, 见 Component 中的 attachBehaviorInternal 方法
同时行为类中有个 $owner, 保存了当前的组件对象 (用于事件绑定)
注入完成, 怎么生效的 (以 model 为例)?
见 yii\base\BaseActiveRecord 的魔术方法
如果当前对象的 attributes 中没有找到对应的属性, 调用 parent::__get($name), 位于 Component
Component::__get($name) 中, 判断当前对象中是否存在 getter 方法, 没有则遍历 $_behaviors, 如果 behavior 中有匹配的 getter 方法, 则返回值
注: 如果同一个组件类中注入了不同的 behavior, 而不同的 behavior 有相同的属性, 那么使用组件类实例访问属性的时候, 返回的 是先 attach 的 behavior, 因为在 getter 中是 foreach 循环 $_behaviors, 存在则 return, 循环终止
同理:
设置属性的值是在__set 魔术方法中做了 $_behaviors 的遍历
调用行为中的方法是在__call 魔术方法中做了 $_behaviors 的遍历
不手动 attachBehavior, 怎么做?
只需要在具体的组件类中定义一个 behaviors 方法
方法中返回行为类的配置
在魔术方法__get,__set,__call 中, 调用相应的方法前都执行了 ensureBehaviors
ensureBehaviors 做的工作就是获取 behaviors() 方法中的配置数组, 然后 attach 到当前组件类
行为中的事件?
可以在行为类中定义一个 events 方法, 返回事件的配置数组
在 attach 行为的时候, 会获取 events 中的配置, 执行 on 事件绑定
所以在事件绑定方法 on 和事件触发方法 trigger 方法中, 都首先调用了 $this->ensureBehaviors() 方法, 来保证行为中的事件也已经调用绑定成功了
4, 简单的示例代码
- //Yii 中的祖先类
- class Object
- {
- // 魔术方法, 实现 getter
- public function __get($name)
- {
- $getter = 'get'.$name;
- if(method_exists($this, $getter)){
- return $this->$getter();
- }
- }
- // 判断是否有对应的 getter 方法 || 直接有相应的属性
- public function canGetProperty($name, $checkVars = true)
- {
- return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
- }
- }
- // 行为基类
- class Behavior extends Object
- {
- // 行为注册到的那个组件类的引用
- public $owner;
- // 行为附加操作
- public function attach($owner)
- {
- $this->owner = $owner;
- // 事件的处理
- foreach($this->events() as $event => $handler){
- //$this->owner->on($event, $handler);
- }
- }
- //behavior 中可以定义 events,attach 中会进行事件的绑定
- public function events()
- {
- return [];
- }
- }
- // 组件基类
- class Component extends Object
- {
- public $_behaviors = [];
- //__get
- public function __get($name)
- {
- $getter = 'get'.$name;
- if(method_exists($this, $getter)){
- return $this->$getter();
- }else{
- $this->ensureBehaviors();
- foreach($this->_behaviors as $behavior){
- return $behavior->$name;
- }
- }
- }
- // 读取相应组件中的 behaviors 配置, 调用 attachBehaviorInternal, 将当前组件行为保存到 $_behaviors
- public function ensureBehaviors()
- {
- if($this->_behaviors === null){
- $this->_behaviors = [];
- foreach($this->behaviors() as $name => $behavior){
- $this->attachBehaviorInternal($name, $behavior);
- }
- }
- }
- // 将当前组件对象保存传递给行为类中了 owner, 在组件类中保存所有的行为
- public function attachBehaviorInternal($name, $behavior)
- {
- if(!($behavior instanceof Behavior)){
- // 根据配置创建 behavior 对象
- }
- if(is_int($name)){
- $behavior->attach($this);
- $this->_behaviors[] = $behavior;
- }else{
- $behavior->attach($this);
- $this->_behaviors[$name] = $behavior;
- }
- return $behavior;
- }
- }
- //test 测试要使用的行为的类必须继承 Component
- class TestModel extends Component
- {
- public $test = 'test';
- }
- class MyBehavior extends Behavior
- {
- public $mybehavior = 'get my behavior success';
- public function getMybehavior()
- {
- return $this->mybehavior;
- }
- }
- $model = new TestModel();
- $myBehavior = new MyBehavior();
- $model->attachBehaviorInternal('myBehavior', $myBehavior);
- echo $model->mybehavior;
- // 输出
- //get my behavior success
来源: https://www.cnblogs.com/skyfynn/p/8921058.html