Thinkphp钩子Hook的工作笔记

之前写到TP3.1的行为扩展是tag();在TP3.2中引入了另一种说法—:钩子。

我们来看一下TP3.2中的钩子这个东西:

一:文件流程:

1:/index.php ->require './ThinkPHP/ThinkPHP.php';

2:/ThinkPHP/ThinkPHP.php—->require CORE_PATH.'Think'.EXT; Think\Think::start();

3:/ThinkPHP/Library/Think/Think.class.php—–>App::run();

4:/ThinkPHP/Library/Think/App.class.php 。到这里基本流程就走完了,(这里不说细节);

首先,我们要明确一些说法。在TP中,设置陷阱的过程称为##绑定事件##,而某个事件触发的功能函数称为##行为##。

钩子应该具有的基本方法应该有:

设置钩子(导入钩子)
触发事件
执行行为
首先我们看看TP是怎么写的,源代码位于ThinkPHP/Library/Think/Hook.class.php,Hook类中全是静态方法,其中有唯一静态属性$tags,他是一个数组,键为绑定的事件,值为绑定的行为。

其中有两个方法可以用于绑定,前者是单个,后者是是批量。

    static public function add($tag,$name) {
        echo $tag;
        echo "\n";
        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;
        }
    }

    static public function import($data,$recursive=true) {
        if(!$recursive){ // 覆盖导入
            self::$tags   =   array_merge(self::$tags,$data);
        }else{ // 合并导入
            foreach ($data as $tag=>$val){
                if(!isset(self::$tags[$tag]))
                    self::$tags[$tag]   =   array();           
                if(!empty($val['_overlay'])){
                    // 可以针对某个标签指定覆盖模式
                    unset($val['_overlay']);
                    self::$tags[$tag]   =   $val;
                }else{
                    // 合并模式
                    self::$tags[$tag]   =   array_merge(self::$tags[$tag],$val);
                }
            }           
        }
    }
当系统触发了某个事件,比如app_start事件,TP会找到Hook::listen方法,该方法会查找$tags中有没有绑定app_start事件的方法,然后用foreach遍历$tags属性,并执行Hook:exec方法。

    static public function listen($tag, &$params=NULL) {
        if(isset(self::$tags[$tag])) {
            if(APP_DEBUG) {
                G($tag.'Start');
                trace('[ '.$tag.' ] --START--','','INFO');
            }
            foreach (self::$tags[$tag] as $name) {
                APP_DEBUG && G($name.'_start');
                $result =   self::exec($name, $tag,$params);
                if(APP_DEBUG){
                    G($name.'_end');
                    trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                }
                if(false === $result) {
                    // 如果返回false 则中断插件执行
                    return ;
                }
            }
            if(APP_DEBUG) { // 记录行为的执行日志
                trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
            }
        }
        return;
    }
Hook:exec方法会检查行为名称,如果包含Behavior关键字,那么入口方法必须为run方法,而执行run方法的参数在调用Hook::listen时指定。但如果不用##Behavior##关键字做配置,即将系统默认的ReadHtmlCacheBehavior改为ReadHtml,系统会报错吗?答案是会的!

如果去掉Behavior,系统就会找该类中绑定事件名称的方法,即app_begin。这样的好处是,不会强制使用run方法,一个行为可以复用了。

    static public function exec($name, $tag,&$params=NULL) {
        if('Behavior' == substr($name,-8) ){
            // 行为扩展必须用run入口方法
            $tag    =   'run';
        }
        $addon   = new $name();
        return $addon->$tag($params);
    }

补充:

Thinkphp提供了一个钩子机制,Thinkphp提供了一些载点,可以让用户在相应的时机处理一些用户自己的扩展。 例如app_begin,action_begin,view_begin等。 Hook的配置写在tags.php中

return array(
    'app_begin' => array(
        'Behavior\CheckLangBehavior'
    ),
    'action_begin' => array(
        'Home\Behaviors\LocateBehavior',
        'Home\Behaviors\TokenBehavior',
        'Home\Behaviors\CookieBehavior'
    ),
    'view_begin' => array(
        'Home\Behaviors\SeoBehavior',
        'Home\Behaviors\ParseBehavior'
    ),
    'view_end'=>array(
        'Home\Behaviors\SlowLogBehavior',
    ),
);
例如上面这个配置文件使用了4个载点,分别在下面挂上了自己的behavior扩展。 这样thinkphp在执行到加载控制器方法action_begin时,就会先执行用户自己定义的三个扩展

