当前位置: 首页 > 编程日记 > 正文

Yii2之行为

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

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

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

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;}}
}

  可以看到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中方式,这里直接上源码,主要看注释:

/*** 用于静态绑定行为* 当访问当前组件类的属性、方法、事件的时候,都先会调用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 \yii\base\Behavior $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

相关文章:

ACM学习历程—HDU5586 Sum(动态规划)(BestCoder Round #64 (div.2) 1002)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid5586 题目大意就是把一段序列里面的数替换成f(x),然后让总和最大。 首先可以计算出初始的总和,以及每一个值换成f(x)的增益a[x]。 那么就是求一段子序列a[x]的最值了,经典的D…

ant models 内获取 url 的参数传递到组件

models代码: import { getCList} from "@/services/api"; import { MessageTip } from @/utils/tools.js import { router } from umi;const customerModel = {namespace: customerModel,state: {channelList: [], // 渠道列表},reducers: {getUrlQuery(state, { …

软件开发向大数据开发过渡_如果您是过渡到数据科学的开发人员,那么这里是您的最佳资源...

软件开发向大数据开发过渡by Cecelia Shao邵Ce It seems like everyone wants to be a data scientist these days — from PhD students to data analysts to your old college roommate who keeps Linkedin messaging you to ‘grab coffee’.如今,似乎每个人都想…

php随笔(1)

PHP标记的四种风格 1、XML风格 <?php echo <p>Hello world</p> ; ?> 2、简短风格 <? echo <p>Hello world</p> ; ?> 3、SCRIPT <script language php>echo <p>Hello wordl.</p>;</script> 4、ASP风格 <% …

微信小程序云开发图片上传完整代码附效果图

在app.json里面加如下代码, 使用 WeUI组件库。点击跳转 "useExtendedLib": {"weui": true}, 先看效果图 wxml <!--pages/publish/publish.wxml--> <view class"page" data-weui-theme"{{theme}}"><view class"w…

图片lightbox2

1. 官网下载 http://lokeshdhakar.com/projects/lightbox2/ 2.引入 css jquery js 3. HTML格式 <a href"images/image-2.jpg" data-lightbox"roadtrip"> <img srcImage #1> </a> <a href"images/image-3.jpg" data-lig…

夏天和空调_您可以在今年夏天开始学习650项免费的在线编程和计算机科学课程...

夏天和空调Seven years ago, universities like MIT and Stanford first opened up free online courses to the public. Today, more than 900 schools around the world have created thousands of free online courses, popularly known as Massive Open Online Courses or …

bzoj1854: [Scoi2010]游戏

可以跑二分图 到第一个不能匹配的点就退出 嗯 还有并查集判环的做法&#xff1f; 1 #include<iostream>2 #include<algorithm>3 #include<cstdio>4 #include<cstdlib>5 #include<cstring>6 #include<string>7 8 using namespace std;9 10…

ant 获取当前url的参数

在util.js 中封装一个函数 公共函数&#xff1a; import { parse } from querystring; export const getPageQuery () > parse(window.location.href.split(?)[1]); 例如当前url 为&#xff1a;http://localhost:8000/manage/member_s_custome?corpIdww02c137b227b01c…

微软todo使用教程_Todo教程可能很有趣-但是,这是从头开始构建自己的项目的方法...

微软todo使用教程There are many great tutorials that walk you through creating apps, from simple todo lists to fully working web apps. But how do you start your own projects from scratch? Without the safety net of a tutorial, you might feel a bit lost on w…

python的with语句

from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from setting import EREBUS_DB_CONNECT_STRING from contextlib import contextmanager# 创建数据库引擎&#xff0c;echo为True&#xff0c;会打印所有的sql语句 engine cre…

MSSQL 2012 拒绝了对对象 'extended_properties' (数据库 'mssqlsystemresource',架构 'sys')的 SELECT 权限...

查看数据库的表的时候报如下错误&#xff1a; MSSQL 2012 拒绝了对对象 extended_properties (数据库 mssqlsystemresource&#xff0c;架构 sys)的 SELECT 权限。 (Microsoft SQL Server&#xff0c;错误: 229) 解决方法&#xff1a; 在数据库里相应的用户权限中&#xff0c;把…

ant 接口返回文件流,前端自动下载实现

封装网络请求 : reqAxios.js import Axios from axios; import qs from qs; import { router } from umi; import { message } from antd;Axios.defaults.withCredentials = true// const httpUrl = https://xxx.cn/work_telecom_manage const httpUrl = window.location.o…

矩阵奇异值分解特征值分解_推荐系统中的奇异值分解与矩阵分解

矩阵奇异值分解特征值分解Recently, after watching the Recommender Systems class of Prof. Andrew Ng’s Machine Learning course, I found myself very discomforted not understanding how Matrix Factorization works.最近&#xff0c;在观看了Andrew Ng教授的机器学习课…

小程序云开发获取手机号完整代码 云函数中网络请求第三方接口

小程序云开发获取手机号完整代码 效果图&#xff1a; 小程序代码 <button open-type"getPhoneNumber" bindgetphonenumber"getPhoneNumber">登录</button> getPhoneNumber: function (e) {var that this;if (!e.detail.errMsg || e.detail.…

集合恒等式定律及文氏图

1、分配律 1.1 A∩(B∪C) (A∩B)∪(A∩C) 说明&#xff1a;从左至右&#xff0c;图1中三角形区域为 A&#xff0c;草绿色区域为 (B∪C)&#xff0c;即有三角形又有草绿色底色的区域即为 A∩(B∪C) 图2中三角形区域为 (A∩B)&#xff0c;草绿色区域为 (A∩C)&#xff0c;三角形…

微信 小程序布局 水平菜单

<!-- 菜单列表部分 --><view class"wear-menu"><view classmenu-box wx:key"menu" wx:for"{{menuList}}" wx:for-index"index"><view class"menu-img" bindtap"selectMenu" data-index"…

keras神经网络回归预测_如何使用Keras建立您的第一个神经网络来预测房价

keras神经网络回归预测by Joseph Lee Wei En通过李维恩 一步一步的完整的初学者指南&#xff0c;可使用像Deep Learning专业版这样的几行代码来构建您的第一个神经网络&#xff01; (A step-by-step complete beginner’s guide to building your first Neural Network in a c…

transform总结

1. 用jquery的css方法获取transform得到的是矩阵matrix,不利于获取translate的值&#xff0c; 优先使用dom.style.webKitTransform进行transform的读写 2. 从transform中读取translate的值方法 //jquery版本 function fGetTranslate($obj,type){var transformMatrix obj.css(&…

uni-app 音频控制

选择不同的音频&#xff0c;销毁上一个音频&#xff0c;播放最新的音频文件。 效果图&#xff1a; 音频组件代码: <template><view id"share_card" class"share_card"><view class"top"><img class"ms" :class…

推荐5款实用的jQuery时间轴插件

1、使用CSS3和jQuery制作的水平时间轴 这是一个可以在PC和移动端表现非常棒的水平时间轴&#xff0c;它由上部水平滑块和下部时间点对应的内容区块&#xff0c;点击时间轴上的时间点&#xff0c;下部内容会滑动到对应的内容区块。使用CSS3和jQuery技术使得时间轴滑块可以左右滑…

devkit_如何使用NodeMCU Devkit和Firebase数据库开始物联网

devkitby Jibin Thomas吉宾托马斯(Jibin Thomas) 如何使用NodeMCU Devkit和Firebase数据库开始物联网 (How to get started with IoT using NodeMCU Devkit and Firebase database) “The Internet will disappear. There will be so many IP addresses, so many devices, sen…

洛谷p1162填涂颜色(dfs写法)

这道题本是放在试炼场bfs里的&#xff0c;但是我觉得dfs好写些 所以就用dfs过了 题目如下 题目描述 由数字0 组成的方阵中&#xff0c;有一任意形状闭合圈&#xff0c;闭合圈由数字1构成&#xff0c;围圈时只走上下左右4个方向。现要求把闭合圈内的所有空间都填写成2.例如&…

Microsoft .NET Framework 4.6.1

适用于操作系统平台&#xff1a;Windows 7 SP1、Windows 8、Windows 8.1、Windows 10、Windows Server 2008 R2 SP1、Windows Server 2012 和 Windows Server 2012 R2 .NET Framework 4.6.1 官方更新介绍页面 http://msdn.microsoft.com/en-us/library/ms171868%28vvs.110%29.a…

VUE还没生效,页面闪屏的问题解决办法 v-cloak

当网络较慢&#xff0c;网页还在加载 Vue.js &#xff0c;而导致 Vue 来不及渲染&#xff0c;这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题。 html&#xff1a; <div id"app">{{context}} </div>js&#xff1a; <script…

如何使用Python创建,读取,更新和搜索Excel文件

This article will show in detail how to work with Excel files and how to modify specific data with Python.本文将详细显示如何使用Excel文件以及如何使用Python修改特定数据。 First we will learn how to work with CSV files by reading, writing and updating them.…

字符串基本操作

1.已知‘星期一星期二星期三星期四星期五星期六星期日 ’&#xff0c;输入数字&#xff08;1-7&#xff09;&#xff0c;输出相应的‘星期几’ s"星期一星期二星期三星期四星期五星期六星期日" iint(eval(input("请输入1-7的数字:"))) print(s[3*(i-1):3*i…

IOS长按识别二维码失败

IOS长按不识别二维码&#xff0c;出现放大图片的问题解决。 CSS加入样式&#xff1a; touch-callout: none; -webkit-touch-callout: none; -ms-touch-callout: none; -moz-touch-callout: none; 代码&#xff1a; <!DOCTYPE html> <html><head><s…

从svn下载的Mavn项目,到本地后不识别(MyEcplise)

从svn上面现在的mavn的项目到本地不识别的原因 1.首先要确认本机的mavn的环境是否正确。 2.查看本机的Myecplise的mavn的环境配置是否正确 3.在cmd当中执行命令 mvn -Dwtpversion1.0 eclipse:myeclipse &#xff0c;可能svn上面的文件是eclipse建立的&#xff0c;需要进行转化。…

python导入外部包_您会喜欢的10个外部Python软件包

python导入外部包by Adam Goldschmidt亚当戈德施密特(Adam Goldschmidt) 您会喜欢的10个外部Python软件包 (10 External Python packages you are going to love) Python is an experiment in how much freedom programmers need. Too much freedom and nobody can read anoth…