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

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

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

  1. class Behavior extends Object
  2. {
  3. /**
  4. * 当前行为的拥有对象,一般为Component对象
  5. * @var type
  6. */
  7. public $owner;
  8.  
  9. /**
  10. * 返回当前行为类拥有的所有事件,这些事件将被attach()方法绑定到行为对象的拥有者。
  11. * 配置格式:事件名称 => 事件处理器
  12. * 事件处理器的配置有4种格式:
  13. * 1.当前类成员方法名称,字符串形式,相当于 [$this, '成员方法名称']
  14. * 2.[类名, 方法名],数组形式
  15. * 3.[对象, 方法名],数组形式
  16. * 4.匿名函数,形式:function($event){ ... }
  17. * @return type
  18. */
  19. public function events()
  20. {
  21. return [];
  22. }
  23.  
  24. /**
  25. * 绑定行为到组件
  26. * @param type $owner
  27. */
  28. public function attach($owner)
  29. {
  30. $this->owner = $owner;
  31. foreach ($this->events() as $event => $handler) {//遍历行为的事件列表,调用组件对象的on()方法把所有事件都绑定到组件上
  32. $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
  33. }
  34. }
  35.  
  36. /**
  37. * 从组件上解绑行为
  38. */
  39. public function detach()
  40. {
  41. if ($this->owner) {
  42. foreach ($this->events() as $event => $handler) {//遍历行为的事件列表,调用组件对象的off()方法把所有事件从组件解绑
  43. $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
  44. }
  45. $this->owner = null;
  46. }
  47. }
  48. }

  可以看到yii\base\Behavior行为类其实很简单,只有一个成员变量和三个成员方法。成员变量$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中方式,这里直接上源码,主要看注释:

  1. /**
  2. * 用于静态绑定行为
  3. * 当访问当前组件类的属性、方法、事件的时候,都先会调用ensureBehaviors()确保这里配置的所有行为都已经绑定到组件中,
  4. * 若未绑定则一一进行绑定,这里返回的行为列表最后会保存在_behaviors成员变量中。
  5. * @return 静态绑定到当前组件的所有行为的列表,这里的行为可以有4种表示形式:
  6. * 1. '行为类名称':匿名行为,只有行为类的名称
  7. * 2. '行为名称' => '行为类名称':命名行为,只有行为类名称
  8. * 3. [//匿名行为,配置数组
  9. * 'class' => '行为类名称',
  10. * '属性名1' => '属性值1',
  11. * '属性名2' => '属性值2'
  12. * ],
  13. * 4. '行为名称' => [//命名行为,配置数组
  14. * 'class' => '行为类名称',
  15. * '属性名1' => '属性值1',
  16. * '属性名2' => '属性值2'
  17. * ]
  18. */
  19. public function behaviors()
  20. {
  21. return [];
  22. }
  23.  
  24. public function ensureBehaviors()
  25. {
  26. if ($this->_behaviors === null) {
  27. $this->_behaviors = [];
  28. foreach ($this->behaviors() as $name => $behavior) {
  29. $this->attachBehaviorInternal($name, $behavior);
  30. }
  31. }
  32. }

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

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

  1. /**
  2. * 为组件绑定一个行为
  3. * @param type $name:行为名称
  4. * @param type $behavior:行为配置,可以是一个对象,一个行为类名,或者一个行为类对象的配置数组
  5. * @return type
  6. */
  7. public function attachBehavior($name, $behavior)
  8. {
  9. $this->ensureBehaviors();
  10. return $this->attachBehaviorInternal($name, $behavior);
  11. }

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

  1. /**
  2. * 为组件绑定一个行为
  3. * @param type $name:行为名称,若是数字,表示匿名行为
  4. * @param type $behavior:行为配置,可以是一个对象,一个行为类名,或者一个行为类对象的配置数组
  5. * @return type
  6. */
  7. private function attachBehaviorInternal($name, $behavior)
  8. {
  9. if (!($behavior instanceof Behavior)) {//$behavior不是行为类对象,先根据配置创建一个对象
  10. $behavior = Yii::createObject($behavior);
  11. }
  12. if (is_int($name)) {//匿名行为:直接绑定
  13. $behavior->attach($this);
  14. $this->_behaviors[] = $behavior;
  15. } else {//命名行为
  16. if (isset($this->_behaviors[$name])) {//存在同名行为:先解绑原有同名行为
  17. $this->_behaviors[$name]->detach();
  18. }
  19. $behavior->attach($this);//调用行为类对象的attach()方法,此方法将会把行为类对象的所有事件绑定到当前组件对象
  20. $this->_behaviors[$name] = $behavior;
  21. }
  22.  
  23. return $behavior;
  24. }

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

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

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

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

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

  1. /**
  2. * 从组件解绑一个行为
  3. * @param type $name:行为名称
  4. * @return null或解绑的行为对象
  5. */
  6. public function detachBehavior($name)
  7. {
  8. $this->ensureBehaviors();
  9. if (isset($this->_behaviors[$name])) {
  10. $behavior = $this->_behaviors[$name];
  11. unset($this->_behaviors[$name]);//从组件的行为列表中删除
  12. $behavior->detach();//调用行为对象的detach()方法,此方法将解绑此行为绑定在组件上的所有事件
  13. return $behavior;
  14. }
  15.  
  16. return null;
  17. }

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

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

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

  1. /**
  2. * 重载PHP魔术方法__get()支持行为
  3. * @param type $name:属性名称
  4. * @return type
  5. * @throws InvalidCallException
  6. * @throws UnknownPropertyException
  7. */
  8. public function __get($name)
  9. {
  10. $getter = 'get' . $name;
  11. if (method_exists($this, $getter)) {//当前对象存在$name属性的getter方法:直接调用
  12. return $this->$getter();
  13. }
  14.  
  15. $this->ensureBehaviors();
  16. foreach ($this->_behaviors as $behavior) {//遍历当前对象已绑定的所有行为,若行为对象中存在$name属性且可读则返回
  17. if ($behavior->canGetProperty($name)) {
  18. return $behavior->$name;
  19. }
  20. }
  21.  
  22. if (method_exists($this, 'set' . $name)) {
  23. throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
  24. }
  25.  
  26. throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
  27. }
  28.  
  29. /**
  30. * 重载PHP魔术方法__set()支持行为
  31. * @param type $name:属性名称
  32. * @param \yii\base\Behavior $value
  33. * @return type
  34. * @throws InvalidCallException
  35. * @throws UnknownPropertyException
  36. */
  37. public function __set($name, $value)
  38. {
  39. $setter = 'set' . $name;
  40. if (method_exists($this, $setter)) {//当前对象存在$name属性的setter方法:直接调用
  41. $this->$setter($value);
  42. return;
  43. } elseif (strncmp($name, 'on ', 3) === 0) {//$name以'on '开头,表示给当前对象的某个事件绑定处理器
  44. $this->on(trim(substr($name, 3)), $value);
  45. return;
  46. } elseif (strncmp($name, 'as ', 3) === 0) {//$name以'as '开头,表示给当前对象绑定行为
  47. $name = trim(substr($name, 3));
  48. $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
  49. return;
  50. }
  51.  
  52. $this->ensureBehaviors();
  53. foreach ($this->_behaviors as $behavior) {//遍历当前对象已绑定的所有行为,若行为对象中存在$name属性且可写则设置值
  54. if ($behavior->canSetProperty($name)) {
  55. $behavior->$name = $value;
  56. return;
  57. }
  58. }
  59.  
  60. if (method_exists($this, 'get' . $name)) {
  61. throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
  62. }
  63.  
  64. throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
  65. }
  66.  
  67. /**
  68. * 重写PHP魔术方法__call()支持行为,
  69. * @param type $name:方法名称
  70. * @param type $params:传递给方法的参数
  71. * @return type
  72. * @throws UnknownMethodException
  73. */
  74. public function __call($name, $params)
  75. {
  76. $this->ensureBehaviors();
  77. foreach ($this->_behaviors as $object) {//遍历组件绑定的所有行为,若某个行为对象中存在$name成员方法则调用之
  78. if ($object->hasMethod($name)) {
  79. return call_user_func_array([$object, $name], $params);
  80. }
  81. }
  82. throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
  83. }

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

