在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
YII 框架源码分析
百度联盟事业部——黄银锋 目 录 2.5 、App 应用 10 2.6 、WebApp 应用 11 4.1 、Action 23 4.2 、Filter 24 5.1 、DAO 层 30 5.1.3 、Command 对象 31 5.2.1 、Command 构造器 33 5.3.2 、单表 ORM 34 5.3.3 、多表 ORM 36 5.3.4 、CModel 与 CValidator 37
1、引言 1.1、Yii 简介 Yii 的作者是美籍华人“薛强”,他原是 Prado 核心开发成员之一。2008 年薛强另起炉灶, 开发了 Yii 框架,于 2008 年 12 月 3 日发布了 Yii1.0 版本。 Yii 是目前比较优秀的 PHP 框架之一,它的支持的特性包括:MVC、DAO/ActiveRecord、 I18N/L10N、caching、AJAX 支持、用户认证和基于角色的访问控制、脚手架、输入验证、部 件、事件、主题化以及 Web 服务等。 Yii 的很多思想参考了其它一些比较优秀的 Web 框架(我们写东西时是不是也喜欢参考别人的? 有木有?嘿嘿,都喜欢站在别人的肩膀上干活!),下面是一个简短的列表:
1.2、本文内容与结构 本文对 Yii1.1.8 版本的源代码进行了深入的分析,本文的内容与结构为: 组件化与模块化:对 Yii 的基于组件和事件驱动编程模式的基础类(CComponent)进行分 析;对组件化和模块化的工作原理进行分析;对 WebApp 应用创建 Controller 流程等进行分 析。 系统组件:对 Yii 框架自带的重要组件进行分析,主要包括:日志路由组件、Url 管理组 件、异常处理组件、Cache 组件、基于角色的访问控制组件等。 控制器层:控制器主要包含 Action 和 Filter,对 Action 与 Filter 的工作原理进行分析。 模型层:对 DAO 层、元数据和 Command 构造器、ORM 的原理进行分析 视图层:对视图层的渲染过程、Widget 和客户端脚本组件等进行分析 本文档中的错误或不妥之处在所难免,殷切希望本文档的读者给予批评指正!
2、组件化与模块化 2.1、框架加载和运行流程 start 加载YiiBase.php 1、安装autoload方法, 为类的实例化做准备 2、获得框架所有类的路 径(Yii1.1.8共208类) 根据ActionId创建Action对象 1、从成员函数中寻找Action 2、从ActionMap中寻找Action抛加载request组件(Get,Post,Cookie)创建Action 否 异 是否成功? 常 创建WebApp实例 1、初始化别名:application、 webroot、ext 2、安装异常处理句柄,抛异常时 交给异常处理组件来处理 3、配置核心组件:urlManager、 errorHandler、session、db等 4、根据配置信息来配置组件、子 模块、WebApp成员属性等 5、加载preload组件:log组件、 request组件等
运行WebApp 1、触发onBeginRequest事件 加载Url管理组件 根据配置信息分析url,解析出路 由:route=ControllerId/ActionId 根据ControllerId创建控制器 1、从ControllerMap中寻找 2、从子模块中寻找 3、从ControllerPath中寻找 创建控制器 抛 是否成功? 否 异常根据filters()配置,创建出当 前Action的所有Filter对象运行Filter1的preFilter方法 运行Filter2的preFilter方法检查Get参 抛 数与Action 否 异 的参数否常一致运行ActionPartial render渲染出核心部分的html
Layout render 渲染出整体的html
echo html
2、处理请求 运行控制器 运行Filter2的postFilter方法 运行Filter1的postFilter方法 3、触发onEndRequest事件 注册的句柄: 异常处理 组件
XX处抛异常 throw Exception。。。 end 比如记录日志 Yii 框架加载和运行流程共分 4 个阶段(也许看着有点吓人,木有关系,我们先知道一个大概): Step1:WebApp 初始化与运行 1.1、 加载 YiiBase.php,安装 autoload 方法;加载用户的配置文件; 1.2、 创建 WebApp 应用,并对 App 进行初始化,加载部分组件,最后执行 WebApp Step2:控制器初始化与运行 2.1、 加载 request 组件,加载 Url 管理组件,获得路由信息 route=ControllerId/ActionId 2.2、 创建出控制器实例,并运行控制器 Step3:控制器初始化与运行 3.1、 根据路由创建出 Action 3.2、 根据配置,创建出该 Action 的 Filter; 3.3、 执行 Filter 和 Action Step4:渲染阶段 4.1、 渲染部分视图和渲染布局视图 4.2、 渲染注册的 javascript 和 css
2.2、YiiBase 静态类 YiiBase 为 YII 框架的运行提供了公共的基础功能:别名管理与对象创建管理。 在创建一个 php 的对象时,需要先 include 这个类的定义文件,然后再 new 这个对象。 在不同环境下(开发环境/测试环境/线上环境),apache 的 webroot 路径的配置可能不一样, 所以这个类的定义文件的全路径就会不同,Yii 框架通过 YiiBase 的别名管理来解决了这个问 题。 在 创 建 对象时 , 需 要导入 对应 类的定义 , 经常需 要 使 用这 5 个 函数 : include()、 include_once()、require()、require_once()、set_include_path()。Yii 通过使用 YiiBase::import() 来统一解决这个问题。下图描述了 YiiBase 提供“别名管理与对象创建管理”的工作原理: 通过createComponent创建对象
1、如果类不存在,则通过import导入 通过new创建对象 2、new这个对象 3、根据输入对这个对象的属性初始化 import导入一个类的定义导入一个路径到include_path autoload如果类是别名打头的,通过别管管理接口获得全路径别名管理getPathOfAlias setPathOfAlias添加一个别名的全路径 首先看别名管理,它是通过为某个文件夹(一个文件夹往往对应一个模块)起一个别名, 在 YII 框架中可以使用这个别名来替代这个文件夹的全路径,比如:system 别名代表的是框 架 /home/work/yii/framework 的 路 径 , 所 以 可 以 使 用 system.base.CApplication 代表 /home/work/yii/framework/base/CApplication.php 文件的路径。当然在应用层(我们)的代码中 也可以通过 Yii::setPathOfAlias 来注册别名。 一般情况下我们使用绝对路径或者相对路径来进行文件引用,当然这 2 种情况都有弊端。 绝对路径:当我们的代码部署到测试环境或者线上环境的时候需要大量修改被 include 文件 的路径;相对路径:当某些模块的文件夹的位置发生调整(改名)的时候,所有的相对路径都 需要修改。而使用别名的方式只需要改一处:注册别名的时候,即 Yii::setPathOfAlias()。从 而将文件夹的变动而导致的代码改动集中到一处完成。 再看 import 功能:a、导入一个类的定义,从而可以创建该类的对象;b、将某个文件夹 加入到 include_path,从而可以直接 include 这个文件下的所有文件。Yii::import 相当于如下 5 个函数的统一:include()、include_once()、require()、require_once()、set_include_path()。 而且一般情况下速度会比这些函数更快。当然 Yii::import 支持别名的功能,从而可以解决路 径变动带来的麻烦。 最后看一下对象的创建,在 YII 框架中有 2 中方法创建对象:1、使用 new 关键字;2、
使用 Yii::createComponent 方法。 当使用 new 关键字创建对象时,autoload 会分 3 步来寻找对应类的定义:a、判断是否 为 framework 中的类(framework 的所有类和这个类的全路径都保存在 YiiBase 的一个成员变 量中,就相当于整个框架都 import 了);2、判断是否使用 Yii::import 导入了这个类,对于非 框架的类,我们在创建这个类的对象时需要先 import 这个类的定义;3、从 include_path 目 录下查找以这个类名字命名的 php 脚本,所以在开发的时候类名尽量与文件名保存一致, 这样我们导入(import)包含这个文件的文件夹就行了,从而无需把这个文件夹中的每个文件 都导入一遍。 当使用 Yii::createComponent 方法创建对象时,它提供了比 new 关键字更多的功能:a、 通过这个类的全路径别名来指定类的位置和类名(类名必须与文件名一致),当这个类还没有 导入的时候,会根据全路径来自动导入这个类的定义;2、对创建出来的对象的成员变量进 行赋值。即如下图描述,原来要写 3 行以上的代码,现在一行代码就可以搞定(write less, do more)。
Yii::import('application.models.Student'); $obj = new Student(); $obj->age = 16; $obj->name = 'jerry';
$obj = Yii::createComponent(array( 'class'=>'application.models.Student', 'age'=>16, 'name'=>'jerry' ));
2.3、组件 CComponent 类就是组件,它为整个框架的组件编程和事件驱动编程提供了基础,YII 框架中的大部分类都将 CComponent 类作为基类。CComponent 类为它的子类提供 3 个特性: 1、成员变量扩展 通过定义两个成员函数(getXXX/setXXX)来定义一个成员变量,比如: public function getText() {…} public function setText {…} 这样就相当于定义了一个 text 成员变量,可以这样调用 $a=new CComponent; $a=$component->text; // 等价于$a=$component->getText(); $component->text='abc'; // 等价于$component->setText('abc'); CComponent 是通过魔术方法 get 和 set 来实现“成员变量扩展”特性的,如果对类 本身不存在的成员变量进行操作时,php 会调用这个类的 get 和 set 方法来进行处理。 CComponent 利用这两个魔术方法实现了“成员变量扩展”特性。下图描述了一个 CComponent 的子类,它增加了 active 和 sessionName 两个成员变量,该图描述了对于这两个成员变量的 调用流程。 getActive setActive
是否存在这个 成员变量 否 set() get() getSessionName setSessionName 是 使用本对象的成员变量 getXXX setXXX 面向对象编程中直接定义一个成员变量就可以了,为什么 CComponent 要通过定义 2 个 函数来实现一个成员变量呢?一个主要得原因是需要对成员变量进行“延时加载”,一般情 况下类的成员变量是在构造函数或者初始化函数进行统一赋值,但是在一次 web 请求的处 理过程中不是每个成员变量都会被使用,比如 App 类中定义了两个成员变量:$cache 和$db ($cache 是一个缓存对象,$db 是一个数据库链接对象),这两个对象在 App 类初始化的时 候创建,但是一个 web 网站的有些页面,它内容可以通过缓存获取,那么数据库链接对象 其实就不需要创建。如果将 App 定义为 CComponent 的子类,在 App 类中定义两个方法: getCache/getDb,这样就可以做到第一次使用 db 成员变量的时候,才调用 getDb 函数来进行 数据库链接的初始化,从而实现延时加载——即在第一次使用时进行初始化。虽然延时加载 会增加一次函数调用,但是可以减少不必要的成员变量的初始化(总体上其实是提升了网站 的访问速度),而且可以使得我们的代码更加易维护、易扩展。 延时加载应该是“成员变量扩展”特性的最重要的用途,当然这个特性还会有其它用途, 想一想,当你操作一个成员变量的时候,你其实是在调用 getXXX 和 setXXX 成员函数,你是 在调用一段代码! 2、事件模型 事件模型就是设计模式中的“观察者模式”:当对象的状态发生了变化,那么这个对象 可以将该事件通知其它对象。 为了使用事件模型,需要实现这三个步骤:1、定义事件;2、注册事件句柄;3、触发 事件。 CComponent 的子类通过定义一个以 on 打头的成员函数来定义一个事件,比如:public function onClick(){…},接着通过调用 attachEventHandler 成员函数来注册事件句柄(可以注册 多个事件句柄),最后通过调用 raiseEvent 来触发事件。
attachEventHandler detachEventHandler raiseEvent
事件句柄容器
onclick fun_11 fun_12 „ fun_1n beforeinsert fun_2n afterinsert fun_3n „„
Key
Value fun_mn
CComponent 类使用一个私有的成员变量来保存事件以及处理该事件的所有句柄,该成 员变量可以看作一个 hash 表,hash 表的 key 是事件的名称,hash 表的 value 是事件处理函 数链表。 3、行为类绑定 有两种办法可以对类添加特性:1、直接修改这个类的代码:添加一些成员函数和成员 变量;2、派生:通过子类来扩展。很明显第二种方法更加易维护、易扩展。如果需要对一 个类添加多个特性(多人在不同时期),那么需要进行多级派生,这显然加大了维护成本。 CComponent 使用一种特殊的方式对类信息扩展——行为类绑定。行为类是 CBehavior 类的一个子类,CComponent 可以将一个或者多个 CBehavior 类的成员函数和成员变量添加 到自己身上,并且在不需要的时候卸载掉某些 CBehavior 类。下面是一个简单的例子: //计算器类 class Calculator extends CBehavior { public function add($x, $y) { return $x + $y; } public function sub($x, $y) { return $x - $y; } ... } $comp = new CComponent(); //为我的类添加计算器功能 $comp->attachbehavior('calculator', new Calculator()); $comp->add(2, 5); $comp->sub(2, 5); CComponent 通过 get、 set 和 call 这 3 个魔术方法来实现“行为类绑定”这个特性, 当调用 CComponent 类不存在的成员变量和成员方法的时候,CComponent 类会通过这三个 魔法方法在“动态绑定的行为对象”上进行查找。即将不存在的成员变量和成员方法路由到 “动态绑定对象”上。
attachBehavior detachBehavior
绑定一个对象 解除绑定
obj1
set() obj2
是否不存在 否 get() 查询各个对象
call() obj3
是
使用本对象的成员 变量和成员函数
„
绑定的对象
使用绑定对象流程 绑定的维护流程
可以用 3 句话来总结 CComponent 类的特性: 1、 更好的配置一个对象,当设置对象的成员变量的时候,其实是运行一段代码; 2、 更好的监听一个对象,当对象的内部状态发生变化的时候,其它对象可以得到通知; 3、 更好的扩展一个对象,可以给一个对象增加成员变量和成员函数,还能监听这个对 象的状态。
2.4、模块
模块是整个系统中一些相对独立的程序单元,完成一个相对独立的软件功能。比如 Yii 自带的 gii 模块,它实现了在线代码生成的功能。CModule 是所有模块类的基类,它有 3 部 分组成: a、基本属性(模块 id,模块路径等); b、组件,这是模块的核心组成部分,模块可以看成这些组件的容器; c、子模块,这为模块提供了扩展性,比如一个模块做大了,可以拆成多个子模块(每个 子模块也是有这 3 部分组成,是一个递归结构)。 下图是模块与它的成员之间的包含关系图:
模板基 本属性 组件 子模块
下表列出了 CModule 各个组成部分:
可以非常方便的对模块的这 3 个组成部分进行初始化:使用一个数组进行配置,数组的 key 是需要配置的属性,value 就是需要配置的值,下图是一个例子,为什么会如此方面的进 行配置呢?因为 CModule 继承自 CComponent 类,所以在对成员属性进行配置的时候,其实 是在运行一段代码,即一个成员函数。 array( 'basePath'=>dirname( FILE ).DIRECTORY_SEPARATOR.'..',//模块的路径 'preload'=>array('log'),//需要预先加载日志组件 'import'=>array('application.models.*', 'application.components.*',),//需要include的路径 //组件的配置 'components'=>array( 'user'=>array(//用户组件的配置 'allowAutoLogin'=>true ),
'log'=>array(//日志组件的配置 'class'=>'CLogRouter', 'routes'=>array(array('class'=>'CWebLogRoute','levels'=>'trace, profile')) ) ), //模块的配置 'modules'=>array( 'gii'=>array(//自动生成代码模块的配置 'class'=>'system.gii.GiiModule', 'password'=>'123456' ), ), );
2.5 、App 应用
应用是指请求处理中的执行上下文。它的主要任务是分析用户请求并将其分派到合适的 控制器中以作进一步处理。它同时作为服务中心,维护应用级别的配置。鉴于此,应用也叫 做“前端控制器”。 Yii 使用 CApplication 类用来表示 App 应用,CApplication 继承自 CModule,它在父类基 础上做了 3 方面的扩展:1、增加一个 run 方法;2、添加了若干成员属性;3、添加了若干 组件。 run 方法的作用相当于 C 语言的 main 函数,是整个程序开始运行的入口,内部调用虚 函数 processRequest 来处理每个请求,CApplication 有 2 个子类:CWebApplication 和 CConsoleApplication,它们都实现了该方法。在处理每个请求的开始和结束分别发起了 onBeginRequest 和 onEndRequest 事件,用于通知监听的观察者。复习一下“Yii 框架加载和 运行流程”图,从中可以找到该方法在整个流程中所起的作用。 添加的成员变量、成员函数和组件见下表:
2.6 、WebApp 应用
每个 web 请求都由 WebApp 应用来处理,即 WebApp 应用为 http 请求的处理提供了运 行的环境。WebApp 应用就是 CWebApplication 类,它的最主要工作是根据 url 中的路由来创 建对于的控制类,下图描述了控制器创建的过程,主要由 3 步组成: 1、在成员变量 controllerMap 中查找,判断是否有对应的 Controller,controllerMap 的 优先级最高 2、在子模块中中查找,判断是否有对应的 Controller 3、在 ControllerPath 及其子文件夹中查找 搜索控制器的过程 输入的路由为:seg1/seg2/seg3
调用createController(‘seg1/seg2/seg3’,$app)
1 在controllerMap中寻 找id为seg1的控制类
调用createController(‘seg2/seg3’, $subModule)
2 不存在 递归调用
不存在 3 在ControllerPath路 径下逐层寻找
是否存在ControllerPath/seg1/Seg2Controller.php ControlId为/seg1/seg2 不存在 是否存在ControllerPath/seg1/seg2/Seg3Controller.php ControlId为/seg1/seg2/seg3
添加的重要的成员变量、成员函数和组件见下表:
全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论