设为首页 收藏本站
查看: 1018|回复: 0

[经验分享] CI框架源码阅读笔记6 扩展钩子 Hook.php

[复制链接]

尚未签到

发表于 2015-8-23 14:25:13 | 显示全部楼层 |阅读模式
    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组件完成的,先看该组件的类图:
   DSC0000.jpg
  其中:
    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
*
* @accessprivate
* @returnvoid
*/
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
*
* @accessprivate
* @paramstringthe hook name
* @returnmixed
*/
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
*
* @accessprivate
* @paramarraythe hook details
* @returnbool
*/
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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-103033-1-1.html 上篇帖子: PHP 代码规范 下篇帖子: PHP开发环境的配置
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表