Yii2之行为的更多相关文章

  1. Yii2的深入学习--行为Behavior

    我们先来看下行为在 Yii2 中的使用,如下内容摘自 Yii2中文文档 行为是 [[yii\base\Behavior]] 或其子类的实例.行为,也称为 mixins,可以无须改变类继承关系即可增强一 ...

  2. 网站实现微信登录之回调函数中登录逻辑的处理--基于yii2开发的描述

    上一篇文章网站实现微信登录之嵌入二维码中描述了如何在自己的登录页面内嵌入登录二维码,今天的这篇文章主要是描述下在扫码成功之后微信重定向回网站后登录逻辑的处理,其实也就是验证身份信息,授权用户登录的逻辑 ...

  3. 网站实现微信登录之嵌入二维码——基于yii2开发的描述

    之前写了一篇yii2获取登录前的页面url地址的文章,然后发现自己对于网站实现微信扫码登录功能的实现不是很熟悉,所以,我会写2-3篇的文章来描述下一个站点如何实现微信扫码登录的功能,来复习下微信扫码登 ...

  4. yii2获取登录前的页面url地址--电脑和微信浏览器上的实现以及yii2相关源码的学习

    对于一个有登录限制(权限限制)的网站,用户输入身份验证信息以后,验证成功后跳转到登录前的页面是一项很人性化的功能.那么获取登录前的页面地址就很关键,今天在做一个yii2项目的登录调试时发现了一些很有意 ...

  5. 记一次nginx部署yii2项目时502 bad gateway错误的排查

    周六闲来无事,就试着安装和部署下yii2,安装过程没什么问题,但部署到nginx上时遇到了502 bad gatewary问题,折腾了半天才搞定.这个问题是我以前在部署yii2时没有遇到过的,因此记在 ...

  6. yii2的权限管理系统RBAC简单介绍

    这里有几个概念 权限: 指用户是否可以执行哪些操作,如:编辑.发布.查看回帖 角色 比如:VIP用户组, 高级会员组,中级会员组,初级会员组 VIP用户组:发帖.回帖.删帖.浏览权限 高级会员组:发帖 ...

  7. yii2 RESTful api的详细使用

    作者:白狼 出处:http://www.manks.top/yii2-restful-api.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...

  8. yii2 ActiveRecord多表关联以及多表关联搜索的实现

    作者:白狼 出处:http://www.manks.top/yii2_many_ar_relation_search.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明 ...

  9. yii2权限控制rbac之rule详细讲解

    作者:白狼 出处:http://www.manks.top/yii2_rbac_rule.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留 ...

  10. yii2组件之多图上传插件FileInput的详细使用

    作者:白狼 出处:http://www.manks.top/yii2_multiply_images.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连 ...

