yii2 源码分析 Component类分析 (二)

转载请注明链接http://www.cnblogs.com/liuwanqiu/p/6739538.html

组件(component),是Yii框架的基类,实现了属性、事件、行为三类功能,它集成自object类

   类前面的大部分注释:    1.事件在定义的类中唯一名称标示,事件名称区分大小写
    2.将事件处理程序到附加到事件,是用on
    3.有三种事件函数的调用方法,匿名函数调用,对象调用,静态方法调用,全局函数调用
    4.可以在配置中配置组件,使用方式是:
        [
            ‘on add‘ => function ($event) { ... }
        ]
    5.当你想在事件中传入数据时可以这样使用:
        $post->on(‘update‘, function ($event) {
              // 数据被这样调用 $event->data
        }, $data);
    6.行为类也是该组件类的子类,用法和事件差不多
class Component extends Object
{
    /**
     * 以键值对的方式存储事件处理
     */
    private $_events = [];
    /**
     *存储对象的行为。默认为null
     */
    private $_behaviors;

    /**
     * 返回组件的属性
     * 此方法按照下列顺序检查或执行:
     *
     *  - 属性通过get方法定义,返回get结果
     *  - 属性是个行为,返回行为的属性值
     *
     * 不要直接调用此方法,因为它是一个PHP魔术方法
     */
    public function __get($name)
    {
        $getter = ‘get‘ . $name;
        if (method_exists($this, $getter)) {
            // 判断get方法是否存在,存在就直接返回
            return $this->$getter();
        } else {
            //确保行为已经绑定
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canGetProperty($name)) {
                	//如果行为中有这个属性,就返回这个属性
                    return $behavior->$name;
                }
            }
        }
        //返回人性化的提示,属性只能写或不存在
        if (method_exists($this, ‘set‘ . $name)) {
            throw new InvalidCallException(‘Getting write-only property: ‘ . get_class($this) . ‘::‘ . $name);
        } else {
            throw new UnknownPropertyException(‘Getting unknown property: ‘ . get_class($this) . ‘::‘ . $name);
        }
    }

    /**
     * 设置组件的属性.
     * 此方法将按下列顺序检查并相应执行:
     *
     *  - 属性存在,设置该属性并返回
     *  - 如果 $name 是 ‘on ex‘,就会将 ex 事件添加到该对象中
     *  - 如果 $name 是 ‘as ex‘,就会将 ex 行为添加到该对象中
     *  - 添加对 behaviors 的处理,循环 behaviors,如果其中有相应的属性,就设置它
     */
    public function __set($name, $value)
    {
        $setter = ‘set‘ . $name;
        if (method_exists($this, $setter)) {
            // 设置该对象的属性,并返回
            $this->$setter($value);

            return;
        } elseif (strncmp($name, ‘on ‘, 3) === 0) {
            // 字符串截取后是on开头的,执行on方法附件事件,并返回
            $this->on(trim(substr($name, 3)), $value);

            return;
        } elseif (strncmp($name, ‘as ‘, 3) === 0) {
            // 字符串截取后是as开头的,执行attachBehavior方法附件事件,并返回
            $name = trim(substr($name, 3));
            //$value这个对象是Behavior类的一个实例,取$value为参数,否则静态调用Yii方法创造一个新的对象。并返回
            $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

            return;
        } else {
            //确保行为已经绑定
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canSetProperty($name)) {
                	//遍历行为,如果行为中有可以设置的属性$name,给该行为类中的属性设置属性值
                    $behavior->$name = $value;

                    return;
                }
            }
        }
        //返回人性化的提示,属性只能读或不存在
        if (method_exists($this, ‘get‘ . $name)) {
            throw new InvalidCallException(‘Setting read-only property: ‘ . get_class($this) . ‘::‘ . $name);
        } else {
            throw new UnknownPropertyException(‘Setting unknown property: ‘ . get_class($this) . ‘::‘ . $name);
        }
    }

    /**
     * 检查属性是否已经被定义
     * 此方法按照下列顺序检查或执行:
     *
     *  - 定义过的属性返回真
     *  - 如果是一个行为,返回行为的属性是否被设置
     *  - 不存在此属性返回false
     */
    public function __isset($name)
    {
        $getter = ‘get‘ . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter() !== null;
        } else {
            // 确定行为已经绑定
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canGetProperty($name)) {
                	// 如果 behavior 中有 $name 属性,且不为 null,该属性存在 返回 true
                    return $behavior->$name !== null;
                }
            }
        }
        return false;
    }

    /**
     * 设置属性为null,即删除属性
     * 重写 Object 中的 unset 方法,
     * 通过setter定义的属性:设置该属性值为空
     * 属性的行为:将属性值设为空
     */
    public function __unset($name)
    {
        $setter = ‘set‘ . $name;
        if (method_exists($this, $setter)) {
            $this->$setter(null);
            return;
        } else {
             // 确定行为已经绑定
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canSetProperty($name)) {
                    $behavior->$name = null;
                    return;
                }
            }
        }
        throw new InvalidCallException(‘Unsetting an unknown or read-only property: ‘ . get_class($this) . ‘::‘ . $name);
    }

    /**
     * 调用组件不存在的方法时被隐式调用
     * 调用方法名 重写 Object 中的 call 方法,添加对行为的处理,循环 behaviors,
     * 如果其中有相应方法,就执行该 behavior 的方法
     */
    public function __call($name, $params)
    {
    	 // 确定行为已经绑定
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $object) {
            if ($object->hasMethod($name)) {
            	//行为中存在名为 $name 的方法,就执行它
                //call_user_func_array 调用回调函数,并把一个数组参数作为回调函数的参数
                return call_user_func_array([$object, $name], $params);
            }
        }
        //方法不存在就抛出异常
        throw new UnknownMethodException(‘Calling unknown method: ‘ . get_class($this) . "::$name()");
    }

    /**
      * 通过克隆现有创建的对象后,此方法会被调用。
      * 他将会消除所有的行为。所有的引用属性 仍然会是一个指向原来的变量的引用
     */
    public function __clone()
    {
        $this->_events = [];
        $this->_behaviors = null;
    }

    /**
     * 返回true或false表示此属性在组件中是否被定义
     * - 类拥有一个关联的set或get方法
     *   (属性名不区分大小写);
     * - 类具有指定名称的成员变量 (when `$checkVars` is true);
     * - 附加的行为具有给定名称的属性 (when `$checkBehaviors` is true).
     */
    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
    }

    /**
     * 返回一个值,该值指示是否可以读取属性。
     * A property can be read if:
     *
     * - 获取属性
     *   (属性名称不区分大小写);
     * 其它参数和hasProperty方法的参数差不多,就不一一介绍了。。。
     */
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        if (method_exists($this, ‘get‘ . $name) || $checkVars && property_exists($this, $name)) {
            return true;
        } elseif ($checkBehaviors) {
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canGetProperty($name, $checkVars)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 返回一个方法是否可以被设置
     */
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        if (method_exists($this, ‘set‘ . $name) || $checkVars && property_exists($this, $name)) {
            return true;
        } elseif ($checkBehaviors) {
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canSetProperty($name, $checkVars)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 返回一个布尔值,表示是否有此方法
     */
    public function hasMethod($name, $checkBehaviors = true)
    {
        if (method_exists($this, $name)) {
            return true;
        } elseif ($checkBehaviors) {
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->hasMethod($name)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 返回该组件应表现为的行为列表.
     *
     * 子类可以重写此方法,以指定它们想要表现的行为.
     *
     * 此方法的返回值应该是由行为名称索引的行为对象或配置数组,行为配置可以是指定行为类的字符串,也可以是以下结构的数组:
     *
     * ```php
     * ‘behaviorName‘ => [
     *     ‘class‘ => ‘BehaviorClass‘,
     *     ‘property1‘ => ‘value1‘,
     *     ‘property2‘ => ‘value2‘,
     * ]
     * ```
     *
     * 新建的行为必须继承行为类 [[Behavior]]. 可以使用名称或匿名连接行为.
     * 当用作数组键的名称时, 使用这个名称, 该行为稍后可以检索使用[[getBehavior()]],即添加行为
     * 或被分离使用 [[detachBehavior()]],即解除行为. 匿名行为不能检索或分离.
     *
     * 此方法中声明的行为将自动(按需)连接到组件
     *
     * @return 返回值为数组的行为配置.
     */
    public function behaviors()
    {
        return [];
    }

    /**
     * 判断 _events 中的一个事件是否具有事件处理程序.
     * @param string $name 事件名
     * @return boolean whether there is any handler attached to the event.
     */
    public function hasEventHandlers($name)
    {
        $this->ensureBehaviors();
        // 判断事件是否存在 $name 否则 调用Event类中的的方法判断是否有处理程序
        return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
    }

    /**
     *将事件处理程序附加到事件.
     *
     * 事件处理程序必须是有效的PHP回调. 例如:
     *
     * ```
     * function ($event) { ... }         // anonymous function----匿名函数
     * [$object, ‘handleClick‘]          // $object->handleClick()---对象方法
     * [‘Page‘, ‘handleClick‘]           // Page::handleClick()---静态类方法
     * ‘handleClick‘                     // global function handleClick()---全局函数
     * ```
     *
     * 事件处理程序必须定义以下签名,
     *
     * ```
     * function ($event)
     * ```
     *
     * `$event`包括与事件关联的参数.
     *
     * @param string $name 事件名
     * @param callable $handler  事件处理函数
     * @param $data 当事件触发时,将数据传递给事件处理程序.
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
     *当事件处理程序被调用时,这些数据可以通过[ [事件::数据] ]来访问
     * @param boolean $append是否将新事件处理程序追加到现有的结尾,处理程序列表。如果FALSE,新的处理程序将被插入在现有事件的开始
     * @see off()
     */
    public function on($name, $handler, $data = null, $append = true)
    {
        $this->ensureBehaviors();
        //$append 判断是否添加到事件(event)的后面,确保_events中有该事件
        if ($append || empty($this->_events[$name])) {
        	//将事件处理程序和参数添加到event数组末尾
            $this->_events[$name][] = [$handler, $data];
        } else {
        	//否则 添加到 event 的前面
            array_unshift($this->_events[$name], [$handler, $data]);
        }
    }

    /**
     * 删除事件处理程序.
     * [[on()]]的反方法.
     * @param string $name 事件名
     * @param callable $handler 事件处理程序
     * 如果为空,清除所有的事件处理程序
     * @return boolean 是否发现并分离的处理程序
     * @see on()
     */
    public function off($name, $handler = null)
    {
        $this->ensureBehaviors();
        // 相应的事件不存在,返回false
        if (empty($this->_events[$name])) {
            return false;
        }
        //没有handler,清除该事件的所有事件处理程序 并返回true
        if ($handler === null) {
            unset($this->_events[$name]);
            return true;
        } else {
            $removed = false;//删除标记
            foreach ($this->_events[$name] as $i => $event) {
                if ($event[0] === $handler) {//遍历该事件 判断事件处理程序是否符合
                    unset($this->_events[$name][$i]);//删除该事件处理程序
                    $removed = true;
                }
            }
            if ($removed) {
            	 // 如果删除成功,就需要重新构建以下索引,重新赋值
                $this->_events[$name] = array_values($this->_events[$name]);
            }
            return $removed;
        }
    }

    /**
     * 触发事件.
     * 它调用包括事件级处理程序在内的所有附加处理程序
     * @param string $name 事件名
     * @param Event $event 事件参数. 如果未设置,默认的 [[Event]] 对象将被创建.
     */
    public function trigger($name, Event $event = null)
    {
    	//确保行为绑定
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
        	// 事件名不为空 构建Event对象,为传入到handler函数中做准备
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            // 遍历事件 给事件的data属性赋值
            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                // handler的函数中传入了一个Event对象
                call_user_func($handler[0], $event);
                // 事件是否被处理,如果了处理事件即handled被设置为true时,停止进一步处理
                if ($event->handled) {
                    return;
                }
            }
        }
        // 触发类级别的事件处理程序
        Event::trigger($this, $name, $event);
    }

    /**
     * 获取行为类
     * @param string $name t行为名
     * @return null|Behavior 行为对象,如果行为不存在为null
     */
    public function getBehavior($name)
    {
    	//确保行为绑定
        $this->ensureBehaviors();
         //_behaviors中的行为类存在,返回行为类名,否则返回空
        return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
    }

    /**
     * 获取所有的行为类
     * @return Behavior[] list 返回一个所有行为类的数组
     */
    public function getBehaviors()
    {
        $this->ensureBehaviors();
        return $this->_behaviors;
    }

    /**
     * 内部使用的添加一个行为到该组件。
     * 通过提供的配置文件创建一个Behavior对象,通过调用 [[Behavior::attach()]] 方法添加行为到组件.
     * @param string $name  行为名
     * @param string|array|Behavior $behavior 行为配置. 这可以是以下之一:
     *
     *  - a [[Behavior]] object 一个[[Behavior]]类
     *  - a string specifying the behavior class 一个字符串形式的指定行为类
     *  - 一个配置文件数组,通过调用[[Yii::createObject()]] 创建一个行为对象.
     *
     * @return Behavior the behavior object行为对象
     * @see detachBehavior()
     */
    public function attachBehavior($name, $behavior)
    {
        $this->ensureBehaviors();
        return $this->attachBehaviorInternal($name, $behavior);//添加行为
    }

    /**
     * 将行为列表附加到组件.
     * 行为类通过行为名索引,且必须是一个 [[Behavior]] 对象指定的行为类或者一个配置数组
     * @param array $behaviors  行为列表
     * @see attachBehavior()
     */
    public function attachBehaviors($behaviors)
    {
        $this->ensureBehaviors();
        foreach ($behaviors as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }

    /**
     * 从组件解除行为
     * 通过[[Behavior::detach()]]解除行为
     * @param string $name行为名
     * @return null|Behavior  存在返回分离行为 不存在返回null
     */
    public function detachBehavior($name)
    {
        $this->ensureBehaviors();
        if (isset($this->_behaviors[$name])) {
        	//行为存在,解除行为
            $behavior = $this->_behaviors[$name];
            unset($this->_behaviors[$name]);
            //返回分离行为
            $behavior->detach();
            return $behavior;
        } else {
            return null;
        }
    }

    /**
     * 解除所有行为
     */
    public function detachBehaviors()
    {
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $name => $behavior) {
            $this->detachBehavior($name);
        }
    }

    /**
     * 确保声明的行为都被添加到组件
     */
    public function ensureBehaviors()
    {
        if ($this->_behaviors === null) {
            $this->_behaviors = [];
            foreach ($this->behaviors() as $name => $behavior) {
            	//遍历$this->behaviors()中的behaviors,并添加到$this->_behaviors数组中
                $this->attachBehaviorInternal($name, $behavior);
            }
        }
    }

    /**
     * 内部使用的为该对象添加behavior的方法
     * @param string|integer $name 如果这是一个整数,则表示该行为是匿名的。否则,该行为是一个命名的和任何现有的同名行为将首先分离
     * @param string|array|Behavior $behavior 添加的行为
     * @return Behavior the attached behavior.
     */
    private function attachBehaviorInternal($name, $behavior)
    {
        if (!($behavior instanceof Behavior)) {
        	 // $behavior不是Behavior对象,认为是配置,则创建一个$behavior对象
            $behavior = Yii::createObject($behavior);
        }
        if (is_int($name)) {
        	//行为是整数,绑定到组件
            $behavior->attach($this);
            $this->_behaviors[] = $behavior;
        } else {
            if (isset($this->_behaviors[$name])) {
            	// 如果有同名的行为存在就先解绑掉
                $this->_behaviors[$name]->detach();
            }
            $behavior->attach($this);//重新绑定行为到组件
            $this->_behaviors[$name] = $behavior;
        }
        return $behavior;
    }
}

  

时间: 2024-10-12 02:35:49

yii2 源码分析 Component类分析 (二)的相关文章

[Android FrameWork 6.0源码学习] LayoutInflater 类分析

LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类.是这个工具类的存在,才能让我们写起Layout来那么省劲. 我们接下来进去刨析,看看里边的奥秘 //调用inflate方法就可以把XML解析成View对象 View contentView = LayoutInflater.from(this).inflate(R.layout.activity_main, null); 我们在使用这个类的时候,通常都是像上面这样写,首先通过from函数获取对象,在调用

JDK源码之HashMap 类分析

一 概述 HashMap实现 hashmap继承了AbstractMap,实现了Map接口和Cloneable接口,HashMap是基于哈希表(散列表),实现Map接口的双列集合 jdk8中底层数据结构已经改为二叉树,之前是链表 看hashmap之前,需要把Map,AbstractMap源码撸一遍,这里放我的博文链接: https://www.cnblogs.com/houzheng/p/12687883.html 涉及到的数据结构 二 源码分析 属性 静态内部类(Entry的实现) 三 总结

.NET源码之Page类(二) (转)

.NET源码之Page类(二) 我们在.Net源码之Page类(一) 已经介绍过了初始化与加载阶段了.今天将介绍余下的部分.由于是从源代码上了解生命周期,所以这里会有大量的代码.建议大家看本篇博客的时候最好能够一边对照源代码,最好能够自己调试一遍.希望大家在平时碰到过这方面的问题的,可以留言,能够从源代码这个阶段去剖析问题的实质.         首先我们来回顾一下初始化与加载阶段之间的那个阶段,我们先拿MSDN上对初始化和加载阶段的有2句话描述来看一下: 页初始化阶段:如果当前请求是回发请求,

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"

Netty源码学习——ChannelPipeline模型分析

参考Netty API io.netty.channel.ChannelPipeline A list of ChannelHandlers which handles or intercepts inbound events and outbount operations of aChannel.ChannelPipeline implements an advanced form of theIntercepting Filter pattern to give a user full co

从Java源码的角度来分析HashMap与HashTable的区别

由于HashMap与HashTable都是用来存储Key-Value的键值对,所以经常拿来对比二者的区别,下面就从源码的角度来分析一下HashMap与HashTable的区别, 首先介绍一下两者的区别,然后再从源码分析. HahMap与HahTable两者主要区别: 1.继承的父类不同 <span style="font-size:18px;">public class HashMap<K, V> extends AbstractMap<K, V>

Spring mvc之源码 handlerMapping和handlerAdapter分析

Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mvc的执行流程,以及如何根据URL查找处理器Controller的实现 (适合那些刚阅读源码不知道如何下手的人) http://www.guojinbao.com/borrow/borrowDetail/GETadLPjnf0[d].do 如何根据URL地址---->找到正确处理器Controller

std::string源码探秘和性能分析

std::string源码探秘和性能分析 本文主要讲c++标准库的string的内部实现,以及对象拷贝的性能分析. 文中采用的源码版本为gcc-4.9,测试环境为centos7, x86_64,涉及到指针等数据类型的大小也假定是在64环境位下. stl源码可以在gnu gcc的官方网站下载到:https://gcc.gnu.org/ 头文件 vector头文件,该文件也可以直接在安装了g++的linux系统中找到.主要包含以下头内容: // vector #include <bits/strin

ThreadLocal源码及相关问题分析

前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadLocal是将公共变量copy一份到线程私有内存中以消除并发问题,ThreadLocal是JDK内部提供的高效解决并发问题的工具类之一,本文介绍ThreadLocal的重要方法的源码实现以及相关问题的分析. 数据结构 由上图可以看出,在Thread中维护了一个Entry的列表,Entry存储的是公共变