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