Thinkphp源碼分析系列(五)–系統鈎子實現


Thinkphp的插件機制主要依靠的是Hook.class.php這個類,官方文檔中在行為擴展也主要依靠這個類來實現。下面我們來具體看看tp是怎么利用這個類來實現行為擴展的。

首先,行為擴展是什么?有wordpress二次開發經驗的同學應該很容易明白,其實就是鈎子,tp在其內核的執行過程中內置了諸多鈎子,這些鈎子可以允許我們能夠在不改變內核代碼的基礎上來對內核進行一定程度的修改。tp的鈎子機制的實現類就是Hook.class.php。

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框架中,鈎子標簽的實現機制是這樣的。

首先所有的鈎子標簽和其對應的類是記錄在應用模式文件中。tp默認的應用模式是common,對應的應用模式文件是Thinkphp/Mode/Common.php文件。在此文件中我們可以看到行為擴展的定義:

// 行為擴展定義
'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里面使用到的監聽。

/**
* 運行應用實例 入口文件使用的快捷方法
* @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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM