对Yii框架中行为【behavior】的理解

什么是行为

所谓行为,其实说白了,就是把A类绑定到B类上,可以在不修改B类的情况下,对B类进行功能和属性的扩充,这样B类就拥有了A类的属性和方法,只是在这里,我们习惯成A类为行为。

举一个例子:

class MyClass
{
    public $hello = "我是类的自有属性hello".PHP_EOL;

    public function hello()
    {
        echo "我是类的自有方法hello".PHP_EOL;
    }
}

class MyBehavior
{

    public $hi = "我是行为的自有属性hi".PHP_EOL;

    public function hi()
    {
        echo "我是行为的自有方法hi".PHP_EOL;
    }
}

$class = new MyClass();
$behavior = new MyBehavior();

$class->attachBehavior('behavior1',$behavior);//将类和行为进行绑定,这里为$behavior绑定了一个别名:behavior1

echo $class->hello;//输出:我是类的自有属性hello
$class->hello();//输出:我是类的自有方法hello

echo $class->hi;//期望输出:我是行为的自有属性hi
$class->hi();//期望输出:我是行为的自有方法hi

实现自己的行为

实现行为的关键知识点:__get(),__set(),__call(),call_user_func_array,前三个是PHP的魔术方法,这四个是非常重要的知识点

以上伪代码就是行为的实现,现在我们来动手实现自己的行为:

//定义一个基类,后面的类都来继承它
class Base
{
    //存放行为类的容器数组
    private $_behaviors = [];

    //绑定行为类:其实就是放入数组$_beheviors
    public function attachBehavior($name,$behavior)
    {
        //如果$_beheviors里有这个别名的话,先unset掉,给新绑定的行为让路
        if(isset($this->_behaviors[$name])) {
            unset($this->_behaviors[$name]);
        }
        $this->_behaviors[$name] = $behavior;
    }

    
    public function __get($name)
    {
        foreach($this->_behaviors as $behavior) {
            if($behavior->hasProperty($name)) {
                return $behavior->$name;
            }
        }
    }

    public function __set($name,$value)
    {
        foreach($this->_behaviors as $behavior) {
            if($behavior->hasProperty($name)) {
                $behavior->$name = $value;
            }
        }
    }

    public function __call($method,$params)
    {
        foreach($this->_behaviors as $behavior) {
            if($behavior->hasMethod($method)) {
                call_user_func_array([$behavior,$method],$params);
            }
        }
    }
}

//定义行为基类,所有的行为类都继承它
class Behavior
{
    //判断该行为类是否有$name属性
    public function hasProperty($name)
    {
        return property_exists($this,$name);
    }

    //判断该行为类是否有$method方法
    public function hasMethod($method)
    {
        return method_exists($this,$method);
    }
}

定义好这两个基类之后,我们还要修改一下MyClass和MyBehavior:


class MyClass extends Base
{
    // 让Myclass继承自Base,才能绑定行为
    ···
}


class MyBehavior extends Behavior
{
    // 让MyBehavior继承自Behavior
    ···
}

然后我们继续运行我们的代码:

$class = new MyClass();
$behavior = new MyBehavior();
$class->attachBehavior('behavior1',$behavior);

echo $class->hello;// 输出:我是类的自有属性hello
$class->hello();// 输出:我是类的自有方法hello

echo $class->hi;// 输出:我是行为的自有属性hi
$class->hi();// 输出:我是行为的自有方法hi

// 这里动态去设置行为的属性
$class->hi = "我是重新设置好的行为的自有属性hi".PHP_EOL;
echo $class->hi;// 输出:我是重新设置好的行为的自有属性hi

看到了吗,我们想要的期望输出,这回变成现实了,整体还是很好理解的。

这里没有考虑行为的方法和属性的权限问题,例子里都写成了Public,只是方便举例和测试,实际开发中肯定要进行权限控制判断的。

Yii中的行为

如何使用Yii中的行为

  • 由于在Yii中,实现行为的功能是yii\base\Component中的,所以想实现行为,就必须从yii\base\Component继承;
  • 从yii\base\Behavior派生自己的行为类;
  • 将Component和Behavior绑定起来;
  • 像使用Component自己的属性和方法一样,使用绑定好的行为中的属性和方法;

行为的绑定

在Yii中,一个行为的绑定实现步骤是这样的:

* yii\base\Component::behaviors();             //第一步
* yii\base\Component::ensureBehaviors();       //第二步
* yii\base\Behavior::attach();                 //第三步

在yii项目实际的开发中,我们很少手动去绑定行为,一般都是定义一个数组,如下所示:

class MyClass
{
    // 定义好的要绑定的行为数组
    public function behaviors()
    {
        return [
            // 匿名的行为,仅给出行为的类名
            MyBehavior::className(),

            // 名为myBehavior2的行为,也是仅给出行为的类名
            'myBehavior2' => MyBehavior::className(),

            // 匿名行为,给出了MyBehavior类的配置数组
            [
                'class' => MyBehavior::className(),
            ],
        ];
    }
}

那么,yii是如何去绑定上述定义好的数组的呢?答案在第二步,yii\base\Component::ensureBehaviors():

