0

0

懂得钩子Hook以及在Thinkphp下利用钩子使用行为扩展

php中文网

php中文网

发布时间:2016-06-13 12:23:15

|

914人浏览过

|

来源于php中文网

原创

理解钩子Hook以及在Thinkphp下利用钩子使用行为扩展

什么是钩子函数

个人理解:钩子就像一个”陷阱”、”监听器”,当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中利用钩子,其实就是行为扩展

立即学习PHP免费学习笔记(深入)”;

行为

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

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

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

添加行为扩展

Thinkphp中有系统的行为扩展,在LibThinkApp.class.php中,有这个函数

  <span class="hljs-javadoc">/**     * 运行应用实例 入口文件使用的快捷方法     *<span class="hljs-javadoctag"> @access</span> public     *<span class="hljs-javadoctag"> @return</span> void     */</span>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">public</span> function <span class="hljs-title">run</span>() {        <span class="hljs-comment">// 应用初始化标签</span>        Hook::listen(<span class="hljs-string">'app_init'</span>);        App::init();        <span class="hljs-comment">// 应用开始标签</span>        Hook::listen(<span class="hljs-string">'app_begin'</span>);        <span class="hljs-comment">// Session初始化</span>        <span class="hljs-keyword">if</span>(!IS_CLI){            session(C(<span class="hljs-string">'SESSION_OPTIONS'</span>));        }        <span class="hljs-comment">// 记录应用初始化时间</span>        G(<span class="hljs-string">'initTime'</span>);        App::exec();        <span class="hljs-comment">// 应用结束标签</span>        Hook::listen(<span class="hljs-string">'app_end'</span>);        <span class="hljs-keyword">return</span> ;    }

其中里面的Hook::listen(‘app_init’),Hook::listen(‘app_begin’); 相当于钩子监听的这些tags(这几个tags是系统内置的行为,无需再注册)。要触发自定义行为,必须先注册行为,ThinkPHP中提供了自动注册和手动注册 。

自动注册

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

         <span class="hljs-comment">// 加载模式行为定义</span>          <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$mode</span>[<span class="hljs-string">'tags'</span>])) {              Hook::import(is_array(<span class="hljs-variable">$mode</span>[<span class="hljs-string">'tags'</span>])?<span class="hljs-variable">$mode</span>[<span class="hljs-string">'tags'</span>]:<span class="hljs-keyword">include</span> <span class="hljs-variable">$mode</span>[<span class="hljs-string">'tags'</span>]);          }          <span class="hljs-comment">// 加载应用行为定义</span>          <span class="hljs-keyword">if</span>(is_file(CONF_PATH.<span class="hljs-string">'tags.php'</span>))              <span class="hljs-comment">// 允许应用增加开发模式配置定义</span>              Hook::import(<span class="hljs-keyword">include</span> CONF_PATH.<span class="hljs-string">'tags.php'</span>);   

模式行为是系统内置的,我们可以在LibModecommon.php找到

Cardify卡片工坊
Cardify卡片工坊

使用Markdown一键生成精美的小红书知识卡片

下载
 <span class="hljs-comment">// 行为扩展定义</span>    <span class="hljs-string">'tags'</span>  =>  <span class="hljs-keyword">array</span>(        <span class="hljs-string">'app_init'</span>     =>  <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorBuildLiteBehavior'</span>, <span class="hljs-comment">// 生成运行Lite文件</span>        ),                <span class="hljs-string">'app_begin'</span>     =>  <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorReadHtmlCacheBehavior'</span>, <span class="hljs-comment">// 读取静态缓存</span>        ),        <span class="hljs-string">'app_end'</span>       =>  <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorShowPageTraceBehavior'</span>, <span class="hljs-comment">// 页面Trace显示</span>        ),        <span class="hljs-string">'view_parse'</span>    =>  <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorParseTemplateBehavior'</span>, <span class="hljs-comment">// 模板解析 支持PHP、内置模板引擎和第三方模板引擎</span>        ),        <span class="hljs-string">'template_filter'</span>=> <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorContentReplaceBehavior'</span>, <span class="hljs-comment">// 模板输出替换</span>        ),        <span class="hljs-string">'view_filter'</span>   =>  <span class="hljs-keyword">array</span>(            <span class="hljs-string">'BehaviorWriteHtmlCacheBehavior'</span>, <span class="hljs-comment">// 写入静态缓存</span>        ),    ),

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

