CI框架同意你在不改动系统核心代码的基础上加入或者更改系统的核心功能(如重写缓存、输出等)。

比如,在系统开启hook的条件下(config.php中$config['enable_hooks'] = TRUE;)。通过加入特定的钩子,能够让系统在特定的时刻触发特定的脚本:

  1. $hook['post_system'] = array(
  2. 'class' => 'frameLog',
  3. 'function' => 'postLog',
  4. 'filename' => 'post_system.php',
  5. 'filepath' => 'hooks',
  6. );

上述钩子定义了一个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:终于的页面发送到client之后

3.  CI中钩子的实现

  CI中钩子的核心功能是由Hook组件完毕的,先看该组件的类图:

当中:

  enabled:  钩子功能是否开启的标志。

  hooks :保存系统中启用的钩子列表

  in_progress:之后我们会看到,这个标志位用于防止钩子之间的互相调用而导致的死循环。

  _construct是Hook组件的构造函数,这当中调用了_initialize来完毕初始化的工作

  _call_hook: 调用_run_hook来call指定的钩子程序。

之前CodeIgniter.php中我们已经看到。_call_hook是实际提供给外部调用的接口。

  _run_hook: 实际运行钩子程序的函数

在開始之前,我们先贴出提前定义钩子的结构。

这个结构可能会贯穿在源码的始终,因而我们有必要知道该结构的參数含义。

  1. $hook['xx'] = array(
  2. 'class' => 'xx', //钩子调用的类名。能够为空
  3. 'function' => 'xx',//钩子调用的函数名
  4. 'filename' => 'xx',//该钩子的文件名称
  5. 'filepath' => 'xx',//钩子的文件夹
  6. 'params' => 'xx'//传递给钩子的參数
  7. );

1).  钩子组件初始化

_initialize函数用于钩子组件的初始化,该函数主要完毕的工作有:

(1)     检查配置文件里hook功能是否被启用。这须要载入Config(配置管理组件):

  1. $CFG =& load_class('Config', 'core');
  2.  
  3. if ($CFG->item('enable_hooks') == FALSE)
  4. {
  5. return;
  6. }

(2)     载入定义的hook列表

相同,你能够设定不同的ENVIRONMENT启用不同的hook,假设有的话,优先载入ENVRIONMENT下的hook:

  1. if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
  2. {
  3. include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
  4. }
  5. elseif (is_file(APPPATH.'config/hooks.php'))
  6. {
  7. include(APPPATH.'config/hooks.php');
  8. }

(3)     Hook的检查。假设未设置不论什么hook,或者设置的hook格式错误,则不作不论什么处理,直接退出:

  1. if ( ! isset($hook) OR ! is_array($hook))
  2. {
  3. return;
  4. }

经过initialize之后,Hook::hooks中存储了已经定义的hook列表:

  1. $this->hooks =& $hook;

2.  Call指定的钩子

_call_hook是主程序中直接调用的接口。该接口基本的工作有:

(1).    检查钩子是否被启用,以及call的钩子是否被提前定义(假设未启用或者call的钩子不存在。则直接返回):

  1. if ( ! $this->enabled OR ! isset($this->hooks[$which]))
  2. {
  3. return FALSE;
  4. }

(2).    检查同一个挂钩点是否启用了多个钩子,假设有。则依次运行之:

  1. if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0]))
  2. {
  3. foreach ($this->hooks[$which] as $val)
  4. {
  5. $this->_run_hook($val);
  6. }
  7. }

(3).    否则。仅仅有一个钩子。运行它

  1. else
  2. {
  3. $this->_run_hook($this->hooks[$which]);
  4. }

_run_hook是实际运行hook的函数。

3.  run运行特定的钩子程序

_run_hook函数是hook的实际运行者。该函数接收一个提前定义的hook数组作为參数。实现例如以下:

(1).    假设传递的參数压根就不是数组(自然也就不是有效的hook),那么直接返回:

  1. if ( ! is_array($data))
  2. {
  3. return FALSE;
  4. }