public function ensureBehaviors()
{
    if ($this->_behaviors === null) {
        $this->_behaviors = [];
        // 遍历 $this->behaviors()数组,并绑定
        foreach ($this->behaviors() as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }
}

ensureBehaviors()方法其实是调用了attachBehaviorInternal():

private function attachBehaviorInternal($name, $behavior)
{
    // 如果不是Behavior的实例,比如说只是一个类名的字符串,或者一个配置数组
    if (!($behavior instanceof Behavior)) {
        // 就把这个对象创建出来
        // createObject()人如其名,就是用来创建对象的,这个知识点在Yii的服务定位器中,其实就是去容器里取出来对象
        $behavior = Yii::createObject($behavior);
    }

    // 如果是int,说明在配置$behaviors数组的时候,没有对其人为的设定一个key,那肯定就是默认的数字索引
    if (is_int($name)) {
        $behavior->attach($this); // 这里是第四步
        $this->_behaviors[] = $behavior; //将行为放入$this->_behaviors容器
    } else {

        // 命名行为:

        // 已经有一个同名的行为,要先解除,再将新的行为绑定上去。
        if (isset($this->_behaviors[$name])) {
            $this->_behaviors[$name]->detach();
        }
        $behavior->attach($this); // 这里是第四步
        $this->_behaviors[$name] = $behavior; //将行为放入$this->_behaviors容器
    }

    return $behavior;
}

其实经过以上两步,Yii已经将行为绑定到Component的子类中,这个时候,已经可以使用行为的属性和方法了,那么第三步是干什么的呢,我们先看一下源码,代码在yii\base\Behavior::attach():

public function attach($owner)
{
    // 设置该行为的所有者
    this->owner = $owner;

    // 遍历行为类的事件,绑定到行为类的所有者身上,让所有者拥有触发事件的功能
    foreach ($this->events() as $event => $handler) {
        $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
    }
}

这个attach方法主要是为了把行为中定义的事件都绑定到行为的所有者身上,这样所有者就有了触发事件的能力

行为的解绑

行为的解绑的原理和绑定刚好相反,从行为数组$_behaviors中,unset掉要解绑的行为,代码在yii\base\Component::detachBehavior():

public function detachBehavior($name)
{
    $this->ensureBehaviors();
    if (isset($this->_behaviors[$name])) {
        $behavior = $this->_behaviors[$name];
        unset($this->_behaviors[$name]);
        $behavior->detach(); // 这个方法主要是为了解绑通过行为绑定给Component的事件
        return $behavior;
    }

    return null;
}

$behavior->detach()方法在yii\base\Behavior::detach():

public function detach()
{
    if ($this->owner) {
        foreach ($this->events() as $event => $handler) {
            // 解除Component的事件
            $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
        }
        $this->owner = null;
    }
}

行为的属性和方法注入的原理分析

yii的行为实现,其实和我们文章一开始自己写的行为类实现原理是一样的,主要用到了__get(),__set(),__call()魔术方法:

属性的获取:
public function __get($name)
{
    // 由于在yii的基类Object里为属性定义了getter()方法
    // 所以获取属性的时候,先判断该属性有没有对应的getter()方法
    $getter = 'get' . $name;
    if (method_exists($this, $getter)) {
        // read property, e.g. getName()
        return $this->$getter();
    }

    // 如果没有getter(),那么该属性就可能是行为的属性
    // 所以遍历行为数组,如果哪个行为有这个属性,就返回
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $behavior) {
        if ($behavior->canGetProperty($name)) {
            return $behavior->$name;
        }
    }

    ···
}
属性的设置:
public function __set($name, $value)
{
    // 设置一个属性的时候,如果有setter()方法,那么优先调用setter
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        $this->$setter($value);
        return;
    }

    
    $this->ensureBehaviors();
    // 遍历行为数组,如果行为有$name属性,那么就设置为$value
    foreach ($this->_behaviors as $behavior) {
        if ($behavior->canSetProperty($name)) {
            $behavior->$name = $value;
            return;
        }
    }

    ···
}
方法的获取:
public function __call($name, $params)
{
    $this->ensureBehaviors();
    
    // 遍历行为数组
    foreach ($this->_behaviors as $object) {
        // 如果某个行为有$name这个方法体,那么就调用,参数为$params
        if ($object->hasMethod($name)) {
            return call_user_func_array([$object, $name], $params);
        }
    }
    ···
}

引出:行为和Traits的区别

php的traits是在5.4版本之后才引入的一个新特性,从实现效果上来看,行为和traits都能把自身的属性和方法注入到当前类中去,但是还是有一些区别:

  • 行为从本质上来讲,也是PHP的类,一个行为可以继承自另一个行为,从而实现代码的复用。而traits只是php的一种语法,效果上类似把tratis的代码导入到一个类中,从而实现代码的注入,特性是不支持继承的。
  • 行为可以支持动态的绑定,解绑,而不必对类进行修改。但是traits必须在类内使用use,才能导入,要解除traits时,则要删除这个use语句,也就是说要对类进行修改。
  • traits在效率上要比行为高一点,因为traits是php内部实现的,行为是我们后期自己实现的。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值