<span class="php"><span class="hljs-preprocessor"><?php</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">array</span>(    <span class="hljs-string">"action_begin"</span> => <span class="hljs-keyword">array</span>(<span class="hljs-string">'Home\Behaviors\testBehavior'</span>)    <span class="hljs-comment">//一个标签可以有多个行为,我们也可以这样</span>    <span class="hljs-string">"action_begin"</span> => <span class="hljs-keyword">array</span>(<span class="hljs-string">'HomeBehaviors	est1Behavior'</span>,<span class="hljs-string">'Home\Behaviors\test2Behavior'</span>)); <span class="hljs-preprocessor">?></span></span>

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

手动注册

说到这,我们还是先看下LibThinkHook.class.php的代码

    <span class="hljs-comment">/**     * 动态添加插件到某个标签     *<span class="hljs-phpdoc"> @param</span> string $tag 标签名称     *<span class="hljs-phpdoc"> @param</span> mixed $name 插件名称     *<span class="hljs-phpdoc"> @return</span> void     */</span>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-variable">$tag</span>,<span class="hljs-variable">$name</span>)</span> {</span>        <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">self</span>::<span class="hljs-variable">$tags</span>[<span class="hljs-variable">$tag</span>])){            <span class="hljs-keyword">self</span>::<span class="hljs-variable">$tags</span>[<span class="hljs-variable">$tag</span>]   =   <span class="hljs-keyword">array</span>();        }        <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$name</span>)){            <span class="hljs-keyword">self</span>::<span class="hljs-variable">$tags</span>[<span class="hljs-variable">$tag</span>]   =   array_merge(<span class="hljs-keyword">self</span>::<span class="hljs-variable">$tags</span>[<span class="hljs-variable">$tag</span>],<span class="hljs-variable">$name</span>);        }<span class="hljs-keyword">else</span>{            <span class="hljs-keyword">self</span>::<span class="hljs-variable">$tags</span>[<span class="hljs-variable">$tag</span>][] =   <span class="hljs-variable">$name</span>;        }    }

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

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

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

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

其他事项:

  • 触发行为的关键方法是Hook类中的listen方法,它通过遍历某个行为标签下的所有行为,依次实例化并调用run方法
  • listen方法中,如果之前在配置文件中开启了DEBUG模式,则它会生成日志记录你的行为,这里面牵涉到很多的IO操作,所以你的项目完成时建议取消DEBUG模式以提升速度
  • listen方法中,允许传递参数且只允许传递一个参数(传多个可以用数组呢),不过这个参数是引用传值,所以只能传入变量,传入常量会报错
  • 最后,LibThinkBehavior.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里面的初始化方法

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BaseController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span>{</span>     <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_initialize</span><span class="hljs-params">()</span>     {</span>         <span class="hljs-comment">//记录访问</span>         <span class="hljs-comment">//这里我是手动注册的行为</span>         Hook::add(<span class="hljs-string">'mark_pv'</span>,<span class="hljs-string">'HomeBehaviors	estBehavior'</span>);         hook::listen(<span class="hljs-string">'mark_pv'</span>);        }}

然后我再HomeBehaviors estBehavior.class.php中

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">testBehavior</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Behavior</span> {</span>    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span><span class="hljs-params">(&<span class="hljs-variable">$param</span>)</span>    {</span>        <span class="hljs-comment">//记录数据</span>        <span class="hljs-variable">$data</span>[<span class="hljs-string">'client_ip'</span>] = get_client_ip();        <span class="hljs-variable">$data</span>[<span class="hljs-string">'page_view'</span>] = CONTROLLER_NAME.<span class="hljs-string">'/'</span>.ACTION_NAME;        <span class="hljs-variable">$data</span>[<span class="hljs-string">'create_time'</span>] = time();        <span class="hljs-variable">$data</span>[<span class="hljs-string">'status'</span>] = <span class="hljs-number">0</span>;        M(<span class="hljs-string">'page_view'</span>)->add(<span class="hljs-variable">$data</span>);    }}

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

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

89

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

276

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

59

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

99

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

105

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

230

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

619

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

173

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
前端系列快速入门课程
前端系列快速入门课程

共4课时 | 0.4万人学习

react hooks实战移动端企业级项目
react hooks实战移动端企业级项目

共59课时 | 6.6万人学习

PHP函数之array数组函数视频讲解
PHP函数之array数组函数视频讲解

共76课时 | 26.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号