Thinkphp的插件机制主要依靠的是Hook.class.php这个类,官方文档中在行为扩展也主要依靠这个类来实现。下面咱们来具体看看tp是怎么利用这个类来实现行为扩展的。php
首先,行为扩展是什么?有wordpress二次开发经验的同窗应该很容易明白,其实就是钩子,tp在其内核的执行过程当中内置了诸多钩子,这些钩子能够容许咱们可以在不改变内核代码的基础上来对内核进行必定程度的修改。tp的钩子机制的实现类就是Hook.class.php。apache
Hook.class.php内部维护了一个数组,这个数组的键就是钩子的名称,值就是类的名称的集合。咱们利用Hook类的add方法能够添加一个钩子,其实就是往这个维护的数组上添加一个键值。tp默认已经定义了不少钩子标签。数组
app_init 应用初始化标签位
path_info PATH_INFO检测标签位
app_begin 应用开始标签位
action_name 操做方法名标签位
action_begin 控制器开始标签位
view_begin 视图输出开始标签位
view_parse 视图解析标签位
template_filter 模板内容解析标签位
view_filter 视图输出过滤标签位
view_end 视图输出结束标签位
action_end 控制器结束标签位
app_end 应用结束标签位缓存
在3.2版本的tp框架中,钩子标签的实现机制是这样的。session
首先全部的钩子标签和其对应的类是记录在应用模式文件中。tp默认的应用模式是common,对应的应用模式文件是Thinkphp/Mode/Common.php文件。在此文件中咱们能够看到行为扩展的定义:app
// 行为扩展定义 'tags' => array( 'app_init' => array( 'Behavior\BuildLiteBehavior', // 生成运行Lite文件 ), 'app_begin' => array( 'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存 ), 'app_end' => array( 'Behavior\ShowPageTraceBehavior', // 页面Trace显示 ), 'view_parse' => array( 'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎 ), 'template_filter'=> array( 'Behavior\ContentReplaceBehavior', // 模板输出替换 ), 'view_filter' => array( 'Behavior\WriteHtmlCacheBehavior', // 写入静态缓存 ), ),
咱们在前面说到 ThinkPHP 引导类的时候讲到此类会根据当前的模式读取模式文件而且按照模式文件中的配置依次去读取配置文件从而完成系统核心的加载。其中有一项就是上面的行为模式。在这个引导类的75行左右,程序开始加载模式文件中定义的标签和类,经过Hook::import方法把这些标签和类的映射加载到了Hook内部为的tags数组中。框架
// 加载模式别名定义 if(isset($mode['alias'])){ self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']); }
而后在后面咱们就能够看到tp框架在监听这些标签。何为监听?咱们来看一下APP.class.php里面使用到的监听。wordpress
/** * 运行应用实例 入口文件使用的快捷方法 * @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::listen(‘app_begin’);就是一个监听。当程序执行到此处代码的时候,这个代码会去执行listen方法,此方法会去检测Hook持有的tags数组中是否含有app_begin标签,若是有的话就去看其对应的类文件,并到ThinkPHP\Library\Behavior目录下去寻找对应的类文件并加载实例化。而后就去调用实例化对象的run方法并执行。函数
因而可知,若是咱们想要在应用执行开始的时候加一些咱们本身的实现逻辑,只须要写一个带有run方法的行为类,这个类通常继承自Behavior类,而后在run方法中写入本身的逻辑,而后把咱们写好的类名加到模式文件中,这样就能够轻松的作到扩展核心代码了。学习
下面咱们来具体看一下Hook类的实现细节。
// +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2006~2013 http://topthink.com All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: liu21st <liu21st@gmail.com> // +---------------------------------------------------------------------- namespace Think; /** * ThinkPHP系统钩子实现 */ class Hook { //这就是Hook类持有的tags静态数组变量,此变量以键值对的形式存储标签和类的映射。 static private $tags = array(); /** * 动态添加插件到某个标签 * @param string $tag 标签名称 * @param mixed $name 插件名称 * @return void 其实就是把$tag做为键,$name 做为对应的额值加入到tags数组中。若是$name是一个数组就合并到tags中。 */ 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 array $data 插件信息 * @param boolean $recursive 是否递归合并 * @return void 导入插件的本质仍是把数据加入到tags数组中。可是其传入的参数是$data数组,$data自己就是一个相似于tags的东西,它存储的也是标签和类的映射。因此是把$data和$tags合并了。 */ 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); } } } } /** * 获取插件信息 * @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 此函数最为重要,其中调用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; } /** * 执行某个插件 * @param string $name 插件名称 * @param string $tag 方法名(标签名) * @param Mixed $params 传入的参数 * @return void 执行插件的原理:其实就是经过标签从tags数组中获得类名的集合,而后拼凑出类文件名称,实例化类,执行类的run方法。 */ static public function exec($name, $tag,&$params=NULL) { if('Behavior' == substr($name,-8) ){ // 行为扩展必须用run入口方法 $tag = 'run'; } $addon = new $name(); return $addon->$tag($params); } }
加入博主我的博客,一块儿学习 http://www.kanronghua.com/