美文网首页
TP的钩子

TP的钩子

作者: imjcw | 来源:发表于2019-07-11 16:59 被阅读0次

这里是用 thinkphp 3.2.0 版本的框架来做分析的

TP 中的钩子类叫 Hook,它在 TP 的整个流程中还是蛮常见的。

文件地址:ThinkPHP/Library/Think/Hook.class.php

系统默认有 12 个钩子。

app_init
app_begin
app_end
path_info
action_begin
action_end
view_begin
view_parse
template_filter
view_filter
view_end
url_dispatch

我们在这里不讨论钩子到底在 TP 的流程中到底做了哪些事情,哪些地方埋了钩子。这个需要小伙伴们自己去查询了,了解钩子在每个环节的作用,对于框架运行整个流程的理解还是非常有帮助的,也便于我们开发和扩展框架。

在这些钩子里,url_dispatch 并未定义在 common.php 里。不知道是官方遗漏了还是故意留下的,反正用户可以自定义 url_dispatch 要做的事情。

导入钩子

/**
 * 批量导入插件
 * @param array $data 插件信息
 * @param boolean $recursive 是否递归合并
 * @return void
 */  
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);
            }
        }            
    }
}

这里有三种模式。

一种是直接覆盖导入,也就是 $recursive 为假时,直接使用 array_merge 合并,后者覆盖前者。

一种是指定标签覆盖导入,当某个标签内有 _overlay 且不为空时,这个标签覆盖导入。

一种是合并每个标签的内容,不做任何覆盖。

增加某个钩子里的插件

/**
 * 动态添加插件到某个标签
 * @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;
    }
}

多个插件就合并,否则新增。

获取钩子

/**
 * 获取插件信息
 * @param string $tag 插件位置 留空获取全部
 * @return array
 */
static public function get($tag='') {
    if(empty($tag)){
        // 获取全部的插件信息
        return self::$tags;
    }else{
        return self::$tags[$tag];
    }
}

不指定名称直接返回所有,否则返回指定的。

这里有个问题,就是如果这个钩子不存在...不就报错了么

执行某个钩子

/**
 * 监听标签的插件
 * @param string $tag 标签名称
 * @param mixed $params 传入参数
 * @return void
 */
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;
}

/**
 * 执行某个插件
 * @param string $name 插件名称
 * @param string $tag 方法名(标签名)     
 * @param Mixed $params 传入的参数
 * @return void
 */
static public function exec($name, $tag,&$params=NULL) {
    if(false === strpos($name,'\\')) {
        // 插件(多个入口)
        $class   =  "Addons\\{$name}\\{$name}Addon";
    }else{
        // 行为扩展(只有一个run入口方法)
        $class   =  $name.'Behavior';
        $tag    =   'run';
    }
    $addon   = new $class();
    return $addon->$tag($params);
}

大致看一下,有几点需要注意。

  • Behavior 其实是有顺序的概念的,这里没有体现,需要我们自行去排序
  • 任何一个 Behavior 执行过程,都有可能对后续的流程造成影响,因为一旦返回 false,就会中断后续的 Behavior
  • 每一个 Behavior 都必须实现 run 方法
  • 如果是 Addon(插件),需要按照官方命名空间实现,并且,方法名就是钩子的名称

举个栗子

session 在初始化之前,执行了 app_begin 钩子,这里会去读取静态文件的缓存,如果缓存设置开启,并且有缓存会直接输出,加速页面访问速度。

最后

其实想法是非常不错的,但是在实现上有所欠缺,考虑的东西相对而言少很多,在一定场景下还是可以使用的。

有想法的小伙伴,可以去了解了解事件管理器的实现。

我之前写过一个简单的,有兴趣的小伙伴可以看看,事件管理模块

相关文章

网友评论

      本文标题:TP的钩子

      本文链接:https://www.haomeiwen.com/subject/qkakfctx.html