YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)
YII 框架源码分析
百度联盟事业部——黄银锋
目 录
1、 引言 3
1.1、Yii 简介 3
1.2、本文内容与结构 3
2、组件化与模块化 4
2.1、框架加载和运行流程 4
2.2、YiiBase 静态类 5
2.3、组件 6
2.4、模块 9
2.5 、App 应用 10
2.6 、WebApp 应用 11
3、系统组件 13
3.1、日志路由组件 13
3.2、Url 管理组件 15
3.3、异常处理组件 17
3.4、Cache 组件 17
3.5、角访问控制组件 19
3.6、全局状态组件 21
4、控制器层 23
4.1 、Action 23
4.2 、Filter 24
4.3 、Action 与 Filter 的执行流程 26
4.4、访问控制过滤器 27
5、模型层 30
5.1 、DAO 层 30
5.1.1、数据库连接组件 30
5.1.2、事务对象 31
5.1.3 、Command 对象 31
5.2 、元数据与 Command 构造器 32
5.2.1、表结构查询 32
5.2.2、查询条件对象 33
5.2.1 、Command 构造器 33
5.3 、ORM(ActiveRecord) 34
5.3.1、表的元数据信息 34
5.3.2 、单表 ORM 34
5.3.3 、多表 ORM 36
5.3.4 、CModel 与 CValidator 37
6、视图层 38
6.1、视图渲染流程 38
6.2、Widget 39
6.3、客户端脚本组件 40
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 框架(我们写东西时是不是也喜欢参考别人的? 有木有?嘿嘿,都喜欢站在别人的肩膀上干活!),下面是一个简短的列表:
框架名称 |
参考思想 |
Prado |
基于组件和事件驱动编程模式、数据库抽象 层、模块化的应用架构、国际化和本地化等 |
Ruby on Rails |
配置思想、基于 Active Record 的 ORM |
jQuery |
集成了 jQuery |
Symfony |
过滤设计和插件架构 |
Joomla |
模块化设计和信息翻译方案 |
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 否 异 的参数是否 常
一致
运行Action
Partial render
渲染出核心部分的html
Layout render
渲染出整体的html
Client Script render 嵌入javascript、css生 成最终的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
fun_m1 |
|
|
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 部分 |
详细成员 |
说明 |
基本属性 (用户对整个模块的全局性 的东西进行配置) |
id |
模块的 id |
parentModule |
父模块 |
|
basePath |
当前模块的路径 |
|
modulePath |
子模块的路径 |
|
params |
模块的参数 |
|
preload |
需要预先加载的组件 id |
|
behaviors |
绑定的行为类 |
|
aliases |
新增加的别名,添加到 YiiBase 的别名管理中 |
|
import |
需要包含的文件或者路径 |
|
组件 (这是模块的核心组成部分) |
components |
数组类型,数组的每个成员描述了一个组件 |
子模块 (这为模块提供了扩展性) |
modules |
数组类型,数组的每个成员描述了一个模块, 每个模块也是有这 3 部分组成,是递归结构 |
可以非常方便的对模块的这 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 框架加载和 运行流程”图,从中可以找到该方法在整个流程中所起的作用。
添加的成员变量、成员函数和组件见下表:
类别 |
名称 |
说明 |
成员变量 |
name |
应用的名称 |
charset |
应用的编码集,默认为 UTF-8 |
|
sourceLanguage |
编码所使用的语言和区域 id 号,这在开发多语言时需要, 默认为 UTF-8 |
|
language |
app 要求的语言和区域 id 号,默认为 sourceLanguage |
|
runtimePath |
运行时的路径,比如全局的状态会保存到这个路径下,默 认为 application.runtime |
|
extensionPath |
放第三方扩展的路径,默认为 application.ext |
|
timezone |
获取或者设置时区 |
|
locale |
本地化对象,用于对时间、数字等的本地化 |
|
globalsate |
全局状态数组,该数组会被持久化(通过 statePersister 实现) |
|
组件 |
coreMessages |
对框架层内容进行翻译,支持多语言 |
messages |
对应用层内容进行翻译,支持多语言 |
|
db |
数据库组件 |
errorHandler |
异常处理组件,该组件与 App 配合来处理所有的异常 |
|
securityManager |
安全管理组件 |
|
statePersister |
状态持久化组件 |
|
urlManager |
url 管理组件 |
|
request |
请求组件 |
|
format |
格式化组件 |
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 不存在
递归调用
在id为seg1的子模块 中寻找 |
|
|
|
不存在
3
在ControllerPath路 径下逐层寻找
是否存在ControllerPath/seg1/Seg2Controller.php ControlId为/seg1/seg2
不存在 是否存在ControllerPath/seg1/seg2/Seg3Controller.php
ControlId为/seg1/seg2/seg3
添加的重要的成员变量、成员函数和组件见下表:
类别 |
名称 |
说明 |
成员变量 |
defaultController |
默认的控制类,如果没有指定控制器,则使用该控制器 |
|
layout |
默认的布局,如果控制器没有指定布局,则使用该布局 |
|
controllerMap |
控制器映射表,给某些特定的路由指定控制器 |
|
theme |
设置主题 |
|
controller |
当前的控制器对象 |
|
controllerPath |
控制器文件的路径 |
|
ViewPath |
视图层文件的路径,默认为 protected/views/ |
|
SystemViewPath |
系统视图文件的路径,默认为 protected/views/system/ |
|
LayoutPath |
布局文件的路径,默认为 protected/views/layouts/ |
组件 |
session |
session 组件 |
|
assetManager |
资源管理组件,用于发布私有的 js、css 和 image |
|
user |
用户组件,用户登录等 |
|
themeManager |
主题组件 |
|
authManager |
权限组件,实现了基于角色的权限控制 |
|
clientScript |
客户端脚本管理组件,管理 js 和 css 代码 |
3、系统组件
3.1、日志路由组件
每个 Web 系统在运行的过程中都需要记录日志,日志可以记录到文件或数据库中,在 开发阶段可以把日志直接输出到页面得底部,这样可以加快开发速度。Yii 在日志处理上做 了如下 2 点重要工作:
1、每个 Http 请求,可能需要记录多条日志(数据库更新日志/与其它系统交互日志)。比 如某次 Http 请求要记录 18 条日志,我们是每记一条日志都写一次硬盘(即写 18 硬盘)呢,还 是在请求快结束的时候一次性写硬盘?很显然,先把这些日志保存在一个 php 的数组中, 在请求快结束的时候,把数组中的所有日志一次性写硬盘速度要快一些。
2、每条日志可以从 2 个维度来进行分类:日志的严重级别、日志的业务逻辑。用下表
来描述“百度创意专家”产品的日志在这 2 个维度上的情况:
业务逻辑 严重级别 |
数据库日志 |
用户中心接口日志 |
Drmc 接口日志 |
Memcache 日志 |
trace |
|
|
|
|
info |
|
|
|
|
profile |
|
|
|
|
warning |
|
|
|
|
error |
|
|
|
|
按业务逻辑分为:数据库操作日志、用户中心接口日志、Drmc 接口日志、Memcache 更新日志等等。
按照严重级别分为:trace、info、profile、warning、error。 我们可能希望把不同业务逻辑(数据库日志、与其它系统交互的日志)的日志记录到不同
的文件中,这样可以分门别类的查看。因为 error 日志一般比较严重,所以我们可能还希望 把所有的 error 记录到一个单独的文件中或者 mongodb 中。Yii 中的日志路由组件可以将不 同类别的日志路由到不同的目的地(文件、数据库、邮箱和页面),利用它可以非常方便维护 和管理日志。
如下是一个日志路由组件的配置,该配置将不同业务逻辑的日志记录到不同的文件中, 把错误日志单独记录到 error.log 文件中,把严重的日志直接发邮件,在开发过程还将日志输 出到页面上,加快了开发速度。具体配置如下:
'log'=>array(
'class'=>'CLogRouter', 'routes'=>array(
array(//数据库日志记录到db.log中 'class'=>'CFileLogRoute', 'categories'=>'db.*', 'logFile'=>'db.log',
),
array(//与用户中心交互的日志记录到uc.log中 'class'=>'CFileLogRoute', 'categories'=>'uc.*',
'logFile'=>'uc.log',
),
array(//与Drmc交互的日志记录到uc.log中 'class'=>'CFileLogRoute', 'categories'=>'drmc.*', 'logFile'=>'drmc.log',
),
array(//所有的错误日志记录到error.log中 'class'=>'CFileLogRoute', 'levels'=>'error', 'logFile'=>'error.log',
),
array(//因为用户中心很重要,所有的用户中心错误日志需要离开发邮件
'class'=>'CEmailLogRoute', 'categories'=>'uc.*', 'levels'=>'error', 'emails'=>'admaker@baidu.com',
),
array(//开发过程中,把所有的日志直接打印到页面底部,这样就不需要登录服务器看日志了
'class'=>'CWebLogRoute' 'levels'=>'trace,info,profile,warning,error',
),
)
通过上面的代码可以知道,Yii 的日志记录是通过配置数组驱动的,接下来对 Yii 中日志
处理进行深入的分析,下图描述 Yii 中日志处理的流程和原理:
1、根据日志路由组件的配置, 生成多个日志记录对象
日志路由组件
日志记录对象1
db.log
2、将日志保存 到日志缓冲区
日志 缓冲区
3、缓冲区满或 请求结束时通知 日志路由组件
日志记录对象2
。。。
uc.log
日志记录对象i
5、把日志 输出到指定 的目的地
发送邮件日志
4、每个日志记录 对象从缓冲区中取 出自己需要的日志
日志记录对象N
日志输出到页面
一次 Http 请求的过程中,记录日志的处理流程分如下 5 个阶段:
Step1:根据日志路由器的配置信息,生成各个日志记录对象,即 CFileLogRoute、
CEmailLogRoute 和 CWebLogRoute 的对象,日志路由组件统一管理这些对象;
Step2:程序员调用写日志的接口(见下表),来记录日志,所有的日志都是暂时保存在一 个 php 的数组缓冲区中;
Step3:当缓冲区满的时候或请求处理结束的时候,会触发以个 Flush 事件,通知日志路 由组件来取日志,这里使用的就是是观察者模式;
Step4:每个日志记录对象分别取出自己需要的日志,比如数据库的日志、用户中心交 互日志、error 级别的日志
上一篇: Eclipse搭建Spring开发环境