下面的笔记,自己再看觉得好二,但是有时候大脑就是会短路,反过来想是顺理成章,实际最开始不熟悉的时候,难免会走些之后看起来很傻很二的弯路,错路。
本人脸皮厚,分析错了的地方,也就不删了,不装高智商。
以OneThink的后台首页为例:
这里有其自定一个的标签hook
里面传了一个AdminIndex
作为参数,想必是调用了AdminIndex
的方法什么的,感觉全局搜索应该会有对应的代码。搜索结果如图:
三个控制器里面有AdminIndex
这个方法,还有一条SQL与之相关。
摘出一个控制器的代码:
class DevTeamAddon extends Addon{ //实现的AdminIndex钩子方法 public function AdminIndex($param){ $config = $this->getConfig(); $this->assign('addons_config', $config); if($config['display']) $this->display('widget'); } }
SQL语句为:
INSERT INTO `onethink_hooks` VALUES ('13', 'AdminIndex', '首页小格子个性化显示', '1', '1382596073', 'SiteStat,SystemInfo,DevTeam');
补充说明:表字段依次为id
,name
,description
,type
,update_time
,addons
。
根据AdminIndex
里面的代码,我去找其父类的getConfig
的方法。按住Command
(windows下安转Ctrl
),点击跳转到父类方法:
/** * 获取插件的配置数组 */ final public function getConfig($name=''){ static $_config = array(); if(empty($name)){ $name = $this->getName(); } if(isset($_config[$name])){ return $_config[$name]; } $config = array(); $map['name'] = $name; $map['status'] = 1; $config = M('Addons')->where($map)->getField('config'); if($config){ $config = json_decode($config, true); }else{ $temp_arr = include $this->config_file; foreach ($temp_arr as $key => $value) { if($value['type'] == 'group'){ foreach ($value['options'] as $gkey => $gvalue) { foreach ($gvalue['options'] as $ikey => $ivalue) { $config[$ikey] = $ivalue['value']; } } }else{ $config[$key] = $temp_arr[$key]['value']; } } } $_config[$name] = $config; return $config; }
代码比较简单,首先看静态变量$_config
里有没有,没有就去数据里找,如果数据库里也没有,就去看这个插件目录下面的配置文件。发现这似乎是与钩子需要实现的插件相关的代码,似乎与钩子无关。
不禁想,既然AdminIndex
也不是实现其父类的方法,那么钩子是如何找到的呢?我想是不是应该从刚刚搜出来的数据库入手。因为数据库里存了一个钩子的name=>AdminIndex
。这里还需要先去看看
{:hook('AdminIndex')}
经过编译之后到底是什么样子,这个{:hook()}
到底是个什么函数呢?,还是搜AdminIndex
,查看onethink/Application/Admin/View/Index/index.html
编译之后的文件发现是echo hook('AdminIndex')
,如图所示:
点击进去,查看hook
这个函数
跳转到function.php
查看其代码为:
/** * 处理插件钩子 * @param string $hook 钩子名称 * @param mixed $params 传入参数 * @return void */ function hook($hook,$params=array()){ \Think\Hook::listen($hook,$params); }
顺藤摸瓜,找到onethink/ThinkPHP/Library/Think/Hook.class.php
里面的listen
方法
/** * 监听标签的插件 * @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; }
把debug代码去掉,就一点点:
static public function listen($tag, &$params=NULL) { if(isset(self::$tags[$tag])) { foreach (self::$tags[$tag] as $name) { $result = self::exec($name, $tag,$params); } } return; }
然后找里面的self::exec
方法
/** * 执行某个插件 * @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); }
哦,原来钩子里不仅可以挂插件,也可以挂行为扩展。这里还是以插件为例子。后台首页的onethink/Application/Admin/View/Index/index.html
里执行了hook('AdminIndex')
,它里面执行了Hook::listen('AdminIndex')
,继续执行了Hook::exec($name,'AdminIndex')
。
通过第一张截图可以知道AdminIndex
这个钩子在三个文件/Addons/DevTeam/SiteStat.class.php
,/Addons/DevTeam/SystemInfo.class.php
,/Addons/DevTeam/DevTeamAddon.class.php
中被实现。也就是调用Hook::listen()
方法的时候,里面的静态成员self::$tags[$tag]
是
array('SiteStat','SystemInfo','DevTeam')
这样在Hook::exec()
方法里面就可以找到Addons\\SiteStat\\SiteStatAddon
现在剩下的任务就是只有一个了,要弄清楚这个self::$tags[$tag]
是如何初始化的。
想了好一会,又去刷了会微博,实在没想到,但是这个数据的初始化应该是从前面说的那个数据库的onethink_hooks
表里查询出来的。所以我尝试着搜下M('Hooks')
(虽然这是一个很笨的办法)。看了下找到的内容,似乎两个控制器里面的代码都不是我所想看到的。
没道理啊,由于这里是静态方法,静态成员,也不存在实例化的问题,在这个类里面也没有看到初始化的代码,唯一的可能就是系统初始化的时候从数据库里加载里已有的钩子。但是为什么搜M('Hooks')
搜不到呢?
我又从ThinkPHP.php
往下找,看到Think/Think::start()
里面有很多初始化的内容,里面有Hooks::import()
// 加载模式行为定义 if(isset($mode['tags'])) { Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']); }
而这里导入的都是onethink/ThinkPHP/Mode/common.php
里面的数组里面的行为扩展
// 行为扩展定义 'tags' => array( 'app_begin' => array( 'Behavior\ReadHtmlCache', // 读取静态缓存 ), 'app_end' => array( 'Behavior\ShowPageTrace', // 页面Trace显示 ), 'view_parse' => array( 'Behavior\ParseTemplate', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎 ), 'template_filter'=> array( 'Behavior\ContentReplace', // 模板输出替换 ), 'view_filter' => array( 'Behavior\WriteHtmlCache', // 写入静态缓存 ), ),
好吧,再看Think/Think::start()
里面还有一处使用Hooks::import()
Hook::import(include CONF_PATH.'tags.php'); ///文件地址onethink/Application/Common/Conf/tags.php
里面包含的是一个数组
array( 'app_init'=>array('Common\Behavior\InitHook') );
这样在执行完这个导入之后与原有的数据做array_merge
,Hook::$tags
的数据为:
array(6) { ["app_begin"] => array(1) { [0] => string(22) "Behavior\ReadHtmlCache" } ["app_end"] => array(1) { [0] => string(22) "Behavior\ShowPageTrace" } ["view_parse"] => array(1) { [0] => string(22) "Behavior\ParseTemplate" } ["template_filter"] => array(1) { [0] => string(23) "Behavior\ContentReplace" } ["view_filter"] => array(1) { [0] => string(23) "Behavior\WriteHtmlCache" } ["app_init"] => array(1) { [0] => string(24) "Common\Behavior\InitHook" } }
没错应该就是从这里入手,从名字上来看就是InitHook
相关的文件了。其实前面刚刚搜M('Hooks')
的时候有搜出来,没仔细看。就是InitHookBehavior.class.php
这个文件,走到这个文件就是初始化的最后一步了,之前还有一步,就是如何执行的Common\Behavior\InitHookBehavior::run()
,根据前面的经验,既然在Hook::$tags
里面有app_init
的键,所以只需搜下,即可发现
原来在\Think\App::run()
的最开始做了钩子的初始化(执行Hook::listen('app_init')
)
/** * 运行应用实例 入口文件使用的快捷方法 * @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::exec('Common\Behavior\InitHook')
,根据这样就实例化Common\Behavior\InitHookBehavior()
,然后调用了其run()
方法。也就是说在我们实例化一个app的时候,它先用钩子的监听方法,触发了所有钩子的初始化,将所有钩子数据放入了Hook::$tags
里面。下面是Common\Behavior\InitHookBehavior()
的代码:
// 初始化钩子信息 class InitHookBehavior extends Behavior { // 行为扩展的执行入口必须是run public function run(&$content){ if(isset($_GET['m']) && $_GET['m'] === 'Install') return; $data = S('hooks'); if(!$data){ $hooks = M('Hooks')->getField('name,addons'); foreach ($hooks as $key => $value) { if($value){ $map['status'] = 1; $names = explode(',',$value); $map['name'] = array('IN',$names); $data = M('Addons')->where($map)->getField('id,name'); if($data){ $addons = array_intersect($names, $data); Hook::add($key,$addons); } } } S('hooks',Hook::get()); }else{ Hook::import($data,false); } } }
这样也验证了前面对初始化的时候做M('Hooks')
取数据的猜想,前面虽然搜到了这个文件,但没认真看。怎么把数据库里的钩子挂载的插件用逗号切割,也都在里面了。这样就在后面使用钩子函数执行hook('AdminIndex')
的时候,Hook::$tags
里面已经有对应的数据了。
["AdminIndex"] => array(3) { [0] => string(8) "SiteStat" [1] => string(10) "SystemInfo" [2] => string(7) "DevTeam" }