(2).    检查hook运行状态。

in_progress用于标志当前hook的运行状态。这个參数的主要作用,是防止hook之间的相互调用而导致的死循环。

  1. if ($this->in_progress == TRUE)
  2. {
  3. return;
  4. }

(3).    Hook的合法性检查。

为了方便讲述,我们再次提出一个提前定义的hook须要的參数:

  1. $hook['xx'] = array(
  2. 'class' => 'xx', //钩子调用的类名,能够为空
  3. 'function' => 'xx',//钩子调用的函数名
  4. 'filename' => 'xx',//该钩子的文件名称
  5. 'filepath' => 'xx',//钩子的文件夹
  6. 'params' => 'xx'//传递给钩子的參数
  7. );

当中class和params是可选參数,其它3个參数为必选參数。假设不提供,则因为无法准确定位到hook程序,仅仅能直接返回:

  1. if ( ! isset($data['filepath']) OR ! isset($data['filename']))
  2. {
  3. return FALSE;
  4. }
  5.  
  6. $filepath = APPPATH.$data['filepath'].'/'.$data['filename'];
  7.  
  8. if ( ! file_exists($filepath))
  9. {
  10. return FALSE;
  11. }

(4).   到这里。已经基本确认钩子程序的位置了,这里有两种情况:

a. 提前定义的hook中class參数为空。表明使用的是过程式的调用方式。则直接运行hook文件里的function xxx

b. class參数不为空,提供的是面向对象的方式,则实际的钩子程序是$class->$function .相同,假设既没有设置class,也没有设置function參数,则无法运行hook。直接返回:

  1. $class = FALSE;
  2. $function = FALSE;
  3. $params = '';
  4.  
  5. /* 获取 hook class */
  6. if (isset($data['class']) AND $data['class'] != '')
  7. {
  8. $class = $data['class'];
  9. }
  10.  
  11. /* 获取 hook function */
  12. if (isset($data['function']))
  13. {
  14. $function = $data['function'];
  15. }
  16.  
  17. /* 获取传递的 hook 參数 */
  18. if (isset($data['params']))
  19. {
  20. $params = $data['params'];
  21. }
  22.  
  23. /* 假设class和function都不存在,则无法定位hook程序,直接返回 */
  24. if ($class === FALSE AND $function === FALSE)
  25. {
  26. return FALSE;
  27. }

(5).   设置运行标志in_progress。并运行上述两种情况下的hook:

  1. /* 面向对象的设置方式 */
  2. if ($class !== FALSE)
  3. {
  4. if ( ! class_exists($class))
  5. {
  6. require($filepath);
  7. }
  8.  
  9. $HOOK = new $class;
  10. $HOOK->$function($params);
  11. }
  12.  
  13. else
  14. {
  15. if ( ! function_exists($function))
  16. {
  17. require($filepath);
  18. }
  19.  
  20. $function($params);
  21. }

最后,别忘了在hook运行完之后,设置标识位in_progress为false,并返回运行成功的标志:

  1. $this->in_progress = FALSE;
  2. return TRUE;