‘Home\Behaviors\LocateBehavior’, ‘Home\Behaviors\TokenBehavior’, ‘Home\Behaviors\CookieBehavior’

下面让我们来详细看看Hook执行的流程。

首先,在Think.class.php中Think::start()方法中

          // 加载应用行为定义
          if(is_file(CONF_PATH.'tags.php'))
          // 允许应用增加开发模式配置定义
          Hook::import(include CONF_PATH.'tags.php');  
thinkPHP加载了tags.php配置文件,从中读取用户的载点配置。通过Hook:import加载。

其次,在App.class.php中,App:run()方法中,Thinkphp监听了app_init,app_begin,app_end三个载点。

   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通过Hook::listen这个方法来监听载点。我们来看看这个方法是怎么写的。

static public function listen($tag, &$params=NULL) {
        if(isset(self::$tags[$tag])) {
            if(APP_DEBUG) {
                G($tag.'Start');
                trace('[ '.$tag.' ] --START--','','INFO');
            }
            foreach (self::$tags[$tag] as $name) {
                APP_DEBUG && G($name.'_start');
                $result =   self::exec($name, $tag,$params);
                if(APP_DEBUG){
                    G($name.'_end');
                    trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                }
                if(false === $result) {
                    // 如果返回false 则中断插件执行
                    return ;
                }
            }
            if(APP_DEBUG) { // 记录行为的执行日志
                trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
            }
        }
        return;
    }
$tags是通过Hook::import()加载进来的用户载点配置 Hook通过循环配置项来检测载点下是否有用户的扩展需要执行,通过self::exec($name, $tag,$params);来执行。 需要注意一下Hook::exec()这个方法。

static public function exec($name, $tag,&$params=NULL) {
        if('Behavior' == substr($name,-8) ){
            // 行为扩展必须用run入口方法
            $tag    =   'run';
        }
        $addon   = new $name();
        return $addon->$tag($params);
    }
这里规定了如果用户加载的是行为扩展,即以Behevior结尾,必须用run方法作为入口。

下面我们再来看看其他的载点又是如何定义的

View.class.php中的View::display()方法中定义了view_begin,view_end两个载点。

public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
    G('viewStartTime');
    // 视图开始标签
    Hook::listen('view_begin',$templateFile);
    // 解析并获取模板内容
    $content = $this->fetch($templateFile,$content,$prefix);
    // 输出模板内容
    $this->render($content,$charset,$contentType);
    // 视图结束标签
    Hook::listen('view_end');
}
View::fetch()中定义了view_parse与view_filter载点。

public function fetch($templateFile='',$content='',$prefix='') {
    if(empty($content)) {
        $templateFile   =   $this->parseTemplate($templateFile);
        // 模板文件不存在直接返回
        if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);
    }
    // 页面缓存
    ob_start();
    ob_implicit_flush(0);
    if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
        $_content   =   $content;
        // 模板阵列变量分解成为独立变量
        extract($this->tVar, EXTR_OVERWRITE);
        // 直接载入PHP模板
        empty($_content)?include $templateFile:eval('?>'.$_content);
    }else{
        // 视图解析标签
        $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
        Hook::listen('view_parse',$params);
    }
    // 获取并清空缓存
    $content = ob_get_clean();
    // 内容过滤标签
    Hook::listen('view_filter',$content);
    // 输出模板文件
    return $content;
}
Controller.class.php的构造方法中监听了action_begin,析构函数中监听了action_end。

public function __construct() {
        Hook::listen('action_begin',$this->config);
        //实例化视图类
        $this->view     = Think::instance('Think\View');
        //控制器初始化
        if(method_exists($this,'_initialize'))
            $this->_initialize();
    }
ajaxReturn方法中监听了ajax_return。

Controller方法中我们可以看到thinkphp是如何在控制器中加入assign方法的。

  $this->view     = Think::instance('Think\View');
  protected function assign($name,$value='') {
        $this->view->assign($name,$value);
        return $this;
  }
同样的方法我们也可以在我们的behavior扩展中加入assign方法。

时间: 2024-09-26 20:31:41

Thinkphp钩子Hook的工作笔记的相关文章

解析WordPress中函数钩子hook的作用及基本用法_php技巧

WordPress 的插件机制实际上只的就是这个 Hook 了,它中文被翻译成钩子,允许你参与 WordPress 核心的运行,是一个非常棒的东西,下面我们来详细了解一下它.钩子分类 钩子分为两种,一种叫做动作(action),还有一种叫做过滤器(filter).这两种钩子实现原理基本一样,后边会讲到,使用上的区别在于过滤器有返回值,而动作没有. 动作的理念是让你在一种情况或者一个特别的位置执行一些函数,比如发送一封邮件等:过滤器则是让你修改 WordPress 核心需要用到的一个值,然后 Wo

