Yii2.0源码阅读-behavior的实现原理

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

时间: 2024-08-29 16:24:19

Yii2.0源码阅读-behavior的实现原理的相关文章

Yii2.0源码分析之——设置别名函数(setAlias)和获取别名函数(getAlias)

首先说说什么是别名.在Yii中有很多的路径,在开发的过程当前我们也会使用一些路径.一般来说都需要使用绝对路径,但绝对路径都很长.所以,为了方便的使用路径,可以在Yi中i给每个路径起个名称,这个名称就是别名.别名的格式: 别名必须以"@"字符开头,别名中还可以包含"/".如("@www"为根别名,"@www/test"就为子别名) 别名最后的目录分隔符("\"或者"/")都将去掉(如果有的

AFNetworking 3.0源码阅读 - AFURLSessionManager

这次来说一下AFURLSessionManager 从头文件的英文注释可以看出AFURLSessionManager类创建并管理着NSURLSession对象,而NSURLSession又是基于NSURLSessionConfiguration的.同时该类也是AFHTTPSessionManager的父类,下一篇来讲. AFURLSessionManager实现了四个协议 1.NSURLSessionDelegate URLSession:didBecomeInvalidWithError: U

muduo2.0源码阅读记录

花了20天的时间读了陈硕先生的<Linux多线程服务端编程>一书的前8章.当然,每天阅读的时间并不算多,中间有些部分也反反复复看了几遍,最后也算是能勉强接受作者传授的知识.配合书把muduo2.0网络部分的代码和日志库代码细读了一遍,这也算是个人第一次较为深入地去读取一个开源项目源码.通过书和源码的阅读,确实是对不少东西加深了理解. 本来想按自己的理解来写源码阅读笔记的,但考虑到网上关于muduo代码的解析文章已经很多并且写的很好了,就放弃了这个想法.摘录几个自己在源码阅读过程中参考的网页:

seajs 3.0.0 源码阅读笔记

写笔记的时候才注意到我看的源代码是 3.0.0 的,但是官方发布的最新版本是 2.3.0.相信大部分是相同的,所以先把这个记完,再看一次 2.3.0 的代码. seajs 的源代码可以在 github上获取.seajs 在文档"如何参与开发"中说明了阅读顺序,当然为了便于阅读,在了解了目录结构之后,我直接阅读了合并好的 sea-debug.js. 整个seajs采用的是2空格缩进,避免分号的写法,我不是很习惯,但不影响阅读. 文件/目录结构 文档中各个源文件所包含的内容大致如下: in

Vue2.0源码阅读笔记--生命周期

一.Vue2.0的生命周期 Vue2.0的整个生命周期有八个:分别是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6.updated,7.beforeDestroy,8.destroyed. 用官方的一张图就可以清晰的了解整个生命周期: Vue最新源码下载:地址 二:源码分析 1.先看new Vue实例的方法 创建Vue实例的文件是: src/core/instance/index.js function Vue

AFNetworking 3.0源码阅读 - AFURLRequestSerialization

AFURLRequestSerialization模块主要做的两样事情: 1.创建普通NSMUtableURLRequest请求对象2.创建multipart NSMutableURLRequest请求对象此外还有比如:处理查询的URL参数 也就是说这主要实现了请求报文构造器的功能 在AFURLRequestSerialization.h文件中声明了三个类来帮助开发者构造请求报文 1.AFHTTPRequestSerializer 2.AFJSONRequestSerializer 3.AFPr

spark1.1.0源码阅读-taskScheduler

1. sparkContext中设置createTaskScheduler 1 case "yarn-standalone" | "yarn-cluster" => 2 if (master == "yarn-standalone") { 3 logWarning( 4 "\"yarn-standalone\" is deprecated as of Spark 1.0. Use \"yarn-clu

spark1.1.0源码阅读-dagscheduler and stage

1. rdd action ->sparkContext.runJob->dagscheduler.runJob 1 def runJob[T, U: ClassTag]( 2 rdd: RDD[T], 3 func: (TaskContext, Iterator[T]) => U, 4 partitions: Seq[Int], 5 callSite: String, 6 allowLocal: Boolean, 7 resultHandler: (Int, U) => Unit

[thinkphp 5.0源码阅读] 缓存(一)

保存缓存: user表数据: cache()方法保存缓存: 访问 http://mythinkphp.com/index/index/cache ,两个缓存被保存(runtime/cache目录下): 来看其中一个缓存文件:\runtime\cache\7e\58d63b60197ceb55a1c487989a3720.php <?php //000000003600a:2:{i:0;a:4:{s:2:"id";i:1;s:4:"name";s:2:"