随机推荐

  1. JS中的DOM对象及JS对document对像的操作

    DOM对象 windows:属性:opener(打开者) 方法:open().close(),setTimeout().setInterval()... location:属性:href 方法:rel ...

  2. 创建maven项目pom.xml第一行报错

    之前也创建过几次maven项目,也是第一行报错,之前直接是右键项目强制更新maven好像就解决了,这次遇见这个问题使用这个方法好像不起作用了,给的一堆英文报错又看不懂,幸好在网上看见路人甲大神提示,根 ...

  3. Python生成器主要用法

    代码如下: #!/usr/bin/env python3 # -*- coding: utf-8 -*- __author__ = '人生入戏' def use(name): print(" ...

  4. 警惕Java编译器中那些“蜜糖”陷阱

    一.前言 随着Java编译器不断地向前发展,它为程序员们提供了越来越多的“蜜糖”(compiler suger),极大地方便了程序的开发,例如,foreach的增强模式,自动拆箱与装箱以及字符串的连接 ...

  5. 虚拟WEB目录的映射原理

    一个文件系统目录可以被映射成为多个虚拟WEB目录,虚拟WEB目录名称可以是多级目录结构的形式,tomca按照最长路径匹配原则处理请求的URL 设置WEB站点的根目录: <Host>元素的a ...

  6. Maven简介(一)

    在现实的企业中,以低成本.高效率.高质量的完成项目,不仅仅需要技术大牛,企业更加需要管理大牛,管理者只懂技术是远远不够的.当然,管理可以说有很多的方面,例如:对人员的管理,也有对项目的管理等等.如果你 ...

  7. 为什么要用深度学习来做个性化推荐 CTR 预估

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:苏博览 深度学习应该这一两年计算机圈子里最热的一个词了.基于深度学习,工程师们在图像,语音,NLP等领域都取得了令人振奋的进展.而深 ...

  8. 写了一个迷你confirm弹窗插件,有取消和确认操作处理并支持单个确认使用弹窗和锁屏禁止滚动

    由于项目想精简不想用其他第三方的ui插件,又很需要像confirm等小效果来完善交互,且使用的频率也是相当的高,于是自己造了一个,省时也省力 代码已经粘贴出来,直接复制即可看到效果,高手勿喷,可以相互 ...

  9. 接口测试——httpclient介绍与请求方式详解

    httpClient工具介绍 HTTP协议可能是现在lntemet上使用得最多.最重要的协议了,越来越多的Java应用程序需要直接通过HTTP协议来访问网络资源.虽然在JDK的java.net包中已经 ...

  10. hdu2157矩阵快速幂

    How many ways?? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...