《Oracle DBA工作笔记》第二章 常用工具和问题分析

<Oracle DBA工作笔记>第二章 常用工具和问题分析   一.1  BLOG文档结构图     一.2  本文简介 建荣的新书<Oracle DBA工作笔记>第二章的目录如下图,主要讲解了SQL*Plus.exp/imp.expdp/impdp以及常见的问题分析,第二章的目录如下:     下边小麦苗将自己阅读完第二章后整理的一些内容分享给大家. 一.3  第一章内容修改 一.3.1  删除数据库的几种方式 这个内容是第一章(http://blog.itpub.net/267

《Oracle DBA工作笔记》第一章

<Oracle DBA工作笔记>第一章 BLOG文档结构图I 本文简介 建荣的新书<Oracle DBA工作笔记>拿到手了,下午离下班还有1个小时的时候有空了,就阅读了下新书的第一章内容,第一章的目录如下图,主要讲解了下数据库的安装和配置,阅读完第一章的内容还是收获不小的,于是按照小麦苗的读书习惯,趁热打铁将自己还不知道或者说还不太了解的内容整理一下,以免遗忘,好记性不如烂笔头. 第一章内容 "欲事之无繁,则必劳于始而逸于终" 这句话是苏轼的<决壅蔽>

[Delphi]钩子(HOOK)机制的使用

                                                  [Delphi]钩子(HOOK)机制的使用     作者:e梦缘   来源:CSND  SetwindowsHookEx函数提供15种不同的消息监视类型,也就是15中不同的钩子. 分别用于捕获某一特定类型或某一范围的消息(如:键盘消息,鼠标消息等). 我们这里仅以鼠标钩子的使用为例,讨论在DELPHI下怎样编写DLL程序和怎样在自己的程序中安装使用鼠标钩子函数. Windows提供API函数Set

用钩子(hook)实现C#的屏幕键盘效果

要实现一个屏幕键盘,需要监听所有键盘事件,无论窗体是否被激活.因此需要一个全局的钩子,也就是系统范围的钩子. 什么是钩子(Hook) 钩子(Hook)是windows提供的一种消息处理机制平台,是指在程序正常运行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息,(钩子)实际上是一个处理消息的程序段,通过系统调用,把它挂入系统.每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权.这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该

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

第一部分:API函数简介 1.       SetWindowsHookEx函数 函数原型 HHOOK SetWindowsHookEx(   int idHook,        // hook type   HOOKPROC lpfn,     // hook procedure   HINSTANCE hMod,    // handle to application instance   DWORD dwThreadId   // thread identifier ); 函数功能:该函

用键盘全局钩子Hook监视多进程键盘操作

闲来无事,在WIN2K下用BCB5做了个键盘挂钩小程序,监视全局按键情况.Hook安放和回调函数放在一个单独DLL中,DLL原码如下: //----------------------------------------------------------------------------------------------------extern "C" __declspec(dllexport) void __stdcall SetHook(HWND,bool);LRESULT

Php中钩子(Hook)的应用例子

我们先来回顾下原本的开发流程: 产品汪搞出了一堆需求: 当用户注册成功后需要发送短信.发送邮件等等: 然后聪明机智勇敢的程序猿们就一扑而上: 把这些需求转换成代码扔在 用户注册成功 和 跳转到首页 之间: 没有什么能够阻挡:充满创造力的猿们: <?php  class Test{  public function index(){  // 用户注册成功  /* 此处是一堆发送短信的代码 */  /* 此处是一堆发送邮件的代码 */  /* 此处是一堆其他功能的代码 */  // 前往网站首页  

关于文件钩子(hook)

问题描述 有个程序不断往一个固定的文本文件中写入信息,频率很高,只想截取每次写入信息中的一部分,大家推荐什么方法,我的初步想法是用文件钩子,但是我写过键盘钩子,不知道文件钩子应该怎么写,涉及到哪些函数.大家给点建议.不能用文件监控发现变化在读取,因为写入频率很高. 解决方案 解决方案二:那你直接读文件不行吗,要不就勾住写文件的api函数,自己查C#api钩子解决方案三:该回复于2011-12-12 16:29:00被版主删除解决方案四:引用1楼bdmh的回复: 那你直接读文件不行吗,要不就勾住写