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的钩子,用于在最终的页面渲染之后的脚本处理(参数的含义可以参考后面或者手册,这里暂时不做更多解释)。

那么问题来了:

  1. 钩子是什么?
  2. CI中支持的钩子有哪些?
  3. CI中钩子是如何实现的?

我们一步步来看。

1.  钩子是什么

  百度百科上对于钩子的定义是:

钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

  从上述定义我们可以看出几点:

  1. 钩子是一种事件驱动模式,它的核心自然是事件(CI中pre_system,pre_controller等都是特定的事件)。
  2. 既然是事件驱动,那么必然要包含最重要的两个步骤: (1)、事件注册。对于Hook而言,就是指Hook钩子的挂载。(2).事件触发。在特定的时间点call特定的钩子,执行相应的钩子程序。
  3. 既然是事件驱动,那么也应该支持统一挂钩点的多个注册事件。
  4. 启动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

时间: 2024-12-28 17:18:09

CI框架源码阅读笔记6 扩展钩子 Hook.php的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

CI框架源码阅读笔记1 - 环境准备、基本术语和框架流程

最开始使用CI框架的时候,就打算写一个CI源码阅读的笔记系列,可惜虎头蛇尾,一直没有行动.最近项目少,总算是有了一些时间去写一些东西.于是准备将之前的一些笔记和经验记录下来,一方面权作备忘,另一方面时刻提醒自己:借鉴和学习才有出路,忘记过去意味着背叛! 基本术语说明 在本文开始之前,有必要对文中反复出现的术语做一个简单的说明,如果你对这一部分已经熟谙,完全可以略过.本文中反复出现和提及的术语包括: 前端控制器(Front Controller): 用于集中控制用户的所有请求的组件,将用户的请求发

CI框架源码阅读笔记8 控制器Controller.php

最近时间有些紧,源码阅读系列更新有些慢.鉴于Controller中代码比较少,本次Blog先更新该文件的源码分析. 在经过路由分发之后,实际的应用Controller接管用户的所有请求,并负责与用户数据的交互.CI中所有的应用控制器都应该是CI_Controller的子类(除非你扩展了CI的核心,那么你的Controller父类可以是MY_Controller). 在应用程序控制器中,我们经常会用到这样的代码: /* 加载配置文件 */ $this->load->config("co

****CI框架源码阅读笔记7 配置管理组件 Config.php

http://blog.csdn.net/ohmygirl/article/details/41041597 一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件中(这里指Application/Config/Config.php文件),有如下多项配置: $config['base_url'] = 'http://test.xq.com'; $config['index_page'] = ''; $config['uri_protocol'] = 'AUTO

CI框架源码阅读笔记9 CI的自动加载机制autoload

本篇并不是对某一组件的详细源码分析,而只是简单的跟踪了下CI的autoload的基本流程.因此,可以看做是Loader组件的分析前提. CI框架中,允许你配置autoload数组,这样,在你的应用程序初始化时,会自动加载相应的类库,例如,在application/config/autoload.php中,autoload的配置如下: $autoload['libraries'] = array("smarty", "redis"); 则CI框架初始化时,会自动加载l

PHP框架CodeIgniter CI框架源码学习笔记-index.php一切的入口

CI框架(CodeIgniter)的基本执行流程图以备参考: index.php作为CI框架的入口文件,源码阅读,自然由此开始.