浅析Yii2.0的行为Behavior

栏目: PHP · 发布时间: 6年前

内容简介:概念理解:使用行为(behavior)可以在不修改现有类的情况下,对类的功能进行扩充。 通过将行为绑定到一个类,可以使类具有行为本身所定义的属性和方法,就好像类本来就有这些属性和方法一样。 而不需要写一个新的类去继承或包含现有类。在功能上类似于 Traits ,达到类似于多继承的目的。我们先来看一下

概念理解:使用行为(behavior)可以在不修改现有类的情况下,对类的功能进行扩充。 通过将行为绑定到一个类,可以使类具有行为本身所定义的属性和方法,就好像类本来就有这些属性和方法一样。 而不需要写一个新的类去继承或包含现有类。在功能上类似于 Traits ,达到类似于多继承的目的。

行为的实现demo

<?php

namespace common\components;


use yii\base\Component;

// 待绑定行为的类
class MyClass extends Component
{
    
}


<?php


namespace common\components;


use yii\base\Behavior;

// 定义一个行为类
class BehaviorTest extends Behavior
{
    const EVENT_AFTER_SAVE = 'eventAfterAttach';

    public $_val = '我是BehaviorTest里面的公有属性_val';

    public function getOutput()
    {
        echo '我是BehaviorTest里面的公有方法getOutput';
    }

    public function events()
    {
        return [
            self::EVENT_AFTER_SAVE => 'afterAttach'
        ];
    }

    public function afterAttach()
    {
        echo '事件已触发';
    }
}

// 在控制器或者命令行下调用

$myClass = new MyClass();

$myBehavior = new BehaviorTest();

// 将行为绑定到 MyClass 的实例
$myClass->attachBehavior('test', $myBehavior);

// MyClass 实例调用行为类中的属性
echo $myClass->_val;

// MyClass 实例调用行为类中的方法
$myClass->getOutput();

// MyClass 实例触发行为类中定义的事件
$myClass->trigger(BehaviorTest::EVENT_AFTER_SAVE);

行为的绑定原理

我们先来看一下 $myClass->attachBehavior('test', $myBehavior); 行为绑定的时候做了哪些事情?好的,又是我们的老朋友 yii\base\Component

// yii\base\Component 的部分代码

private $_behaviors;

public function behaviors()
{
    return [];
}

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

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

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

$myClass 调用 attachBehavior() 传入俩个参数,一个是行为名称,另一个是行为类的名称或实例或者是数组,接着 attachBehavior() 调用了 ensureBehaviors() ,这个函数我们暂时用不到,因为我们没有在 MyClass 里面重载 behaviors() ,不过也能大概猜到 ensureBehaviors() 的用途了。再往下调用的是私有函数 attachBehaviorInternal() ,这个函数先判断传进来的 $behavior 是否已实例化,如果还没有则进行实例化,再通过 $name 判断是匿名行为还是命名行为,如果是命名行为,需要查看是否已经绑定同名的行为,如果绑定了同名的行为,会将以前的行为先解绑再调用 $behavior->attach($this); [注:这里 $this 指的 MyClass 实例,也就是 $myClass ],这样我们就来到了 yii\base\Behaviorattach() 方法,下面是 attach() 方法的源码:

public function attach($owner)
{
    $this->owner = $owner;
    foreach ($this->events() as $event => $handler) {
        $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
    }
}

$this->owner = $owner; [注:这里 $this 指的是 $behavior 也就是类 BehaviorTest 的实例],这句代码指定了行为类的主人是谁,后面的代码看着似乎似曾相识?是的,就是将行为类 events() 方法里面的事件也绑定的宿主身上,这里不具体展开,有兴趣的小伙伴可以看一下 浅析Yii2.0的事件Event 。最后将行为名称和行为实例放到 $myClass 的属性 _behavior 中,至此,行为的绑定就结束了。好像也没干什么啊,我们现在可以打印一下 $myClass 的数据结构是怎样的?

