❥(^_-) Yii2框架源码解析之属性、行为和事件component.php

前言

今天这篇博客主要讲Yii2框架中的组件类component.php。这个组件类囊括了今天要讲的主题:属性、行为和事件。

首先在这里解释一下什么是属性,什么事行为,什么是事件。

属性        

属性就是指类的成员变量,Yii2框架中的组件类component.php继承了对象基础类BaseObject.php。在对象基础类里面,运用php中的魔术方法__get()和__set(),实现对类属性的赋值和获取。

行为

行为和php的trait有点类似。使用行为必须继承组件类component.php(或行为类Behavior.php),它无须改变类继承关系,即可增强一个已有的组件类功能。 当行为附加到组件后,它将“注入”它的方法和属性到组件, 然后可以像访问组件内定义的方法和属性一样访问它们。

事件

事件和thinkPHP中钩子的概念差不多。事件可以将自定义代码“注入”到现有代码中的特定执行点。 附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。 

上源码

首先看下对象基础类BaseObject.php的源码,然后分析它主要的功能是什么。

BaseObject.php

<?php

class BaseObject implements Configurable
{
    //这是我一个对象基础类,主要用于属性的设置和获取。
    //当类的属性只有获取方法(getter),没有设置方法(setter)则认为此属性只读。
    //生命周期:
    //构造函数 -> 对象属性初始化 -> init函数


    public static function className()
    {
        // 获取调用者完整类名
        return get_called_class();
    }

    public function __construct($config = [])
    {
        // 存在配置,设置当前类默认属性。
        if (!empty($config)) {
            Yii::configure($this, $config);
        }
        $this->init();
    }

    public function init()
    {
    }

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) { // 存在获取器,直接获取。
            return $this->$getter();
        } elseif (method_exists($this, 'set' . $name)) { //不存在获取器,但是存在设置器。那么就抛出错误。
            throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
        }

        // 不存在获取器也不存在设置器,直接属性不存在异常。(注意:是获取不存在的属性或者是没有访问权限的属性)
        throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
    }

    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) { // 存在设置器,就设置属性
            $this->$setter($value);
        } elseif (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);
        }
    }

    public function __isset($name)
    {
        // 使用isset()判断对象属性是否存在时候调用。
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            // 如果存在getter,则根据获取器返回值判断是否null。是null则false。否则ture;
            return $this->$getter() !== null;
        }
        // 不存在getter直接false;
        return false;
    }

    public function __unset($name)
    {
        // 使用unset()销毁对象属性时调用。

        $setter = 'set' . $name;
        if (method_exists($this, $setter)) { // 存在设置器,就设置为null.
            $this->$setter(null);
        } elseif (method_exists($this, 'get' . $name)) { // 不存在设置器,存在获取器,返回“销毁只读属性”错误。
            throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
        }
        // 否则不做处理。
    }


    public function __call($name, $params)
    {
        // 调用类的未知方法,直接抛出“调用未知方法”错误。
        throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
    }


    public function hasProperty($name, $checkVars = true)
    {
        // 如果不存在获取器,并且类属性不存在。那么如果存在设置器,则返回真。
        return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
    }


    public function canGetProperty($name, $checkVars = true)
    {
        // 是否存在获取器。如果第二参数为真,就是:是否存在类属性。
        return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
        // 上面的写法相当于
        /*if (method_exists($this, 'get' . $name)) {
            return method_exists($this, 'get' . $name);
        } else {
            if ($checkVars) {
                return property_exists($this, $name);
            } else {
                return $checkVars;
            }
        }*/
    }


    public function canSetProperty($name, $checkVars = true)
    {
        // 是否存在设置器。如果第二参数为真,就是:是否存在类属性。
        return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
    }


    public function hasMethod($name)
    {
        return method_exists($this, $name);
    }
}

首先我们看到,这个类的构造方法,先通过配置的方式,设置了继承子类的初始属性。所以继承类不需要覆盖写当前类的构造方法,只需要覆盖init()这个方法即可。

其次,继承这个类的子类,可以添加设置器方法setter和获取器方法getter来对类的属性值进行特殊处理。整体代码也比较简单。这里不做过多赘述。


component.php

首先,yii框架的行为和事件的功能设计中,都用到了注册器模式。都是先把行为或者事件注册到组件类的全局树上($_events和$_behaviors)。然后再在程序里面,在需要的地方进行相应操作。我在博客中,把行为或者事件“注入”到全局树上的这个操作称为“绑定”,把行为或者事件“取消注入”全局树的操作称为“解绑”。这里将行为和事件分开来讲吧,我们先将实现原理,再来看源码。主要代码都在组件类component.php中。

