当前位置: 首页 > 后端技术 > PHP

Yii2.0的Behavior

时间:2023-03-30 01:29:10 PHP

概念理解分析:使用behavior(行为)可以在不修改已有类的情况下扩展类的功能。通过将行为绑定到类,可以使类具有行为本身定义的属性和方法,就好像类拥有它们一样。而不是编写一个新类来继承或包含现有类。它在功能上类似于Traits,达到类似多重继承的目的。行为实现demo'afterAttach'];}publicfunctionafterAttach(){echo'事件已被触发';}}//调用$myClass=newMyClass();$myBehavior=newBehaviorTest();//将行为绑定到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;publicfunctionbehaviors(){return[];}publicfunctionattachBehavior($name,$behavior){$this->ensureBehaviors();返回$this->attachBehaviorInternal($name,$behavior);}publicfunctionensureBehaviors(){if($this->_behaviors===null){$this->_behaviors=[];foreach($this->behaviors()as$name=>$behavior){$this->attachBehaviorInternal($name,$behavior);}}}私有函数attachBehaviorInternal($name,$behavior){if(!($behaviorinstanceofBehavior)){$behavior=Yii::createObject($behavior);}if(is_int($name)){$behavior->attach($this);$this->_behaviors[]=$behavior;}else{if(isset($this->_behaviors[$name])){$this->_behaviors[$name]->detach();}$行为r->附加($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()方法的源码:publicfunction附加($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\MyClassObject([_events:yii\base\Component:private]=>Array([eventAfterAttach]=>Array([0]=>Array([0]=>Array([0]=>common\components\BehaviorTestObject([_val]=>IamthepublicpropertyinBehaviorTest_val[owner]=>common\components\MyClassObject*RECURSION*)[1]=>afterAttach)[1]=>)))[_eventWildcards:yii\base\Component:private]=>Array()[_behaviors:yii\base\Component:private]=>Array([test]=>common\components\BehaviorTestObject([_val]=>IamthepublicpropertyinBehaviorTest_val[owner]=>common\components\MyClassObject*RECURSION*)))可以看到$myClass绑定了一个行为测试和一个事件eventAfterAttach,那么绑定行为后如何调用行为类中的属性和方法呢?行为的使用原理是看demo中的$myClass->_val代码是如何执行的?根据上面$myClass的数据结构可以看出,并没有属性_val,而是在yii\base\Component中实现了魔术方法__get()。我们来看看源代码公共函数__get($name){$getter='get'.$名称;if(method_exists($this,$getter)){//读取属性,例如getName()返回$this->$getter();}//行为属性$this->ensureBehaviors();foreach($this->_behaviorsas$behavior){if($behavior->canGetProperty($name)){返回$behavior->$name;}}if(method_exists($this,'set'.$name)){thrownewInvalidCallException('Gettingwrite-onlyproperty:'.get_class($this).'::'.$name);}thrownewUnknownPropertyException('Gettingunknownproperty:'.get_class($this).'::'.$name);}又似曾相识?是的,类似于yii\base\BaseObject中属性的实现。有兴趣的小伙伴可以看看Yii2.0的property属性。然后直接看注释的behavior属性部分,然后调用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的方法。公共函数__call($name,$params){$this->ensureBehaviors();foreach($this->_behaviorsas$object){if($object->hasMethod($name)){returncall_user_func_array([$object,$name],$params);}}}thrownewUnknownMethodException('Callingunknownmethod:'.get_class($this)."::$name()");}注意到还有个老友ensureBehaviors()这个函数好像到处都有?是的,查看yii\base\Component中的源码可以发现,所有的public方法都会调用这个函数,那么这个函数是做什么的呢?其实demo中的绑定行为可以称为主动绑定,即我们主动调用函数attachBehavior()绑定行为,对应被动绑定。实现方式是在要绑定的类中重载behaviors()函数来实现绑定,相当于一个行为的一个配置项。两种绑定方式看个人喜好。如果一个类需要绑定的行为非常明确,建议使用配置项的方式进行绑定,即被动绑定。下面是将demo中的绑定方式改为被动绑定。newBehaviorTest()];}}现在$myClass->attachBehavior('test',$myBehavior);去掉这段代码,$myClass也可以调用类BehaviorTest中的属性和方法,都是通过yii\base\Component实现的。从打印出来的数据结构也可以看出,Component主要围绕_events_eventWildcards_behaviors这三个属性展开。第二个属性是事件的通配符模式,也可以归于事件,所以Component的主要作用就是实现事件和行为。而且实现原理也差不多,都是将事件和行为句柄绑定到Component上,然后当事件或行为被触发时,回调相应的句柄。但是,虽然在取消时删除了对应的句柄,但是在取消行为时,绑定行为时绑定的事件也需要取消,这是不同的。以上总结是指对Yii2.0的深入理解。其实以前也见过,但也仅限于看到而已。我没有运行demo调试和查看源代码。然后我误以为我明白了。事实上,两天后我什么都不记得了。向上。所以现在想通过写博客来加深自己的理解。由于本人水平有限,欢迎各位朋友交流指正。

最新推荐
猜你喜欢