Hook组件的完整源代码:

  1. <?
  2.  
  3. php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  4.  
  5. class CI_Hooks {
  6.  
  7. /**
  8. * Determines wether hooks are enabled
  9. *
  10. * @var bool
  11. */
  12. var $enabled = FALSE;
  13. /**
  14. * List of all hooks set in config/hooks.php
  15. *
  16. */
  17. var $hooks = array();
  18. /**
  19. * Determines wether hook is in progress, used to prevent infinte loops
  20. *
  21. */
  22. var $in_progress = FALSE;
  23.  
  24. /**
  25. * Constructor
  26. */
  27. function __construct()
  28. {
  29. $this->_initialize();
  30. log_message('debug', "Hooks Class Initialized");
  31. }
  32.  
  33. /**
  34. * Initialize the Hooks Preferences
  35. *
  36. * @access private
  37. * @return void
  38. */
  39. function _initialize()
  40. {
  41. $CFG =& load_class('Config', 'core');
  42.  
  43. // If hooks are not enabled in the config file
  44. // there is nothing else to do
  45.  
  46. if ($CFG->item('enable_hooks') == FALSE)
  47. {
  48. return;
  49. }
  50.  
  51. if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
  52. {
  53. include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
  54. }
  55. elseif (is_file(APPPATH.'config/hooks.php'))
  56. {
  57. include(APPPATH.'config/hooks.php');
  58. }
  59.  
  60. if ( ! isset($hook) OR ! is_array($hook))
  61. {
  62. return;
  63. }
  64.  
  65. $this->hooks =& $hook;
  66. $this->enabled = TRUE;
  67. }
  68.  
  69. /**
  70. * Call Hook
  71. *
  72. * Calls a particular hook
  73. *
  74. * @access private
  75. * @param string the hook name
  76. * @return mixed
  77. */
  78. function _call_hook($which = '')
  79. {
  80. if ( ! $this->enabled OR ! isset($this->hooks[$which]))
  81. {
  82. return FALSE;
  83. }
  84.  
  85. if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0]))
  86. {
  87. foreach ($this->hooks[$which] as $val)
  88. {
  89. $this->_run_hook($val);
  90. }
  91. }
  92. else
  93. {
  94. $this->_run_hook($this->hooks[$which]);
  95. }
  96.  
  97. return TRUE;
  98. }
  99.  
  100. /**
  101. * Run Hook
  102. *
  103. * Runs a particular hook
  104. *
  105. * @access private
  106. * @param array the hook details
  107. * @return bool
  108. */
  109. function _run_hook($data)
  110. {
  111. if ( ! is_array($data))
  112. {
  113. return FALSE;
  114. }
  115.  
  116. // If the script being called happens to have the same hook call within it a loop can happen
  117.  
  118. if ($this->in_progress == TRUE)
  119. {
  120. return;
  121. }
  122.  
  123. if ( ! isset($data['filepath']) OR ! isset($data['filename']))
  124. {
  125. return FALSE;
  126. }
  127.  
  128. $filepath = APPPATH.$data['filepath'].'/'.$data['filename'];
  129.  
  130. if ( ! file_exists($filepath))
  131. {
  132. return FALSE;
  133. }
  134.  
  135. $class = FALSE;
  136. $function = FALSE;
  137. $params = '';
  138.  
  139. if (isset($data['class']) AND $data['class'] != '')
  140. {
  141. $class = $data['class'];
  142. }
  143.  
  144. if (isset($data['function']))
  145. {
  146. $function = $data['function'];
  147. }
  148.  
  149. if (isset($data['params']))
  150. {
  151. $params = $data['params'];
  152. }
  153.  
  154. if ($class === FALSE AND $function === FALSE)
  155. {
  156. return FALSE;
  157. }
  158.  
  159. $this->in_progress = TRUE;
  160.  
  161. // Call the requested class and/or function
  162. if ($class !== FALSE)
  163. {
  164. if ( ! class_exists($class))
  165. {
  166. require($filepath);
  167. }
  168.  
  169. $HOOK = new $class;
  170. $HOOK->$function($params);
  171. }
  172. else
  173. {
  174. if ( ! function_exists($function))
  175. {
  176. require($filepath);
  177. }
  178.  
  179. $function($params);
  180. }
  181.  
  182. $this->in_progress = FALSE;
  183. return TRUE;
  184. }
  185.  
  186. }