事件

首先来讲事件,前面已经大概讲了一下事件的概念。使用事件,必须继承component.php组件类。整个事件的使用分为2~3个步骤。

1. 绑定事件到全局。

2. 调用trigger方法触发事件。

3.解绑事件。

事件的全局树上保存的是事件名称,以及事件的处理器(真正的执行程序)。事件的处理器可以是以下几种类型:

1. 类的静态方法

2. 对象的方法

3. 匿名函数

4. 全局函数

实现原理是这样的:

1. 绑定:将事件名称为键,事件处理器作为值(也是一个数组,可能为多个)作为值,保存到全局。

2. 触发:根据传入的事件名称,从全局树上获取对应的事件处理器。利用php函数call_user_func()。触发事件处理器的执行程序。

3. 解绑:正常情况下,解绑是没必要做的(事件类里面定义了事件是否触发的属性)。解绑也可以在事件触发之前调用。解绑就是把事件全局树上对应的事件销毁。(也可以销毁某一事件的某一具体的处理器)

行为

行为的概念前面也已经讲过了。这里具体讲一下使用和框架中的实现原理。

行为的使用主要是行为的绑定和解绑。绑定就是将行为类,绑定到组件中,那么这个组件就拥有了这个行为的全部方法和属性。解绑就是解除此行为对组件的绑定。

实现原理:

解绑的原理和事件是一样的。就是注销全局行为树种的一个值。这里主要讲绑定。

绑定的实现也非常简单。首先component.php继承了BaseObject.php,BaseObject.php里面针对普通类的属性的设置和获取做了优化,可以添加设置器或者获取器,来对属性做一些特殊处理。这里component.php覆盖写了父类BaseObject.php中的所有方法,进一步进行优化,__set()、__get()等魔术方法中添加了对行为的判断。例如__get()方法这样写:

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            // read property, e.g. getName()
            return $this->$getter();
        }

        // behavior property
        $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);
        }

        throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
    }

我们可以看到,如果从当前组件中没有获取到属性的话,就会从当前组件绑定的行为中开始找,寻找行为对象中的属性,找到并返回。实现了对当前组件类的扩展。其他方法也是一样的。

最后附上component.php的源码解析:

<?php
namespace yii\base;

use Opis\Closure\ClosureStream;
use Yii;
use yii\helpers\StringHelper;


class Component extends BaseObject
{
  
    private $_events = [];

    private $_eventWildcards = [];

