PHP的Yii框架中行为的定义与绑定方法讲解
定义行为
要定义行为,通过继承yii\base\Behavior或其子类来建立一个类。如:
namespaceapp\components; useyii\base\Behavior; classMyBehaviorextendsBehavior { public$prop1; private$_prop2; publicfunctiongetProp2() { return$this->_prop2; } publicfunctionsetProp2($value) { $this->_prop2=$value; } publicfunctionfoo() { //... } }
以上代码定义了行为类app\components\MyBehavior并为要附加行为的组件提供了两个属性prop1、prop2和一个方法foo()。注意属性prop2是通过gettergetProp2()和settersetProp2()定义的。能这样用是因为yii\base\Object是yii\base\Behavior的祖先类,此祖先类支持用getter和setter方法定义属性
提示:在行为内部可以通过yii\base\Behavior::owner属性访问行为已附加的组件。
静态方法绑定行为
静态绑定行为,只需要重载yii\base\Component::behaviors()就可以了。这个方法用于描述类所具有的行为。如何描述呢?使用配置来描述,可以是Behavior类名,也可以是Behavior类的配置数组:
namespaceapp\models; useyii\db\ActiveRecord; useapp\Components\MyBehavior; classUserextendsActiveRecord { publicfunctionbehaviors() { return[ //匿名的行为,仅直接给出行为的类名称 MyBehavior::className(), //名为myBehavior2的行为,也是仅给出行为的类名称 'myBehavior2'=>MyBehavior::className(), //匿名行为,给出了MyBehavior类的配置数组 [ 'class'=>MyBehavior::className(), 'prop1'=>'value1', 'prop3'=>'value3', ], //名为myBehavior4的行为,也是给出了MyBehavior类的配置数组 'myBehavior4'=>[ 'class'=>MyBehavior::className(), 'prop1'=>'value1', 'prop3'=>'value3', ] ]; } }
还有一个静态的绑定办法,就是通过配置文件来绑定:
[ 'asmyBehavior2'=>MyBehavior::className(), 'asmyBehavior3'=>[ 'class'=>MyBehavior::className(), 'prop1'=>'value1', 'prop3'=>'value3', ], ]
动态方法绑定行为
动态绑定行为,需要调用yii\base\Compoent::attachBehaviors():
$Component->attachBehaviors([ 'myBehavior1'=>newMyBehavior,//这是一个命名行为 MyBehavior::className(),//这是一个匿名行为 ]);
这个方法接受一个数组参数,参数的含义与上面静态绑定行为是一样一样的。
在上面的这些例子中,以数组的键作为行为的命名,而对于没有提供键名的行为,就是匿名行为。
对于命名的行为,可以调用yii\base\Component::getBehavior()来取得这个绑定好的行为:
$behavior=$Component->getBehavior('myBehavior2');
对于匿名的行为,则没有办法直接引用了。但是,可以获取所有的绑定好的行为:
$behaviors=$Component->getBehaviors();
绑定的内部原理
只是重载一个yii\base\Component::behaviors()就可以这么神奇地使用行为了?这只是冰山的一角,实际上关系到绑定的过程,有关的方面有:
yii\base\Component::behaviors() yii\base\Component::ensureBehaviors() yii\base\Component::attachBehaviorInternal() yii\base\Behavior::attach()
4个方法中,Behavior只占其一,更多的代码,是在Component中完成的。
yii\base\Component::behaviors()上面讲静态方法绑定行为时已经提到了,就是返回一个数组用于描述行为。那么yii\base\Component::ensuerBehaviors()呢?
这个方法会在Component的诸多地方调用__get()__set()__isset()__unset()__call()canGetProperty()hasMethod()hasEventHandlers()on()off()等用到,看到这么多是不是头疼?一点都不复杂,一句话,只要涉及到类的属性、方法、事件这个函数都会被调用到。
这么众星拱月,被诸多凡人所需要的ensureBehaviors()究竟是何许人也?就像名字所表明的,他的作用在于“ensure”。其实只是确保behaviors()中所描述的行为已经进行了绑定而已:
publicfunctionensureBehaviors() { //为null表示尚未绑定 //多说一句,为空数组表示没有绑定任何行为 if($this->_behaviors===null){ $this->_behaviors=[]; //遍历$this->behaviors()返回的数组,并绑定 foreach($this->behaviors()as$name=>$behavior){ $this->attachBehaviorInternal($name,$behavior); } } }
这个方法主要是对子类用的,yii\base\Compoent没有任何预先注入的行为,所以,这个调用没有用。但是对于子类,你可能重载了yii\base\Compoent::behaviros()来预先注入一些行为。那么,这个函数会将这些行为先注入进来。
从上面的代码中,自然就看到了接下来要说的第三个东东,yii\base\Component\attachBehaviorInternal():
privatefunctionattachBehaviorInternal($name,$behavior) { //不是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(); } $behavior->attach($this); $this->_behaviors[$name]=$behavior; } return$behavior; }
首先要注意到,这是一个private成员。其实在Yii中,所有后缀为*Internal的方法,都是私有的。这个方法干了这么几件事:
如果$behavior参数并非是一个Behavior实例,就以之为参数,用Yii::createObject()创建出来。
如果以匿名行为的形式绑定行为,那么直接将行为附加在这个类上。
如果是命名行为,先看看是否有同名的行为已经绑定在这个类上,如果有,用后来的行为取代之前的行为。
在yii\base\Component::attachBehaviorInternal()中,以$this为参数调用了yii\base\Behavior::attach()。从而,引出了跟绑定相关的最后一个家伙yii\base\Behavior::attach(),这也是前面我们讲行为的要素时没讲完的。先看看代码:
publicfunctionattach($owner) { $this->owner=$owner; foreach($this->events()as$event=>$handler){ $owner->on($event,is_string($handler)?[$this,$handler]: $handler); } }
上面的代码干了两件事:
- 设置好行为的$owner,使得行为可以访问、操作所依附的对象
- 遍历行为中的events()返回的数组,将准备响应的事件,通过所依附类的on()绑定到类上
总结
说了这么多,关于绑定,做个小结:
- 绑定的动作从Component发起;
- 静态绑定通过重载yii\base\Componet::behaviors()实现;
- 动态绑定通过调用yii\base\Component::attachBehaviors()实现;
- 行为还可以通过为Component配置as配置项进行绑定;
- 行为有匿名行为和命名行为之分,区别在于绑定时是否给出命名。命名行为可以通过其命名进行标识,从而有针对性地进行解除等操作;
- 绑定过程中,后绑定的行为会取代已经绑定的同名行为;
- 绑定的意义有两点,一是为行为设置$owner。二是将行为中拟响应的事件的handler绑定到类中去。