Thinkphp源码分析系列(五)–系统钩子实现
Thinkphp的插件机制主要依靠的是Hook.class.php这个类,官方文档中在行为扩展也主要依靠这个类来实现。下面我们来具体看看tp是怎么利用这个类来实现行为扩展的。
首先,行为扩展是什么?有wordpress二次开发经验的同学应该很容易明白,其实就是钩子,tp在其内核的执行过程中内置了诸多钩子,这些钩子可以允许我们能够在不改变内核代码的基础上来对内核进行一定程度的修改。tp的钩子机制的实现类就是Hook.class.php。
Hook.class.php内部维护了一个数组,这个数组的键就是钩子的名称,值就是类的名称的集合。我们利用Hook类的add方法可以添加一个钩子,其实就是往这个维护的数组上添加一个键值。tp默认已经定义了很多钩子标签。
app_init 应用初始化标签位
path_info PATH_INFO检测标签位
app_begin 应用开始标签位
action_name 操作方法名标签位
action_begin 控制器开始标签位
view_begin 视图输出开始标签位
view_parse 视图解析标签位
template_filter 模板内容解析标签位
view_filter 视图输出过滤标签位
view_end 视图输出结束标签位
action_end 控制器结束标签位
app_end 应用结束标签位
在3.2版本的tp框架中,钩子标签的实现机制是这样的。
首先所有的钩子标签和其对应的类是记录在应用模式文件中。tp默认的应用模式是common,对应的应用模式文件是Thinkphp/Mode/Common.php文件。在此文件中我们可以看到行为扩展的定义:
// 行为扩展定义
'tags' => array(
'app_init' => array(
'Behavior\BuildLiteBehavior', // 生成运行Lite文件
),
'app_begin' => array(
'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存
),
'app_end' => array(
'Behavior\ShowPageTraceBehavior', // 页面Trace显示
),
'view_parse' => array(
'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
),
'template_filter'=> array(
'Behavior\ContentReplaceBehavior', // 模板输出替换
),
'view_filter' => array(
'Behavior\WriteHtmlCacheBehavior', // 写入静态缓存
),
),
我们在前面说到 ThinkPHP 引导类的时候讲到此类会根据当前的模式读取模式文件并且按照模式文件中的配置依次去读取配置文件从而完成系统核心的加载。其中有一项就是上面的行为模式。在这个引导类的75行左右,程序开始加载模式文件中定义的标签和类,通过Hook::import方法把这些标签和类的映射加载到了Hook内部为的tags数组中。
// 加载模式别名定义
if(isset($mode['alias'])){
self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']);
}
然后在后面我们就可以看到tp框架在监听这些标签。何为监听?我们来看一下APP.class.php里面使用到的监听。
/**
* 运行应用实例 入口文件使用的快捷方法
* @access public
* @return void
*/
static public function run() {
// 应用初始化标签
Hook::listen('app_init');
App::init();
// 应用开始标签
Hook::listen('app_begin');
// Session初始化
if(!IS_CLI){
session(C('SESSION_OPTIONS'));
}
// 记录应用初始化时间
G('initTime');
App::exec();
// 应用结束标签
Hook::listen('app_end');
return ;
}
Hook::listen(‘app_begin’);就是一个监听。当程序执行到此处代码的时候,这个代码会去执行listen方法,此方法会去检测Hook持有的tags数组中是否含有app_begin标签,如果有的话就去看其对应的类文件,并到ThinkPHP\Library\Behavior目录下去寻找对应的类文件并加载实例化。然后就去调用实例化对象的run方法并执行。
由此可见,如果我们想要在应用执行开始的时候加一些我们自己的实现逻辑,只需要写一个带有run方法的行为类,这个类一般继承自Behavior类,然后在run方法中写入自己的逻辑,然后把我们写好的类名加到模式文件中,这样就可以轻松的做到扩展核心代码了。
下面我们来具体看一下Hook类的实现细节。
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2013 http://topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think;
/**
* ThinkPHP系统钩子实现
*/
class Hook {
//这就是Hook类持有的tags静态数组变量,此变量以键值对的形式存储标签和类的映射。
static private $tags = array(); /**
* 动态添加插件到某个标签
* @param string $tag 标签名称
* @param mixed $name 插件名称
* @return void
其实就是把$tag作为键,$name 作为对应的额值加入到tags数组中。如果$name是一个数组就合并到tags中。
*/
static public function add($tag,$name) {
if(!isset(self::$tags[$tag])){
self::$tags[$tag] = array();
}
if(is_array($name)){
self::$tags[$tag] = array_merge(self::$tags[$tag],$name);
}else{
self::$tags[$tag][] = $name;
}
} /**
* 批量导入插件
* @param array $data 插件信息
* @param boolean $recursive 是否递归合并
* @return void
导入插件的本质还是把数据加入到tags数组中。但是其传入的参数是$data数组,$data本身就是一个类似于tags的东西,它存储的也是标签和类的映射。所以是把$data和$tags合并了。
*/
static public function import($data,$recursive=true) {
if(!$recursive){ // 覆盖导入
self::$tags = array_merge(self::$tags,$data);
}else{ // 合并导入
foreach ($data as $tag=>$val){
if(!isset(self::$tags[$tag]))
self::$tags[$tag] = array();
if(!empty($val['_overlay'])){
// 可以针对某个标签指定覆盖模式
unset($val['_overlay']);
self::$tags[$tag] = $val;
}else{
// 合并模式
self::$tags[$tag] = array_merge(self::$tags[$tag],$val);
}
}
}
} /**
* 获取插件信息
* @param string $tag 插件位置 留空获取全部
* @return array
*/
static public function get($tag='') {
if(empty($tag)){
// 获取全部的插件信息
return self::$tags;
}else{
return self::$tags[$tag];
}
} /**
* 监听标签的插件
* @param string $tag 标签名称
* @param mixed $params 传入参数
* @return void
此函数最为重要,其中调用Hook类的另外一个重要方法exec来执行对应钩子标签的类。
*/
static public function listen($tag, &$params=NULL) {
if(isset(self::$tags[$tag])) {
if(APP_DEBUG) {
G($tag.'Start');
trace('[ '.$tag.' ] --START--','','INFO');
}
foreach (self::$tags[$tag] as $name) {
APP_DEBUG && G($name.'_start');
$result = self::exec($name, $tag,$params);
if(APP_DEBUG){
G($name.'_end');
trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
}
if(false === $result) {
// 如果返回false 则中断插件执行
return ;
}
}
if(APP_DEBUG) { // 记录行为的执行日志
trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
}
}
return;
} /**
* 执行某个插件
* @param string $name 插件名称
* @param string $tag 方法名(标签名)
* @param Mixed $params 传入的参数
* @return void
执行插件的原理:其实就是通过标签从tags数组中得到类名的集合,然后拼凑出类文件名称,实例化类,执行类的run方法。
*/
static public function exec($name, $tag,&$params=NULL) {
if('Behavior' == substr($name,-8) ){
// 行为扩展必须用run入口方法
$tag = 'run';
}
$addon = new $name();
return $addon->$tag($params);
}
}
加入博主个人博客,一起学习http://www.kanronghua.com/
Thinkphp源码分析系列(五)–系统钩子实现的更多相关文章
- Thinkphp源码分析系列–开篇
目前国内比较流行的php框架由thinkphp,yii,Zend Framework,CodeIgniter等.一直觉得自己在php方面还是一个小学生,只会用别人的框架,自己也没有写过,当然不是自己不 ...
- Thinkphp源码分析系列(二)–引导类
在上一章我们说到,ThinkPHP.php在设置完框架所需要的变量和调教好环境后,在最后调用了 Think\Think::start(); 即Think命名空间中的Think类的静态方法start ...
- Thinkphp源码分析系列(九)–视图view类
视图类view主要用于页面内容的输出,模板调用等,用在控制器类中,可以使得控制器类把表现和数据结合起来.下面我们来看一下执行流程. 首先,在控制器类中保持着一个view类的对象实例,只要继承自控制器父 ...
- Thinkphp源码分析系列(三)– App应用程序类
// +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO ...
- Thinkphp源码分析系列(七)–控制器基类
在mvc模式中,c代表的就是控制器,是是应用程序中处理用户交互的部分.通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据.控制器是沟通视图和模型的桥梁,他接受用户请求,并调用模型层去处理用户 ...
- Thinkphp源码分析系列(一)–入口文件
正如官方文档上所介绍的,thinkphp使用单一入口,所有的请求都从默认的index.php文件进入.当然不是说一定非得从index.php进入,这应该取决于你的服务器配置,一般服务器都会有默认的首页 ...
- Thinkphp源码分析系列(四)–Dispatcher类
下面我们来分析一下Thinkphp中的url解析和路由调度类.此类主要功能是 // +--------------------------------------------------------- ...
- Thinkphp源码分析系列(六)–路由机制
在ThinkPHP框架中,是支持URL路由功能,要启用路由功能,需要设置ROUTER_ON 参数为true. 开启路由功能后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称, ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
随机推荐
- Next Permutation
Implement next permutation, which rearranges numbers into the lexicographically next greater permuta ...
- 程序员写的东西出了bug,造成了损失谁来承担?
这是个持续多年的话题了,很多大公司,尤其是牛逼的独立分包公司(开发公司)都会有代码审核和严格QA程序,一般的公司就很难说咯,在法律上目前还没有完全支持处罚程序员bug经济损失的判例(国内如此),国外也 ...
- UIView详解
MVC架构模式 MVC(Model-View-Controller)是实现数据和显示数据的视图分离的架构模式(有一定规模的应用都应该实现数据和显示的分离).其中,M代表模型,就是程序中使用的数据和 ...
- Flat UI 工具包
Flat UI是一套精美的扁平风格 UI 工具包,基于 Twitter Bootstrap 实现.这套界面工具包含许多基本的和复杂的 UI 部件,例如按钮,输入框,组合按钮,复选框,单选按钮,标签,菜 ...
- C#压缩图片——高质量压缩方式
传入Bitmap对象,以及新图片的长宽(Bitmap.Size),这样生成的就是跟原图尺寸一致的低质量图片 public Bitmap GetImageThumb(Bitmap mg, Size ne ...
- MAC的终端命令
今天小研究了一下MAC的终端命令,主要为了方便调试程序用,XCODE用不来啊... 在这里记下..防止丢失 pwd 当前工作目录 cd(不加参数) 进root cd(folder) 进入文件夹 cd ...
- etcd第一集
网站:https://github.com/coreos/etcd 一些观点:https://yq.aliyun.com/articles/11035 1.etcd是键值存储仓库,配置共享和服务发现2 ...
- spring retry 使用
1. 场景 系统方法调用时无状态的,同时因为网络原因,或者系统暂时故障,进行的重试 2. maven 依赖 <project xmlns="http://maven.apa ...
- k8s dashboard 部署发布
https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml # Copyright 2015 ...
- HackerRank "Equal Stacks"
Greedy - though simple, but fun! #include <vector> #include <iostream> using namespace s ...