首页 > Yii2之行为

Yii2之行为

  Yii三大特性:属性、事件、行为。前面两篇文章已经分别讲解了属性和事件,本文接着讲讲yii的行为,分析yii行为的实现原理。

   yii中,一个对象绑定了行为之后,就拥有了所绑定行为拥有的所有事件,而且可以访问所绑定行为的成员变量,调用其行为方法。那么,yii是怎么做到的呢?

  Yii中行为的实现需要yiiaseComponentyiiaseBehavior这两个类的交互与配合,其中Component是组件类,Behavior是行为类。Component这个类在上一篇文章中讲解事件的时候已经了解了它的事件机制的实现原理,本文将解析它是如何实现行为支持的,不过在这之前先来了解一下Behavior这个行为类。话不多说,先上yiiaseBehavior类源码:

class Behavior extends Object
{/*** 当前行为的拥有对象,一般为Component对象* @var type */public $owner;/*** 返回当前行为类拥有的所有事件,这些事件将被attach()方法绑定到行为对象的拥有者。* 配置格式:事件名称 => 事件处理器* 事件处理器的配置有4种格式:*                  1.当前类成员方法名称,字符串形式,相当于 [$this, '成员方法名称']*                  2.[类名, 方法名],数组形式*                  3.[对象, 方法名],数组形式*                  4.匿名函数,形式:function($event){ ... }* @return type*/public function events(){return [];}/*** 绑定行为到组件* @param type $owner*/public function attach($owner){$this->owner = $owner;foreach ($this->events() as $event => $handler) {//遍历行为的事件列表,调用组件对象的on()方法把所有事件都绑定到组件上$owner->on($event, is_string($handler) ? [$this, $handler] : $handler);}}/*** 从组件上解绑行为*/public function detach(){if ($this->owner) {foreach ($this->events() as $event => $handler) {//遍历行为的事件列表,调用组件对象的off()方法把所有事件从组件解绑$this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);}$this->owner = null;}}
}

  可以看到yiiaseBehavior行为类其实很简单,只有一个成员变量和三个成员方法。成员变量$owner用于保存绑定当前行为的对象,这个对象所属类应该支持事件与行为,在yii中一般为Component组件类或其子类的对象,因为绑定/解绑行为的时候需要通过这个对象调用其所属类的on()/off()方法来绑定/解绑事件。成员方法events()则用于配置当前行为类所拥有的所有事件,事件处理器有4种表示方法,具体已在上面源码注释中注明。attach()detach()两个方法分别用于绑定和解绑行为,它们分别调用$owner对象的on()off()方法将当前行为类拥有的全部事件绑定到$owner对象或从$owner对象解绑。

  我们知道,当一个Component组件对象要绑定一个Behavior行为的时候,其实主动方是这个Component组件对象,所以yii行为机制中行为的绑定和解绑都是由Component类发起的,下面就来看看Component是如何发起行为的绑定和解绑的。

        先来看看Component是如何绑定行为的。Component绑定行为有两种方式:静态绑定和动态绑定,首先来了解静态绑定方法。Component类使用成员方法behaviors()来返回一个配置数组表示当前组件拥有的所有行为,使用ensureBehaviors()方法来确保这些行为都已绑定到组件上,只要访问Component的成员方法,都会先去调用ensureBehaviors()方法,若发现这些行为尚未绑定则进行绑定,所以看起来behaviors()这里配置的行为是会自动绑定的,无需我们自己写代码去绑定,所以就称之为静态绑定。behaviors()中行为的表示方法有4中方式,这里直接上源码,主要看注释:

/*** 用于静态绑定行为* 当访问当前组件类的属性、方法、事件的时候,都先会调用ensureBehaviors()确保这里配置的所有行为都已经绑定到组件中,* 若未绑定则一一进行绑定,这里返回的行为列表最后会保存在_behaviors成员变量中。* @return 静态绑定到当前组件的所有行为的列表,这里的行为可以有4种表示形式:*			1. '行为类名称':匿名行为,只有行为类的名称*			2. '行为名称' => '行为类名称':命名行为,只有行为类名称*			3. [//匿名行为,配置数组*					'class' => '行为类名称',*					'属性名1' => '属性值1',*					'属性名2' => '属性值2'*			   ],*			4. '行为名称' => [//命名行为,配置数组*					'class' => '行为类名称',*					'属性名1' => '属性值1',*					'属性名2' => '属性值2'*			   ]*/
public function behaviors()
{return [];
}public function ensureBehaviors()
{if ($this->_behaviors === null) {$this->_behaviors = [];foreach ($this->behaviors() as $name => $behavior) {$this->attachBehaviorInternal($name, $behavior);}}
}

其中ensureBehaviors()方法又调用了attachBehaviorInternal()方法,这个稍后再讲。

  动态绑定行为则使用的是attachBehavior()方法,该方法源码如下:

/*** 为组件绑定一个行为* @param type $name:行为名称* @param type $behavior:行为配置,可以是一个对象,一个行为类名,或者一个行为类对象的配置数组* @return type*/
public function attachBehavior($name, $behavior)
{$this->ensureBehaviors();return $this->attachBehaviorInternal($name, $behavior);
}

  我们发现,不管是静态绑定还是动态绑定,最后都调用的是attachBehaviorInternal()方法,那么这个方法到底是干嘛的呢?先看源码:

/*** 为组件绑定一个行为* @param type $name:行为名称,若是数字,表示匿名行为* @param type $behavior:行为配置,可以是一个对象,一个行为类名,或者一个行为类对象的配置数组* @return type*/
private function attachBehaviorInternal($name, $behavior)
{if (!($behavior instanceof Behavior)) {//$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);//调用行为类对象的attach()方法,此方法将会把行为类对象的所有事件绑定到当前组件对象$this->_behaviors[$name] = $behavior;}return $behavior;
}

  可以看到attachBehaviorInternal()方法是负责和Behavior交互的,最终调用的是Behavior类的attach()方法,上面介绍Behavior的时候已经知道attach()会调用Component类的on()方法进行事件绑定,所以,绑定了行为之后,Component就拥有了这个行为所有的事件。另外,不管是静态绑定的事件还是动态绑定的,最终Component所拥有的行为都将保存在其成员变量_behaviors中。

   高度总结一下,Component绑定Behavior的过程其实是很简单的事情,只有两个步骤:

1. Component调用Behaviorattach()方法,将自身对象作为参数传递过去。

2. Behavior通过Component传递过去的对象调用Componenton()方法进行事件绑定。

 

   讲完了Component绑定行为,接下来看看Component是如何解绑行为的。Component解绑行为的方法是detachBehavior(),源码如下:

/*** 从组件解绑一个行为* @param type $name:行为名称* @return null或解绑的行为对象*/
public function detachBehavior($name)
{$this->ensureBehaviors();if (isset($this->_behaviors[$name])) {$behavior = $this->_behaviors[$name];unset($this->_behaviors[$name]);//从组件的行为列表中删除$behavior->detach();//调用行为对象的detach()方法,此方法将解绑此行为绑定在组件上的所有事件return $behavior;}return null;
}

  该方法最终调用Behaviordetach()方法,由于行为绑定的时候Behavior类的attach()方法中已经保存了行为的绑定者owner,所以解绑行为的

时候不需要传参了。在Componentdetach()方法中则调用了Component类的off()方法,将行为的所有事件从组件上解绑。

 

  这样就把yii行为的绑定和解绑原理解析完毕了。然而,文章开头说了,一个对象绑定了行为之后,不但拥有所绑定行为拥有的所有事件,而且还可以访问所绑定行为的成员变量,调用其行为方法。通过上面的讲解,我们知道Component确实拥有了Behavior的所有事件,但是没看出来Component可以访问Behavior的成员变量和方法啊。这就是魔术方法__get()__set()以及__call()的功劳了,Component类重载了这三个方法,源码如下:

/*** 重载PHP魔术方法__get()支持行为* @param type $name:属性名称* @return type* @throws InvalidCallException* @throws UnknownPropertyException*/
public function __get($name)
{$getter = 'get' . $name;if (method_exists($this, $getter)) {//当前对象存在$name属性的getter方法:直接调用return $this->$getter();}$this->ensureBehaviors();foreach ($this->_behaviors as $behavior) {//遍历当前对象已绑定的所有行为,若行为对象中存在$name属性且可读则返回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);
}/*** 重载PHP魔术方法__set()支持行为* @param type $name:属性名称* @param yiiaseBehavior $value* @return type* @throws InvalidCallException* @throws UnknownPropertyException*/
public function __set($name, $value)
{$setter = 'set' . $name;if (method_exists($this, $setter)) {//当前对象存在$name属性的setter方法:直接调用$this->$setter($value);return;} elseif (strncmp($name, 'on ', 3) === 0) {//$name以'on '开头,表示给当前对象的某个事件绑定处理器$this->on(trim(substr($name, 3)), $value);return;} elseif (strncmp($name, 'as ', 3) === 0) {//$name以'as '开头,表示给当前对象绑定行为$name = trim(substr($name, 3));$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));return;}$this->ensureBehaviors();foreach ($this->_behaviors as $behavior) {//遍历当前对象已绑定的所有行为,若行为对象中存在$name属性且可写则设置值if ($behavior->canSetProperty($name)) {$behavior->$name = $value;return;}}if (method_exists($this, 'get' . $name)) {throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);}throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}/*** 重写PHP魔术方法__call()支持行为,* @param type $name:方法名称* @param type $params:传递给方法的参数* @return type* @throws UnknownMethodException*/
public function __call($name, $params)
{$this->ensureBehaviors();foreach ($this->_behaviors as $object) {//遍历组件绑定的所有行为,若某个行为对象中存在$name成员方法则调用之if ($object->hasMethod($name)) {return call_user_func_array([$object, $name], $params);}}throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}

这样Component绑定了Behavior之后就可以访问Behavior的成员变量,调用Behavior的成员方法了!

 

 

 

 

转载于:https://www.cnblogs.com/wujuntian/p/7506040.html

更多相关:

  • 来源:公众号|计算机视觉工坊(系投稿)作者:仲夏夜之星「3D视觉工坊」技术交流群已经成立,目前大约有12000人,方向主要涉及3D视觉、CV&深度学习、SLAM、三维重建、点云后处理、自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、...

  • 点云PCL免费知识星球,点云论文速读。文章:Real-Time LIDAR-Based Urban Road and Sidewalk Detection for Autonomous Vehicles作者:Ern˝o Horváth  , Claudiu Pozna ,and Miklós Unger编译:点云PCL代码:http...

  • 文章:Semantic Histogram Based Graph Matching for Real-Time Multi-Robot Global Localization in Large Scale Environment作者:Xiyue Guo, Junjie Hu, Junfeng Chen, Fuqin Deng, T...

  • 点云PCL免费知识星球,点云论文速读。文章:Robust Place Recognition using an Imaging Lidar作者:Tixiao Shan, Brendan Englot, Fabio Duarte, Carlo Ratti, and Daniela Rus编译:点云PCL(ICRA 2021)开源代码:...

  • 文章:A Survey of Calibration Methods for Optical See-Through Head-Mounted Displays作者:Jens Grubert , Yuta Itoh, Kenneth Moser编译:点云PCL本文仅做学术分享,如有侵权,请联系删除。欢迎各位加入免费知识星球,获取PD...

  • 引言 在这个-SLAM建图和导航仿真实例-项目中,主要分为三个部分,分别是 (一)模型构建(二)根据已知地图进行定位和导航(三)使用RTAB-MAP进行建图和导航 该项目的slam_bot已经上传我的Github。 这是第三部分,完成效果如下 图1 建图和导航 三、使用RTAB-Map进行建图和导航 1. rtab...

  • 引言 在这个-SLAM建图和导航仿真实例-项目中,主要分为三个部分,分别是 (一)模型构建(二)根据已知地图进行定位和导航(三)使用RTAB-MAP进行建图和导航 该项目的slam_bot已经上传我的Github。 由于之前的虚拟机性能限制,我在这个项目中使用了新的ubantu 16.04环境,虚拟机配置 内存 8GCPU...

  • [{name:1},{name:2}].forEach((v,i,ar) => {console.log(v,i,ar)});//基础遍历[{name:1},{name:2}].map((v) => v.name);//[1,2]返回对象数组中指定字段值的一位数组(不改变原始数组)[{name:1},{name:2},{name:3}...

  • 体验内容 使用gmapping方法利用turtlebot底盘移动信息和激光雷达数据进行建图。 1. 安装一些依赖包 sudo apt-get install ros-melodic-move-base* sudo apt-get install ros-melodic-map-server* sudo apt-get insta...

  • 前言 我们知道Java/Python这种语言能够很好得 支持反射。反射机制 就是一种用户输入的字符串到对应实现方法的映射,比如http接口中 用户传入了url,我们需要调用该url对应的方法/函数对象 从而做出对应的操作。 而C++ 并没有友好得支持这样的操作,而最近工作中需要通过C++实现http接口,这个过程想要代码实现得优雅...