内容简介:概念理解:使用行为(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\Behavior
的 attach()
方法,下面是 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
的值。
根据上面获取行为类里面属性的流程我们注意到:
-
因为是通过实例化行为类去调用的属性,所以属性是
protected
或者是private
是获取不到的。 -
如果
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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro JavaScript Design Patterns
Dustin Diaz、Ross Harmes / Apress / 2007-12-16 / USD 44.99
As a web developer, you’ll already know that JavaScript™ is a powerful language, allowing you to add an impressive array of dynamic functionality to otherwise static web sites. But there is more power......一起来看看 《Pro JavaScript Design Patterns》 这本书的介绍吧!