什么是钩子函数

个人理解:钩子就像一个”陷阱”、”监听器”,当A发送一个消息到B时,当消息还未到达目的地B时,被钩子拦截调出一部分代码做处理,这部分代码也叫钩子函数或者回调函数

参考网上说法 
譬如我们用鼠标在某个窗口上双击了一次, 或者给某个窗口输入了一个字母 A; 
首先发现这些事件的不是窗口, 而是系统!

然后系统告诉窗口: 喂! 你让人点了, 并且是连续点了两鼠标, 你准备怎么办? 
或者是系统告诉窗口: 喂! 有人向你家里扔砖头了, 不信你看看, 那块砖头是 A.

这时窗口的对有些事件会忽略、对有些事件会做出反应: 
譬如, 可能对鼠标单击事件忽略, 窗口想: 你单击我不要紧, 累死你我不负责; 
但一旦谁要双击我, 我会马上行动, 给你点颜色瞧瞧! 
这里窗口准备要采取的行动, 就是我们提前写好的事件. 
用 Windows 的话说, 窗口的事件就是系统发送给窗口的消息; 窗口要采取的行动(事件代码)就是窗口>的回调函数.

但是! 往往隔墙有耳. 系统要通知给窗口的”话”(消息), 可能会被另一个家伙(譬如是一个贼)提前听>到! 
有可能这个贼就是专门在这等情报的, 贼知道后, 往往在窗口知道以前就采取了行动! 
并且这个贼对不同的消息会采取不同的行动方案, 它的行动方案一般也是早就准备好的; 
当然这个贼也不是对什么消息都感兴趣, 对不感兴趣的消息也就无须制定相应的行动方案.

总结: 这个”贼”就是我们要设置的钩子; “贼”的”行动方案”就是钩子函数, 或者叫钩子的回调函数.

上面一段话原文链接 http://www.cnblogs.com/del/archive/2008/02/25/1080825.html

总的来说,钩子就像一个”挂载点”挂到函数上,当函数执行过程中遇到这个”挂载点”,挂载点(钩子)就会将一块代码拉出来。比如,我们向脚本中传入了几个参数,然后进行插入操作,插入之前我们有一个钩子(before_insert)正在listen,当函数执行到这个钩子时,就会去通过钩子去调里面的一块代码,我们在这部分钩子函数里面可以对数据进行验证或者加工等,达到你想要的操作

为什么使用钩子

既然钩子是一个监听器,通过钩子来调用一部分代码做处理,那我直接在脚本里面另写一个函数A然后在函数B执行过程中去调用A不就好了?

个人认为:钩子函数相对于直接在函数中调用另外一个函数来说,更加安全方便。当我们需要修改扩展功能时,我们无需修改函数B中的钩子,只需要修改钩子里面的代码块即可,而如果直接修改函数A,则会对函数B所在类进行频繁修改。违背了封闭原则。另一点,利用钩子对后期的维护和功能扩展更加方便。

Thinkphp中使用钩子(行为扩展)

在TP中利用钩子,其实就是行为扩展

行为

行为(Behavior)是一个比较抽象的概念,你可以想象成在应用执行过程中的一个动作或者处理,在框架的执行流程中,各个位置都可以有行为产生,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,行为的存在让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。

而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为标签(位)(tag),当应用程序运行到这个标签的时候,就会被拦截下来,统一执行相关的行为

这里的行为相当于钩子函数,想要”使用”它,我们就必须借助钩子Hook

添加行为扩展

Thinkphp中有系统的行为扩展,在Lib\Think\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_init’),Hook::listen(‘app_begin’); 相当于钩子监听的这些tags(这几个tags是系统内置的行为,无需再注册)。要触发自定义行为,必须先注册行为,ThinkPHP中提供了自动注册和手动注册 。

自动注册

TP里面Lib\Think目录下面Think.class.php中有这两行代码

         // 加载模式行为定义
if(isset($mode['tags'])) {
Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']);
}
// 加载应用行为定义
if(is_file(CONF_PATH.'tags.php'))
// 允许应用增加开发模式配置定义
Hook::import(include CONF_PATH.'tags.php');

模式行为是系统内置的,我们可以在Lib\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', // 写入静态缓存
),
),

用户自定义的行为扩展,需要在CONF_PATH.’tags.php’也就是/Common/conf/tags.php自行创建,举例

