手撸php框架5.3--构建Component之行为的实现

本文介绍了一种PHP中的行为设计模式实现,包括行为类的绑定与解除逻辑、支持事件的处理方式以及通过懒加载确保行为的有效绑定。此外还讨论了如何在类中查找属性和方法时考虑已绑定的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.首先,我们需要一个行为的基类。行为的绑定是相互的,component要持有行为对象,同样行为对象也要持有component的对象,来标识行为依附的对象。所以,component有绑定的动作,行为也需要有绑定的动作。
代码如下:

<?php
namespace tank\base;

class BaseBehavior extends BaseObject
{
    public $subject;

    public function getEvents()//现在不用看这里
    {
        return [];
    }

    public function attach(Component $subject)
    {
        $this->subject = $subject;//现在只看到此处即可
        foreach ($this->getEvents() as $eventName => $eventHandler) {
            $eventHandler = is_string($eventHandler) ? [$this, $eventHandler] : $eventHandler;
            $this->subject->bindEvent($eventName, $eventHandler);
        }
    }

    public function detach()
    {
        if ($this->subject) {
            foreach ($this->getEvents() as $eventName => $eventHandler) {
                $eventHandler = is_string($eventHandler) ? [$this, $eventHandler] : $eventHandler;
                $this->subject->unbindEvent($eventName, $eventHandler);
            }//此处现在忽略即可
            $this->subject = null;
        }

    }
}

中间有一些关于行为支持事件的代码,现在可以不用关心,之后我们会提及
2.component中保存behavior的数据结构

$behaviorMap
[
    'behaviorName' => $behaviorObject,
    ....
]

数据结构相对简单,键名对应行为的名字,值对应行为对象。

3.绑定行为的方法,注意我们仅支持命名的行为,未命名的不处理

 public function attachSingleBehavior($behaviorName, $behaviorDefine)
{
    $this->loadBehaviors();//这里是5中提到的内容

    if (!is_string($behaviorName)) {//行为必须命名,否则无法绑定
        return;
    }

    if (!($behaviorDefine instanceof BaseBehavior)) {
        $behaviorDefine = Tank::generateObject($behaviorDefine);
    }

    if (isset($this->behaviorMap[$behaviorName])) {
        $this->behaviorMap[$behaviorName]->detach();//已经存在的同名行为,先解除绑定
    }
    $behaviorDefine->attach($this);
    $this->behaviorMap[$behaviorName] = $behaviorDefine;

    return $behaviorDefine;
}

4.解除绑定行为的方法

public function detachBehavior($behaviorName)
{
    $this->loadBehaviors();//这里是5中提到的内容

    if (isset($this->behaviorMap[$behaviorName])) {
        $behaviorObject = $this->behaviorMap[$behaviorName];
        unset($this->behaviorMap[$behaviorName]);
        $behaviorObject->detach();
        return $behaviorObject;
    } else {
        return null;
    }
}

5.我们支持在类中覆盖getBehaviors方法,返回行为数组的绑定行为的方式,这是用懒加载的方式实现的。所以,我们提供了loadBehaviors的方法,供在任意需要行为的地方先调用此方法,保证在类getBehaviors方法中定义的行为已经绑定到了。
loadBehaviors方法代码如下:

public function loadBehaviors()
{
    if (!$this->behaviorWasLoad()) {
        foreach ($this->getBehaviors() as $behaviorName => $behaviorDefine) {
            $this->attachSingleBehavior($behaviorName, $behaviorDefine);
        }
    }
}

6.现在要解决在行为中支持事件的问题,采用的方式是在BaseBehavior类中,允许在getEvents()方法中返回一个事件列表,在行为的attach和detach方法中写入绑定事件的逻辑。这样,在绑定和解绑行为时因为会分别调用行为对象的attach和detach方法,会自动地将事件在component事件数据结构中绑定或解绑。
具体代码参考1,3,4的代码。

7.因为有事件的引入,所以类在本类中找不到的属性和方法,要在各个行为中获取,所以也会影响getter和setter特性的实现,具体到代码中就是__set,__get,__call等魔术方法的实现。代码如下:

public function __set($proName, $value)
{
    $setter = 'set'.ucfirst($proName);

    if (method_exists($this, $setter)) {
        $this->$setter($value);
    } else {
        $this->loadBehaviors();
        foreach ($this->behaviorMap as $behavior) {
            if ($behavior->canSetProperty($proName)) {
                $behavior->$proName = $value;
                return;
            }
        }
    }
}

public function __get($proName)
{
    $getter = 'get'.ucfirst($proName);
    if (method_exists($this, $getter)) {
        return $this->$getter();
    } else {
        $this->loadBehaviors();
        foreach ($this->behaviorMap as $behavior) {
            if ($behavior->canGetProperty($proName)) {
                return $behavior->$proName;
            }
        }
    }
}

public function __isset($proName)
{
    $getter = 'get'.ucfirst($proName);
    if (method_exists($this, $getter)) {
        return $this->$getter() !== null;
    } else {
        $this->loadBehaviors();
        foreach ($this->behaviorMap as $behavior) {
            if ($behavior->canGetProperty($proName)) {
                return $behavior->$proName !== null;
            }
        }
    }
    return false;
}

public function __unset($proName)
{
    $setter = 'set'.ucfirst($proName);

    if (method_exists($this, $setter)) {
        $this->$setter(null);
        return;
    } else {
        $this->loadBehaviors();
        foreach ($this->behaviorMap as $behavior) {
            if ($behavior->canSetProperty($proName)) {
                $behavior->$proName = null;
                return;
            }
        }
    }
}

public function __call($methodName, $params)
{
    $this->loadBehaviors();
    foreach ($this->behaviorMap as $object) {
        if (method_exists($object, $methodName)) {
            return call_user_func_array([$object, $methodName], $params);
        }
    }
}

ok,今儿个先到这,欲知后事如何,且听下回分解……
github源码:https://github.com/2lovecode/tank

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值