common\components\MyClass Object
(
    [_events:yii\base\Component:private] => Array
        (
            [eventAfterAttach] => Array
                (
                    [0] => Array
                        (
                            [0] => Array
                                (
                                    [0] => common\components\BehaviorTest Object
                                        (
                                            [_val] => 我是BehaviorTest里面的公有属性_val
                                            [hello:common\components\BehaviorTest:private] => 我是BehaviorTest里面的私有属性hello
                                            [owner] => common\components\MyClass Object
 *RECURSION*
                                        )

                                    [1] => afterAttach
                                )

                            [1] => 
                        )

                )

        )

    [_eventWildcards:yii\base\Component:private] => Array
        (
        )

    [_behaviors:yii\base\Component:private] => Array
        (
            [test] => common\components\BehaviorTest Object
                (
                    [_val] => 我是BehaviorTest里面的公有属性_val
                    [hello:common\components\BehaviorTest:private] => 我是BehaviorTest里面的私有属性hello
                    [owner] => common\components\MyClass Object
 *RECURSION*
                )

        )

)

可以看到 $myClass 已经绑定了一个行为 test ,绑定了一个事件 eventAfterAttach ,那么绑定行为以后,是怎么调用行为类里面的属性和方法呢?

行为的使用原理

还是看一下demo里面 $myClass->_val 这句代码是怎么执行的?根据上面 $myClass 的数据结构可以看出并没有 _val 这个属性,但是 yii\base\Component 里面实现了 __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);
}

又似曾相识?是的,跟 yii\base\BaseObject 里面属性的实现类似,有兴趣的小伙伴可以看一下 浅析Yii2.0的属性Property 。然后直接看注释 behavior property 部分,又去调用了 ensureBehaviors() ,先不管,接着又去遍历 _behaviors 这个属性,根据上面 $myClass 的数据结构得知,此时foreach里面的 $behavior 就是行为类 common\components\BehaviorTest 实例,先通过 canGetProperty 判断 _val 是否可读或者存在,大家可以去看 yii\base\BaseObject 里面该方法的实现。我们这里返回的是 true ,然后就直接通过 common\components\BehaviorTest 的实例 $behavior 返回 _val 的值。

根据上面获取行为类里面属性的流程我们注意到:

  1. 因为是通过实例化行为类去调用的属性,所以属性是 protected 或者是 private 是获取不到的。
  2. 如果 Component 绑定了多个行为,并且多个行为中有同名的属性,那么该 Component 获取的是第一个行为类里面的该属性。

那么行为类里面的方法是怎么被调用的呢?属性的调用是通过 __get() 来实现的,很容易联想到方法的调用是通过 __call() 来实现的,我们查看一下 yii\base\BaseObject 源码,果然里面实现 __call() 这个魔术方法,下面是源码,然后对照上面 $myClass 的数据结构一看就明白了。需要注意的是,跟上面属性的调用一样,方法也必须是 public 的, protected 、private 方法是调用不了的。

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()");
}

注意到 __call() 里面又有一个老朋友 ensureBehaviors() 这个函数似乎无处不在?是的,查看一下 yii\base\Component 里面的源码我们可以发现所有的公有方法都调用了这个函数,那么这个函数到底是干嘛的呢,其实 demo 里面绑定行为的方式可以称为主动绑定,就是我们主动调用函数 attachBehavior() 去绑定行为的,对应的就是被动绑定了,实现方式就是在待绑定行为的类里面重载 behaviors() 这个函数就可以实现绑定了,相当于一个行为的配置项。俩种绑定方式看个人喜好了,如果一个类需要绑定的行为很明确,推荐使用配置项的方法去绑定,也就是被动绑定。下面是将 demo 里面的绑定方式改成被动绑定。

<?php

namespace common\components;


use yii\base\Component;

class MyClass extends Component
{

    public function behaviors()
    {
        return [
            'test' => new BehaviorTest()
        ];
    }
}

此时可以将demo中 $myClass->attachBehavior('test', $myBehavior); 这句代码去掉,$myClass也是同样可以调用类 BehaviorTest 里面的属性和方法

小结

这俩天查看了 Yii2.0 事件、行为的实现方式,觉得有很多相似的地方,都是通过 yii\base\Component 来实现的,通过打印的数据结构也可以看到, Component 主要就是围绕 _events _eventWildcards _behaviors 这三个属性展开的,其中第二个属性是 事件的通配符模式,也可以归属到 事件中,那么这样 Component 的主要功能就是就是实现了 事件和行为。并且实现原理上也是相似的,都是往 Component 里面绑定事件和行为的 handle ,然后触发事件或者行为的时候,再去回调相应的 handle 。不过在解除的时候虽然都是删掉相应的 handle ,但是解除行为还需要解除在绑定行为的时候绑定的事件,这点不太一样。


以上所述就是小编给大家介绍的《浅析Yii2.0的行为Behavior》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Game Programming Patterns

Game Programming Patterns

Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95

The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具