这里是用
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
钩子,这里会去读取静态文件的缓存,如果缓存设置开启,并且有缓存会直接输出,加速页面访问速度。
最后
其实想法是非常不错的,但是在实现上有所欠缺,考虑的东西相对而言少很多,在一定场景下还是可以使用的。
有想法的小伙伴,可以去了解了解事件管理器的实现。
我之前写过一个简单的,有兴趣的小伙伴可以看看,事件管理模块。
网友评论