    private $_behaviors;

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            // read property, e.g. getName()
            return $this->$getter();
        }

        // behavior property
        $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);
        }

        throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
    }

    public function __set($name, $value)
    {
        // 魔术方法。
        // 如果存在设置器, 就调用设置器
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            // set property
            $this->$setter($value);

            return;
        } elseif (strncmp($name, 'on ', 3) === 0) {
            // "on "开头,表示是事件事件处理器,将事件处理器绑定到对应的时间名称
            // on event: attach event handler
            $this->on(trim(substr($name, 3)), $value);

            return;
        } elseif (strncmp($name, 'as ', 3) === 0) {
            // "as "开头,表示是行为。将行为附加到行为对象全局树。
            // as behavior: attach behavior
            $name = trim(substr($name, 3));
            $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

            return;
        }

        // 既不存在设置器,也不是事件和行为。那么可能是行为的属性。
        // behavior property
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $behavior) {
            if ($behavior->canSetProperty($name)) {
                $behavior->$name = $value;
                return;
            }
        }

        // 存在获取器,表示这个属性是只读的。
        if (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
        }

        throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
    }

    public function __isset($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter() !== null;
        }

        // 区别于基类BaseObject的方法,新增一层行为的属性。

        // behavior property
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $behavior) {
            if ($behavior->canGetProperty($name)) {
                return $behavior->$name !== null;
            }
        }

        return false;
    }

    public function __unset($name)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter(null);
            return;
        }

        // behavior property
        $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);
    }

    public function __call($name, $params)
    {
        // 调用行为的方法。

        $this->ensureBehaviors();
        foreach ($this->_behaviors as $object) {
            if ($object->hasMethod($name)) {
                return call_user_func_array([$object, $name], $params);
            }
        }
        throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
    }

    public function __clone()
    {
        // 被克隆的时候,清空克隆对象的行为和事件。
        $this->_events = [];
        $this->_eventWildcards = [];
        $this->_behaviors = null;
    }

    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
    }

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

    public function behaviors()
    {
        // 返回行为列表。默认都是空,如果需要添加,需要在子类覆盖
        // 覆盖需注意:
        // 返回的键是行为名称(也可以不设置键),返回的值是对应的扩展行为对象实例
        return [];
    }

    public function hasEventHandlers($name)
    {
        // 又是他,这块是判断事件是否绑定了处理程序
        $this->ensureBehaviors();

        // 通配符模式,如果存在行为名称且存在事件处理,返回真。
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
            if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) {
                return true;
            }
        }

        // 如果当前存在事件存在值,就返回真。否则在事件类里面判断。
        return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
    }

    public function on($name, $handler, $data = null, $append = true)
    {
        // 将事件处理程序绑定到对应事件。时间处理程序支持以下几种方式:
        // 1. 匿名函数
        // 2. 函数的方法
        // 3. 类的静态方法
        // 4. 全局函数

        $this->ensureBehaviors();

        // 通配符模式
        if (strpos($name, '*') !== false) {
            if ($append || empty($this->_eventWildcards[$name])) { // 如果追加,或者当前事件名称的处理程序为空。就把当前处理程序给它加到最后一位。
                $this->_eventWildcards[$name][] = [$handler, $data];
            } else { // 如果不追加,那么就将事件处理程序加在第一位,优先处理。
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
            }
            return;
        }

        // 普通模式
        if ($append || empty($this->_events[$name])) {
            $this->_events[$name][] = [$handler, $data];
        } else {
            array_unshift($this->_events[$name], [$handler, $data]);
        }
    }

    public function off($name, $handler = null)
    {
        // 将事件处理程序移除对应事件

        $this->ensureBehaviors();

        // 如果当前事件不存在,返回false
        if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) {
            return false;
        }

        // 如果处理器是null,直接清除当前事件所有处理器
        if ($handler === null) {
            unset($this->_events[$name], $this->_eventWildcards[$name]);
            return true;
        }

        // 是否已经移除
        $removed = false;
        // plain event names
        // 普通事件名称清除
        if (isset($this->_events[$name])) { // 这个判断多余了吧?
            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;
            }
        }

        // wildcard event names
        // 通配符事件名称处理
        if (isset($this->_eventWildcards[$name])) { // 这个判断多余了吧?
            foreach ($this->_eventWildcards[$name] as $i => $event) {
                if ($event[0] === $handler) {
                    unset($this->_eventWildcards[$name][$i]);
                    $removed = true;
                }
            }
            if ($removed) {
                $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
                // remove empty wildcards to save future redundant regex checks:
                if (empty($this->_eventWildcards[$name])) {
                    unset($this->_eventWildcards[$name]);
                }
            }
        }

        return $removed;
    }

    public function trigger($name, Event $event = null)
    {
        // 触发事件

        $this->ensureBehaviors();

        $eventHandlers = [];
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
            // 如果当前事件名称,和通配符匹配了。就把通配符的处理器保存起来。
            if (StringHelper::matchWildcard($wildcard, $name)) {
                $eventHandlers = array_merge($eventHandlers, $handlers);
            }
        }

        // 合并通配符匹配到的处理器,与当前事件的处理器。
        if (!empty($this->_events[$name])) {
            $eventHandlers = array_merge($eventHandlers, $this->_events[$name]);
        }

        if (!empty($eventHandlers)) {
            if ($event === null) {
                $event = new Event();
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($eventHandlers 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);
    }


    public function getBehavior($name)
    {
        $this->ensureBehaviors();
        return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
    }

    public function getBehaviors()
    {
        $this->ensureBehaviors();
        return $this->_behaviors;
    }

    public function attachBehavior($name, $behavior)
    {
        $this->ensureBehaviors();
        return $this->attachBehaviorInternal($name, $behavior);
    }

    public function attachBehaviors($behaviors)
    {
        $this->ensureBehaviors();
        foreach ($behaviors as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }

    public function detachBehavior($name)
    {
        $this->ensureBehaviors();
        if (isset($this->_behaviors[$name])) {
            $behavior = $this->_behaviors[$name];
            unset($this->_behaviors[$name]);
            $behavior->detach();
            return $behavior;
        }

        return null;
    }

    public function detachBehaviors()
    {
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $name => $behavior) {
            $this->detachBehavior($name);
        }
    }


    public function ensureBehaviors()
    {
        // 确保行为已经存在当前类的$this->_behaviors属性
        // 只要是涉及到行为的操作的方法,都会调用一遍这个方法。这个其实可以在构造方法里面,只调用一次就可以了。不知道作者为啥要这样写。。。

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


    private function attachBehaviorInternal($name, $behavior)
    {
        // 不属于行为类的对象,则生成一个实例($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;
    }
}

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值