CI框架源码阅读笔记6 扩展钩子 Hook.php
CI框架允许你在不修改系统核心代码的基础上添加或者更改系统的核心功能(如重写缓存、输出等)。例如,在系统开启hook的条件下(config.php中$config['enable_hooks'] = TRUE;
),通过添加特定的钩子,可以让系统在特定的时刻触发特定的脚本:
$hook['post_system'] = array(
'class' => 'frameLog',
'function' => 'postLog',
'filename' => 'post_system.php',
'filepath' => 'hooks',
);
上述钩子定义了一个post_system的钩子,用于在最终的页面渲染之后的脚本处理(参数的含义可以参考后面或者手册,这里暂时不做更多解释)。
那么问题来了:
- 钩子是什么?
- CI中支持的钩子有哪些?
- CI中钩子是如何实现的?
我们一步步来看。
1. 钩子是什么
百度百科上对于钩子的定义是:
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
从上述定义我们可以看出几点:
- 钩子是一种事件驱动模式,它的核心自然是事件(CI中pre_system,pre_controller等都是特定的事件)。
- 既然是事件驱动,那么必然要包含最重要的两个步骤: (1)、事件注册。对于Hook而言,就是指Hook钩子的挂载。(2).事件触发。在特定的时间点call特定的钩子,执行相应的钩子程序。
- 既然是事件驱动,那么也应该支持统一挂钩点的多个注册事件。
- 启动Hook钩子之后,程序的流程可能会发生变化,且钩子之间可能有相互调用的可能性,如果处理不当,会有死循环的可能性。同时,钩子的启用使得程序在一定程度上变得复杂,难以调试。
2. CI中预定义钩子
CI中提供了7个可用的预设挂钩点,分别是:
pre_system: 指在系统加载前期的钩子
pre_controller:调用控制器之前的钩子,路由与安全性检查已经完毕
post_controller_constructor:控制器实例化之后,任何方法调用之前
post_controller:控制器完全运行之后
display_override:重写display
cache_override :重写缓存
post_system:最终的页面发送到客户端之后
3. CI中钩子的实现
CI中钩子的核心功能是由Hook组件完成的,先看该组件的类图:
其中:
enabled: 钩子功能是否开启的标志。
hooks :保存系统中启用的钩子列表
in_progress:之后我们会看到,这个标志位用于防止钩子之间的互相调用而导致的死循环。
_construct是Hook组件的构造函数,这其中调用了_initialize来完成初始化的工作
_call_hook: 调用_run_hook来call指定的钩子程序。之前CodeIgniter.php中我们已经看到,_call_hook是实际提供给外部调用的接口。
_run_hook: 实际执行钩子程序的函数
在开始之前,我们先贴出预定义钩子的结构。这个结构可能会贯穿在源代码的始终,因而我们有必要知道该结构的参数含义。
$hook['xx'] = array(
'class' => 'xx', //钩子调用的类名,可以为空
'function' => 'xx',//钩子调用的函数名
'filename' => 'xx',//该钩子的文件名
'filepath' => 'xx',//钩子的目录
'params' => 'xx'//传递给钩子的参数
);
1. 钩子组件初始化
_initialize函数用于钩子组件的初始化,该函数主要完成的工作有:
(1) 检查配置文件中hook功能是否被启用,这需要加载Config(配置管理组件):
$CFG =& load_class('Config', 'core'); if ($CFG->item('enable_hooks') == FALSE)
{
return;
}
(2) 加载定义的hook列表
同样,你可以设定不同的ENVIRONMENT启用不同的hook,如果有的话,优先加载ENVRIONMENT下的hook:
if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
}
elseif (is_file(APPPATH.'config/hooks.php'))
{
include(APPPATH.'config/hooks.php');
}
(3) Hook的检查。如果未设置任何hook,或者设置的hook格式错误,则不作任何处理,直接退出:
if ( ! isset($hook) OR ! is_array($hook))
{
return;
}
经过initialize之后,Hook::hooks中存储了已经定义的hook列表:
$this->hooks =& $hook;
2. Call指定的钩子
_call_hook是主程序中直接调用的接口。该接口主要的工作有:
(1). 检查钩子是否被启用,以及call的钩子是否被预定义(如果未启用或者call的钩子不存在,则直接返回):
if ( ! $this->enabled OR ! isset($this->hooks[$which]))
{
return FALSE;
}
(2). 检查同一个挂钩点是否启用了多个钩子,如果有,则依次执行之:
if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0]))
{
foreach ($this->hooks[$which] as $val)
{
$this->_run_hook($val);
}
}
(3). 否则,只有一个钩子,执行它
else
{
$this->_run_hook($this->hooks[$which]);
}
_run_hook是实际执行hook的函数。
3. run执行特定的钩子程序
_run_hook函数是hook的实际执行者,该函数接收一个预定义的hook数组作为参数,实现如下:
(1). 如果传递的参数压根就不是数组(自然也就不是有效的hook),那么直接返回:
if ( ! is_array($data))
{
return FALSE;
}
(2). 检查hook执行状态。
in_progress用于标志当前hook的执行状态。这个参数的主要作用,是防止hook之间的相互调用而导致的死循环。
if ($this->in_progress == TRUE)
{
return;
}
(3). Hook的合法性检查。
为了方便讲述,我们再次提出一个预定义的hook需要的参数:
$hook['xx'] = array(
'class' => 'xx', //钩子调用的类名,可以为空
'function' => 'xx',//钩子调用的函数名
'filename' => 'xx',//该钩子的文件名
'filepath' => 'xx',//钩子的目录
'params' => 'xx'//传递给钩子的参数
);
其中class和params是可选参数,其他3个参数为必选参数,如果不提供,则由于无法准确定位到hook程序,只能直接返回:
if ( ! isset($data['filepath']) OR ! isset($data['filename']))
{
return FALSE;
} $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath))
{
return FALSE;
}
(4). 到这里,已经基本确认钩子程序的位置了,这里有两种情况:
a. 预定义的hook中class参数为空,表明使用的是过程式的调用方式,则直接执行hook文件中的function xxx
b. class参数不为空,提供的是面向对象的方式,则实际的钩子程序是$class->$function .同样,如果既没有设置class,也没有设置function参数,则无法执行hook,直接返回:
$class = FALSE;
$function = FALSE;
$params = ''; /* 获取 hook class */
if (isset($data['class']) AND $data['class'] != '')
{
$class = $data['class'];
} /* 获取 hook function */
if (isset($data['function']))
{
$function = $data['function'];
} /* 获取传递的 hook 参数 */
if (isset($data['params']))
{
$params = $data['params'];
} /* 如果class和function都不存在,则无法定位hook程序,直接返回 */
if ($class === FALSE AND $function === FALSE)
{
return FALSE;
}
(5). 设置执行标志in_progress,并执行上述两种情况下的hook:
/* 面向对象的设置方式 */
if ($class !== FALSE)
{
if ( ! class_exists($class))
{
require($filepath);
} $HOOK = new $class;
$HOOK->$function($params);
}
/* 过程式的执行方式 */
else
{
if ( ! function_exists($function))
{
require($filepath);
} $function($params);
}
最后,别忘了在hook执行完之后,设置标识位in_progress为false,并返回执行成功的标志:
$this->in_progress = FALSE;
return TRUE;
Hook组件的完整源码:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class CI_Hooks { /**
* Determines wether hooks are enabled
*
* @var bool
*/
var $enabled = FALSE;
/**
* List of all hooks set in config/hooks.php
*
*/
var $hooks = array();
/**
* Determines wether hook is in progress, used to prevent infinte loops
*
*/
var $in_progress = FALSE; /**
* Constructor
*/
function __construct()
{
$this->_initialize();
log_message('debug', "Hooks Class Initialized");
} /**
* Initialize the Hooks Preferences
*
* @access private
* @return void
*/
function _initialize()
{
$CFG =& load_class('Config', 'core'); // If hooks are not enabled in the config file
// there is nothing else to do if ($CFG->item('enable_hooks') == FALSE)
{
return;
} if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
}
elseif (is_file(APPPATH.'config/hooks.php'))
{
include(APPPATH.'config/hooks.php');
} if ( ! isset($hook) OR ! is_array($hook))
{
return;
} $this->hooks =& $hook;
$this->enabled = TRUE;
} /**
* Call Hook
*
* Calls a particular hook
*
* @access private
* @param string the hook name
* @return mixed
*/
function _call_hook($which = '')
{
if ( ! $this->enabled OR ! isset($this->hooks[$which]))
{
return FALSE;
} if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0]))
{
foreach ($this->hooks[$which] as $val)
{
$this->_run_hook($val);
}
}
else
{
$this->_run_hook($this->hooks[$which]);
} return TRUE;
} /**
* Run Hook
*
* Runs a particular hook
*
* @access private
* @param array the hook details
* @return bool
*/
function _run_hook($data)
{
if ( ! is_array($data))
{
return FALSE;
} // If the script being called happens to have the same hook call within it a loop can happen if ($this->in_progress == TRUE)
{
return;
} if ( ! isset($data['filepath']) OR ! isset($data['filename']))
{
return FALSE;
} $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath))
{
return FALSE;
} $class = FALSE;
$function = FALSE;
$params = ''; if (isset($data['class']) AND $data['class'] != '')
{
$class = $data['class'];
} if (isset($data['function']))
{
$function = $data['function'];
} if (isset($data['params']))
{
$params = $data['params'];
} if ($class === FALSE AND $function === FALSE)
{
return FALSE;
} $this->in_progress = TRUE; // Call the requested class and/or function
if ($class !== FALSE)
{
if ( ! class_exists($class))
{
require($filepath);
} $HOOK = new $class;
$HOOK->$function($params);
}
else
{
if ( ! function_exists($function))
{
require($filepath);
} $function($params);
} $this->in_progress = FALSE;
return TRUE;
} }
参考文献
1. http://codeigniter.org.cn/user_guide/general/hooks.html 手册
2. http://itopic.org/codeigniter-hook.html
3. http://codeigniter.org.cn/forums/thread-4947-1-1.html 钩子实现的Layout
CI框架源码阅读笔记6 扩展钩子 Hook.php的更多相关文章
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...
- CI框架源码阅读笔记3 全局函数Common.php
从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...
- CI框架源码阅读笔记5 基准测试 BenchMark.php
上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...
- CI框架源码阅读笔记2 一切的入口 index.php
上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...
- CI框架源码阅读笔记1 - 环境准备、基本术语和框架流程
最开始使用CI框架的时候,就打算写一个CI源码阅读的笔记系列,可惜虎头蛇尾,一直没有行动.最近项目少,总算是有了一些时间去写一些东西.于是准备将之前的一些笔记和经验记录下来,一方面权作备忘,另一方面时 ...
- CI框架源码阅读笔记8 控制器Controller.php
最近时间有些紧,源码阅读系列更新有些慢.鉴于Controller中代码比较少,本次Blog先更新该文件的源码分析. 在经过路由分发之后,实际的应用Controller接管用户的所有请求,并负责与用户数 ...
- ****CI框架源码阅读笔记7 配置管理组件 Config.php
http://blog.csdn.net/ohmygirl/article/details/41041597 一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件 ...
- CI框架源码阅读笔记9 CI的自动加载机制autoload
本篇并不是对某一组件的详细源码分析,而只是简单的跟踪了下CI的autoload的基本流程.因此,可以看做是Loader组件的分析前篇. CI框架中,允许你配置autoload数组,这样,在你的应用程序 ...
- CI框架源码学习笔记1——index.php
做php开发一年多了,陆陆续续用过tp/ci/yii框架,一直停留在只会使用的层面上,关于框架内部的结构实际上是不甚了解的.为了深入的学习,决定把CI框架的源码从头到尾的学习一下, 主要因为CI框架工 ...
随机推荐
- spring容器对bean生命周期的管理三中方式
spring容器对bean的生命周期管理主要在两个时间点:bean的初始化完成(包括属性值被完全注入),bean的销毁(程序结束,或者引用结束)方式一:使用springXML配置中的init-meth ...
- 彻底理解跨域解决方案JSONP
什么是同源策略? 同源策略,它是由Netscape提出的一个著名的安全策略.现在所有支持JavaScript 的浏览器都会使用这个策略. 所谓同源是指,域名,协议,端口相同.当一个浏览器的两个tab页 ...
- KnockoutJS 3.X API 第四章 表单绑定(10) textInput、hasFocus、checked绑定
textInput绑定目的 textInput绑定主要用于<input>或者<textarea>元素.他提供了DOM和viewmodel的双向更新.不同于value绑定,tex ...
- Java EE开发平台随手记1
过完春节以来,一直在负责搭建公司的新Java EE开发平台,所谓新平台,其实并不是什么新技术,不过是将目前业界较为流行的框架整合在一起,做一些简单的封装和扩展,让开发人员更加易用. 和之前负责具体的项 ...
- 加载的过程中图片变形了? --教你自定义自动适配图片宽高比的RatioLayout
很多同行在开发中可能会遇到这样的问题,就是在加载图片的时候会出现图片变形的问题.其实这很可能就是你的图片宽高比和图片所在容器的宽高比不匹配造成的.比如说图片的宽为200,高为100.宽高比就是2,那么 ...
- 中小型ERP系统开发与实施
1. 能反映企业管理的各个方面和解决企业实际的问题,不限于一般问题的解决,而是深入企业的业务过程 2. 在此软件的背后有真正的管理思想(不是泛泛而言)和对管理的精髓理解 和归纳,有一个整体的较详细的规 ...
- 【博客美化】06.添加QQ交谈链接
博客园美化相关文章目录: [博客美化]01.推荐和反对炫酷样式 [博客美化]02.公告栏显示个性化时间 [博客美化]03.分享按钮 [博客美化]04.自定义地址栏logo [博客美化]05.添加Git ...
- Java多线程系列--“JUC原子类”02之 AtomicLong原子类
概要 AtomicInteger, AtomicLong和AtomicBoolean这3个基本类型的原子类的原理和用法相似.本章以AtomicLong对基本类型的原子类进行介绍.内容包括:Atomic ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Tridiv:基于 Web 的 CSS 编辑器,创建炫丽 3D 图形
Tridiv 是一个基于 Web 的编辑器,使用 CSS 创建 3D 形状.它提供了一个传统的四个面板的操作界面,给出了从每个平面的视图,以及一个预览窗格中示出的最终的效果.使用 Tridiv 可以创 ...