<?php
return array(
"action_begin" => array('Home\\Behaviors\\testBehavior')
//一个标签可以有多个行为,我们也可以这样
"action_begin" => array('Home\Behaviors\test1Behavior','Home\\Behaviors\\test2Behavior')
);
?>

TP在运行时为自动加载这些行为。我们只需写好自己的行为扩展,然后在某个地方监听(Hook::listen(tags)),就可以触发这些行为了

手动注册

说到这,我们还是先看下Lib\Think\Hook.class.php的代码

    /**
* 动态添加插件到某个标签
* @param string $tag 标签名称
* @param mixed $name 插件名称
* @return void
*/
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;
}
}

代码十分简单,我们注册行为只需要Hook::add(tags,name)

注意:这里面name必须是一个包含命名空间路径的类,比如Home\Behavior\testBehavior ,类名后面必须是Behavior结尾,类中必须实现run方法。原因:请看Hook类中代码

  /**
* 执行某个插件
* @param string $name 插件名称
* @param string $tag 方法名
* @param Mixed $params 传入的参数
*/
static public function exec($name, $tag,&$params=NULL) {
//这里截取后八位类名字符串,所以必须是Behavior
if('Behavior' == substr($name,-8) ){
// if('testBehavior' == substr($name,-12)
// {
// $tag = 'test';
// }
// 行为扩展必须用run入口方法
$tag = 'run';
}
echo $name.'<br/>';
$addon = new $name();
return $addon->$tag($params);
}

当然,你也可以修改规则,比如我不想以Behavior为类名结尾,也不想调用run方法,这时候想修改只能在listen()方法里面进行判断修改,如果直接在exec()里面修改,立马报错,因为系统的内置行为都是按这个规则来的啊!除非你把源码中的行为类名和行为方法run改掉,当然我相信你不会这么傻

其他事项:

  • 触发行为的关键方法是Hook类中的listen方法,它通过遍历某个行为标签下的所有行为,依次实例化并调用run方法
  • listen方法中,如果之前在配置文件中开启了DEBUG模式,则它会生成日志记录你的行为,这里面牵涉到很多的IO操作,所以你的项目完成时建议取消DEBUG模式以提升速度
  • listen方法中,允许传递参数且只允许传递一个参数(传多个可以用数组呢),不过这个参数是引用传值,所以只能传入变量,传入常量会报错
  • 最后,Lib\Think\Behavior.class.php,这个抽象类中只有一个抽象方法run(),在你的自己行为扩展中建议继承它,尽管这不是必须的,但是这样更加规范。。。不过TP内置的系统行为都没继承这个抽象类,也不知道闹哪样

流程与举例

使用钩子触发行为扩展的流程:

  1. 自动注册(Common/Conf/tags.php按格式自己添加),或者 手动注册(类中方法如初始方法,调用Hook::add(tags,name));
  2. 写好自己的行为类,类名以Behavior结尾,实现run方法
  3. 在需要添加行为的函数里 ,直接Hook::Listen(tags,prarm),注意一定要传变量,不需要传常量。

这样,整个过程就结束了,下面举个例子

举例

鄙人最近写了个人网站(小博客) http://www.huangtianer.com/ 
我需要记录一下网站的PV,于是我在BaseController里面的初始化方法

class BaseController extends Controller{
public function _initialize()
{
//记录访问
//这里我是手动注册的行为
Hook::add('mark_pv','Home\Behaviors\testBehavior');
hook::listen('mark_pv');
}
}

然后我再Home\Behaviors\testBehavior.class.php中

class testBehavior extends Behavior {

    public function run(&$param)
{
//记录数据
$data['client_ip'] = get_client_ip();
$data['page_view'] = CONTROLLER_NAME.'/'.ACTION_NAME;
$data['create_time'] = time();
$data['status'] = 0;
M('page_view')->add($data);
}
}

运行后正常插入数据,而且如果后期我的个人网站数据库由于访问量太大(呵呵)顶不住压力,不能再记录PV了,我直接删除钩子函数里面的代码,不需要动钩子,就解决了问题,比较方便。

结束语:如有BUG或者建议,欢迎指正

