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

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/

相關文章
相關標籤/搜索