參考文献

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的更多相关文章

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

    从本篇開始.将深入CI框架的内部.一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说.全局函数具有最高的载入优先权.因此大多数的框架中BootStrap ...

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

    上一篇博客(CI框架源代码阅读笔记4 引导文件CodeIgniter.php)中.我们已经看到:CI中核心流程的核心功能都是由不同的组件来完毕的.这些组件类似于一个一个单独的模块,不同的模块完毕不同的 ...

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

    上一节(CI框架源代码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程.这里再次贴出流程图.以备參考: 作为CI框架的入口文件.源代码阅读,自然由此開始. 在源代码阅读的 ...

  4. CI框架源码阅读笔记6 扩展钩子 Hook.php

    CI框架允许你在不修改系统核心代码的基础上添加或者更改系统的核心功能(如重写缓存.输出等).例如,在系统开启hook的条件下(config.php中$config['enable_hooks'] = ...

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

    原文见这里:http://www.cnblogs.com/ohmygirl/p/CIRead-7.html 一个灵活可控的应用程序中,必定会存在大量的可控參数(我们称为配置),比如在CI的主配置文件里 ...

  6. Mongodb源代码阅读笔记:Journal机制

    Mongodb源代码阅读笔记:Journal机制 Mongodb源代码阅读笔记:Journal机制 涉及的文件 一些说明 PREPLOGBUFFER WRITETOJOURNAL WRITETODAT ...

  7. Spark源代码阅读笔记之DiskStore

    Spark源代码阅读笔记之DiskStore BlockManager底层通过BlockStore来对数据进行实际的存储.BlockStore是一个抽象类,有三种实现:DiskStore(磁盘级别的持 ...

  8. 框架Thinkphp5 简单的实现行为 钩子 Hook

    这篇文章主要介绍了关于框架Thinkphp5 简单的实现行为 钩子 Hook,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 实现在一个方法开始和结束加入两个行为:api_init.ap ...

  9. Java Jdk1.8 HashMap源代码阅读笔记二

    三.源代码阅读 3.元素包括containsKey(Object key) /** * Returns <tt>true</tt> if this map contains a ...

随机推荐

  1. http --- 从输入URL到页面加载的过程发生了什么?

    可以分为这几个大的过程: DNS解析 TCP连接 客户端发送HTTP请求 服务器处理请求并返回HTTP报文 浏览器解析渲染页面 结束 其中(1)DNS解析可以理解为主寻找这个IP地址的过程,其中如果找 ...

  2. kotlin官方文档-1.0入门

    什么是Kotlin?   图片发自简书App Kotlin是JetBrains开发的基于JVM的语言,JetBrains想必大家应该很熟悉了,他们创造了很多强大的IDE,android studio谷 ...

  3. SharePoint UserProfileService 接口列表 注解

    Remove Leader 除去领袖 Add Leader  加领袖 Get leaders  获得管理员 Get Profile Scherna Get Profile Scherna Names ...

  4. birthday

    2.29 7.25  7.... 5... 10.01 02 03

  5. <Sicily>Threecolor problem

    一.题目描述 有红黄蓝3种颜色的n个珠子,师傅希望悟空把它们排成红色珠子在左,黄色珠子居中,蓝色珠子在右的一行,然后告诉师傅,从左数起,第m个珠子是什么颜色.众所周知,悟空是只猴子,他没有这个耐心,你 ...

  6. phpstorm10安装并汉化

    一.下载phpstorm 下载地址:https://pan.baidu.com/s/1R64ZROVP1ljGbYfCwWjwxA 二.一直点击下一步安装即可 注意:第3步的时候选择一下支持的后缀 三 ...

  7. lhgDialog使用--loading提示(不自动关闭)

    使用lhgDialog时,发现有一个$.dialog.tips()方法可以实现loading样式的提示,但是存在默认关闭时间.方法如下图所示, 为了实现不自动关闭的方法,查看了相应的源码后,实现不关闭 ...

  8. 【Henu ACM Round#20 C】 Eevee

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 处理处所有的字符串可能的样子. 存在map里面就好. [代码] #include <bits/stdc++.h> usi ...

  9. HDU 3277 Marriage Match III

    Marriage Match III Time Limit: 4000ms Memory Limit: 32768KB This problem will be judged on HDU. Orig ...

  10. SpringMvc 系统启动时加载数据到内存中

    SpringMvc 系统启动时加载数据到内存中 学习了:http://blog.csdn.net/newstruts/article/details/18668269 https://www.cnbl ...