美文网首页Yii2yii2Yii2
Yii2框架源码研究2-Component

Yii2框架源码研究2-Component

作者: Vos井宝 | 来源:发表于2016-04-12 18:30 被阅读330次

    Component继承自Object,因此他具有属性这个特性,在这个基础上,组件提供了两个功能强大的特性:事件和行为。也就是说,如果一个类继承了Component类,他就具有这些特性,就能够给这个类的对象绑定事件和行为。

    事件的作用是在某一个特殊的场合,执行某段代码。一个事件通常包含以下几个要素:

    • 这是一个什么事件
    • 谁触发了事件
    • 谁去处理事件
    • 怎么处理这个事件
    • 处理事件相关的数据是什么

    行为的作用是让某一个对象拥有某一些方法和属性,这些方法和属性被封装在一个行为里,当这个行为依附在某个类中的时候,这个类就具有了这个行为提供的属性和方法。


    为了理解组件是怎么实现这两个特性的,首先需要看一下Component的源代码

    class Component extends Object
    {
        private $_events = [];
        private $_behaviors;
        public function __get($name)
        public function __set($name, $value)
        public function __isset($name)
        public function __unset($name)
        public function __call($name, $params)
        public function __clone()
        {
            $this->_events = [];
            $this->_behaviors = null;
        }
        public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
        public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
        public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
        public function hasMethod($name, $checkBehaviors = true)
        public function behaviors()
        {
            return [];
        }
        public function hasEventHandlers($name)
        public function on($name, $handler, $data = null, $append = true)
        public function off($name, $handler = null)
        public function trigger($name, Event $event = null)
        public function getBehavior($name)
        public function getBehaviors()
        public function attachBehavior($name, $behavior)
        public function attachBehaviors($behaviors)
        public function detachBehavior($name)
        public function detachBehaviors()
        public function ensureBehaviors()
        private function attachBehaviorInternal($name, $behavior)
    }
    
    

    咋一看,发现Component将Object类中的方法全都重写了,好吧。那就先来看看属性这个特性。

    属性

    Component类没有构造方法,因此其初始化的过程和Object类是一样的,对属性的操作也都会定位到魔术方法__set()或者__get()里面,一个一个看:

        public function __set($name, $value)
        {
            $setter = 'set' . $name;
            if (method_exists($this, $setter)) {
                $this->$setter($value);
                return;
            } elseif (strncmp($name, 'on ', 3) === 0) {
                $this->on(trim(substr($name, 3)), $value);
                return;
            } elseif (strncmp($name, 'as ', 3) === 0) {
                $name = trim(substr($name, 3));
                $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
                return;
            } else {
                $this->ensureBehaviors();
                foreach ($this->_behaviors as $behavior) {
                    if ($behavior->canSetProperty($name)) {
                        $behavior->$name = $value;
                        return;
                    }
                }
            }
            if (method_exists($this, 'get' . $name)) {
                throw 
            } else {
                throw 
            }
        }
    

    一目了然,为什么要重写这个方法,因为component的配置数组中可以配置事件和行为,on+空格表示事件,as+空格表示行为。由于行为的属性也是组件的属性,因此还会去行为中查找相应的属性。

        public function __get($name)
        {
            $getter = 'get' . $name;
            if (method_exists($this, $getter)) {
                // read property, e.g. getName()
                return $this->$getter();
            } else {
                // behavior property
                $this->ensureBehaviors();
                foreach ($this->_behaviors as $behavior) {
                    if ($behavior->canGetProperty($name)) {
                        return $behavior->$name;
                    }
                }
            }
            if (method_exists($this, 'set' . $name)) {
                throw
            } else {
                throw 
            }
        }
    

    __get()函数中会去行为中寻找相应的属性。

    事件

    开头说了事件的基本概念,现在来看一下具体有哪些方法吧。
      首先在component类中,定义了一个数组用来存储所有的事件:

        private $_events = [];//name => handlers
    

    这里的handlers是一个数组,因为有可能一个事件有许多事件处理函数。数组里每一个项都是[$handler, $data] 的结构,其中,$handler的结构如下:

          function ($event) { ... }         // anonymous function
          [$object, 'handleClick']          // $object->handleClick()
          ['Page', 'handleClick']           // Page::handleClick()
          'handleClick'                     // global function handleClick()
    

    为什么是这四种呢?后面会看到,在trigger函数中调用了call_user_func函数,这个函数允许使用这四种方式去执行一个方法。

    事件绑定与解除

    绑定事件所进行的操作是将事件的名称和事件处理函数对应起来,并将这个对应关系放在event数组里面。方法如下:

        public function on($name, $handler, $data = null, $append = true)
        {
            $this->ensureBehaviors();
            if ($append || empty($this->_events[$name])) {
                $this->_events[$name][] = [$handler, $data];
            } else {
                array_unshift($this->_events[$name], [$handler, $data]);
            }
        }
    

    相应的事件的解除也就是将事件与其处理函数的关系在event数组中移除。如果$handler为空,将会删除这个事件的所有时间处理函数,相应的函数如下:

        public function off($name, $handler = null)
        {
            $this->ensureBehaviors();
            if (empty($this->_events[$name])) {
                return false;
            }
            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) {
                    //因为unset之后,key混乱了,这样的话key就不混乱了
                    $this->_events[$name] = array_values($this->_events[$name]);
                }
                return $removed;
            }
        }
    

    事件触发

    事件触发后发生的事情就是执行所有绑定的事件处理函数,具体到操作上来说就是遍历数组event[$name],将数据传递给事件handler并执行。

        public function trigger($name, Event $event = null)
        {
            $this->ensureBehaviors();
            if (!empty($this->_events[$name])) {
                if ($event === null) {
                    $event = new Event;
                }
                if ($event->sender === null) {
                    $event->sender = $this;
                }
                $event->handled = false;
                $event->name = $name;
                foreach ($this->_events[$name] as $handler) {
                    $event->data = $handler[1];
                    call_user_func($handler[0], $event);
                    // stop further handling if the event is handled
                    if ($event->handled) {
                        return;
                    }
                }
            }
            // invoke class-level attached handlers
            Event::trigger($this, $name, $event);
        }
    

    这里需要注意的一个地方是,在循环执行所有的事件处理函数的时候,如果某个handler将$event->handled置为true,那么剩下的handler将不会被执行。Event::trigger()这个函数用于触发类事件。

    Event类

    这个类已经多次接触到,总结这个类的使用场景,发现他主要有两个用途:

    1. 用于向事件处理函数传递信息。
    2. 用于触发类事件。

    之前说的事件的绑定,解除的操作,都是基于某一个实例化的对象来说的,假如说某一个类被实例化出来了好多对象,现在想对所有的对象都绑定某一个事件,那就需要对这些对象依次进行绑定,这样做岂不是很麻烦,这时候就可以使用Event类提供的机制,绑定一个类事件,所有从这个类实例化出来的对象都能够触发这个事件。现在来看一下Event类的代码:

    class Event extends Object
    {
        public $name;
        public $sender;
        public $handled = false;
        public $data;
        private static $_events = [];
        public static function on($class, $name, $handler, $data = null, $append = true)
        {
            $class = ltrim($class, '\\');
            if ($append || empty(self::$_events[$name][$class])) {
                self::$_events[$name][$class][] = [$handler, $data];
            } else {
                array_unshift(self::$_events[$name][$class], [$handler, $data]);
            }
        }
        public static function off($class, $name, $handler = null)
        {
            $class = ltrim($class, '\\');
            if (empty(self::$_events[$name][$class])) {
                return false;
            }
            if ($handler === null) {
                unset(self::$_events[$name][$class]);
                return true;
            } else {
                $removed = false;
                foreach (self::$_events[$name][$class] as $i => $event) {
                    if ($event[0] === $handler) {
                        unset(self::$_events[$name][$class][$i]);
                        $removed = true;
                    }
                }
                if ($removed) {
                    self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
                }
    
                return $removed;
            }
        }
        public static function hasHandlers($class, $name)
        {
            if (empty(self::$_events[$name])) {
                return false;
            }
            if (is_object($class)) {
                $class = get_class($class);
            } else {
                $class = ltrim($class, '\\');
            }
            do {
                if (!empty(self::$_events[$name][$class])) {
                    return true;
                }
            } while (($class = get_parent_class($class)) !== false);
    
            return false;
        }
        public static function trigger($class, $name, $event = null)
        {
            if (empty(self::$_events[$name])) {
                return;
            }
            if ($event === null) {
                $event = new static;
            }
            $event->handled = false;
            $event->name = $name;
    
            if (is_object($class)) {
                if ($event->sender === null) {
                    $event->sender = $class;
                }
                $class = get_class($class);
            } else {
                $class = ltrim($class, '\\');
            }
            do {
                if (!empty(self::$_events[$name][$class])) {
                    foreach (self::$_events[$name][$class] as $handler) {
                        $event->data = $handler[1];
                        call_user_func($handler[0], $event);
                        if ($event->handled) {
                            return;
                        }
                    }
                }
            } while (($class = get_parent_class($class)) !== false);
        }
    }
    

    Event类中同样有一个$_events数组,里面保存的内容和Component里面的内容一样,只不过,由于需要根据类名来寻找相应的类事件,因此现在的数组中多了一层:$_events[$name][$class][] = [$handler, $data];
      注册类事件:

    Event::on(  Worker::className(),               // 第一个参数表示事件发生的类 
                Worker::EVENT_OFF_DUTY,            // 第二个参数表示是什么事件 
                function ($event) {                // 对事件的处理 
                    echo $event->sender . ' 下班了'; 
                }
    );
    

    触发类事件,这里$this的作用仅仅是需要知道是谁触发的事件,然后根据这个对象得到其类的名称:

    Event::trigger($this, $name, $event); 
    

    行为

    行为是一个类,想要新建一个行为,首先需要新建一个继承yii\base\Behavior 的类,然后将这个行为依附到另外一个继承了Component或其子类的类上,这个类就有了这个行为,就有了这个行为所具有的属性和方法。依附的过程就是调用这个类的attach方法,相应的解绑的过程就是调用其detach方法。绑定的时候会将行为的事件注册到拥有者,这个拥有者一定是一个Component。先来看看Behavior类:

    class Behavior extends Object
    {
        public $owner;
    
    
        /**
         * Declares event handlers for the [[owner]]'s events.
         * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']`
         * - object method: `[$object, 'handleClick']`
         * - static method: `['Page', 'handleClick']`
         * - anonymous function: `function ($event) { ... }`
        */
        public function events()
        {
            return [];
        }
        public function attach($owner)
        {
            $this->owner = $owner;
            foreach ($this->events() as $event => $handler) {
                $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
            }
        }
        public function detach()
        {
            if ($this->owner) {
                foreach ($this->events() as $event => $handler) {
                    $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
                }
                $this->owner = null;
            }
        }
    }
    

    组件对行为的控制

    组件中有一个变量$_behaviors用于存储所有的行为,这是一个数组(behavior name => behavior)并且这里的behavior表示一个类,当$_behaviors为null的时候,说明还没有初始化。

        public function ensureBehaviors()
        {
            if ($this->_behaviors === null) {
                $this->_behaviors = [];
                foreach ($this->behaviors() as $name => $behavior) {
                    $this->attachBehaviorInternal($name, $behavior);
                }
            }
        }
    

    这个函数刚刚已经碰到过,就是初始化所有行为的一个过程,首先调用函数得到所有的行为,然后依次执行函数attachBehaviorInternal。
      有一点需要说明,在__set()函数中,对as+空格的属性进行了特殊处理,将其当做一个行为来看,这时候调用了attachBehavior函数对这个行为进行attach的处理,在这个函数中首先调用了ensureBehaviors,也就是首先要初始化behaviors()函数定义的行为。相同名称的行为出现时,后者会覆盖前者,因此在配置数组里配置的行为的优先级高于behaviors()函数定义的行为。

    行为attach过程

    attach的行为一共有两种来源,一种是配置数组中利用as+空格定义的,一种是在behaviors()函数中返回的,最终都会调用一个函数:

        private function attachBehaviorInternal($name, $behavior)
        {
            if (!($behavior instanceof 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;
        }
    

    如果一个行为的name是一个整数,那么这个行为仅仅是知性了这个行为的attach函数,其属性和方法并未依附到主体上来。依附的过程就是首先将behavior实例化,然后将其赋值给_behaviors数组,如果已存在同名的行为,则覆盖。

    detach过程

    主要有两步,将$behavior对象从$_behavior中移除,调用$behavior的detach()方法

        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;
            }
        }
    

    相关文章

      网友评论

      • CR:鼓励作者继续写这个系列

      本文标题:Yii2框架源码研究2-Component

      本文链接:https://www.haomeiwen.com/subject/bafwlttx.html