理解钩子Hook以及在Thinkphp下利用钩子使用行为扩展的更多相关文章

  1. 闭包传参 余额计算 钩子hook 闭包中的this JavaScript 钩子

    闭包传参  余额计算    钩子hook 小程序 a=function(e){console.log(this)}() a=function(e){console.log(this)}() VM289 ...

  2. C# 钩子HOOK专题(1)

    目录   基本概念 运行机制 钩子类型 作者 基本概念   钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程 ...

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

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

  4. 利用钩子函数来捕捉键盘响应的windows应用程序

    一:引言: 你也许一直对金山词霸的屏幕抓词的实现原理感到困惑,你也许希望将你的键盘,鼠标的活动适时的记录下来,甚至你想知道木马在windows操作系统是怎样进行木马dll的加载的…..其实这些都是用到 ...

  5. MFC线程钩子和全局钩子[HOOK DLL]

    第一部分:API函数简介 1.       SetWindowsHookEx函数 函数原型 HHOOK SetWindowsHookEx( int idHook,        // hook typ ...

  6. 解析android framework下利用app_process来调用java写的命令及示例

    解析android framework下利用app_process来调用java写的命令及示例 在android SDK的framework/base/cmds目录下了,有不少目录,这些目的最终都是b ...

  7. 屏蔽系统热键钩子Hook程序

    在winform时候,经常需要做屏蔽系统热键: 1.屏蔽左"WIN".右"Win" 2.屏蔽Ctrl+Esc 3.屏蔽Alt+f4 4.屏蔽Alt+Esc 5. ...

  8. thinkphp下通过页面链接传递的参数获取一次后失效

    在thinkphp下通过页面链接传递的参数获取一次后失效,ajax内部无法再次使用.想要使用必须再次用js获取其值,通过ajax传递给后台使用. 1.通过页面链接传递参数给下一页 2.可以再下一页后台 ...

  9. 钩子(hook)编程

    一.钩子介绍 1.1钩子的实现机制 钩子英文名叫Hook,是一种截获windows系统中某应用程序或者所有进程的消息的一种技术.下图是windows应用程序传递消息的过程: 如在键盘中按下一键,操作系 ...

随机推荐

  1. 攻城狮在路上(叁)Linux(十四)--- 查阅文件内容

    常用命令:cat.tac.nl.more.less.head.tail.od... 一.直接查看文件内容:cat.tac.nl <==一次性全部读取 1.cat [-AbEnTv] 文件名 参数 ...

  2. UDP穿透NAT原理解析

    转自:http://www.2cto.com/net/201201/116793.html NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益 ...

  3. uint8_t / uint16_t / uint32_t /uint64_t 是什么数据类型 - 大总结,看完全明白了

    转自:http://blog.csdn.net/kiddy19850221/article/details/6655066 uint8_t / uint16_t / uint32_t /uint64_ ...

  4. [荐]javascript Date format(js日期格式化)

    cnblog:http://www.cnblogs.com/zhangpengshou/archive/2012/07/19/2599053.html 方法一: // 对Date的扩展,将 Date  ...

  5. Error: Could not find or load main class test.EditFile

    今天写了一个简单的小程序,运行之后发现Error: Could not find or load main class test.EditFile,项目无法启动.删除main中的所有内容之后依旧提示该 ...

  6. 数字信号处理实验(六)——FIR滤波器的设计

    一.四种线性相位FIR滤波器的振幅响应 1.自编函数 [Hr,w,a,L]=-n) [Hr,w,a,L]=-n) [Hr,w,a,L]=-n) [Hr,w,a,L]=-n) 2.一个demo clea ...

  7. 模仿QQ左滑删除

    需求: 1.左滑删除 2.向左滑动距离超过一半的时候让它自动滑开,向右滑动超过一半的时候自动隐藏 3.一次只允许滑开一个item 还有,根本不需要自定义view来实现,谨防入坑 布局: <?xm ...

  8. 【jackson 异常】com.fasterxml.jackson.databind.JsonMappingException异常处理

    项目中,父层是Gene.java[基因实体]  子层是Corlib.java[文集库实体],一种基因对用多个文集库文章 但是在查询文集库这个实体的时候报错:[com.fasterxml.jackson ...

  9. LR检查点的if (status == LR_FAIL)写法

    C语言例子:登录失败返回一个信息 int status = web_url("Login", "URL=https://secure.computing.com//log ...

  10. css与js后边有?v=20160101

    原文地址http://blog.csdn.net/zanychou/article/details/8813076 <span style="font-size:14px;" ...