理解鉤子Hook以及在Thinkphp下利用鉤子使用行為擴展

轉載自 http://blog.csdn.net/u013927110/article/details/47000379

什么是鉤子函數

個人理解:鉤子就像一個”陷阱”、”監聽器”,當A發送一個消息到B時,當消息還未到達目的地B時,被鉤子攔截調出一部分代碼做處理,這部分代碼也叫鉤子函數或者回調函數

參考網上說法 譬如我們用鼠標在某個窗口上雙擊了一次, 或者給某個窗口輸入了一個字母 A; 首先發現這些事件的不是窗口, 而是系統!
然后系統告訴窗口: 喂! 你讓人點了, 并且是連續點了兩鼠標, 你準備怎么辦? 或者是系統告訴窗口: 喂! 有人向你家里扔磚頭了, 不信你看看, 那塊磚頭是 A.
這時窗口的對有些事件會忽略、對有些事件會做出反應: 譬如, 可能對鼠標單擊事件忽略, 窗口想: 你單擊我不要緊, 累死你我不負責; 但一旦誰要雙擊我, 我會馬上行動, 給你點顏色瞧瞧! 這里窗口準備要采取的行動, 就是我們提前寫好的事件. 用 Windows 的話說, 窗口的事件就是系統發送給窗口的消息; 窗口要采取的行動(事件代碼)就是窗口>的回調函數.
但是! 往往隔墻有耳. 系統要通知給窗口的”話”(消息), 可能會被另一個家伙(譬如是一個賊)提前聽>到! 有可能這個賊就是專門在這等情報的, 賊知道后, 往往在窗口知道以前就采取了行動! 并且這個賊對不同的消息會采取不同的行動方案, 它的行動方案一般也是早就準備好的; 當然這個賊也不是對什么消息都感興趣, 對不感興趣的消息也就無須制定相應的行動方案.
總結: 這個”賊”就是我們要設置的鉤子; “賊”的”行動方案”就是鉤子函數, 或者叫鉤子的回調函數.
上面一段話原文鏈接 http://www.cnblogs.com/del/archive/2008/02/25/1080825.html

總的來說,鉤子就像一個”掛載點”掛到函數上,當函數執行過程中遇到這個”掛載點”,掛載點(鉤子)就會將一塊代碼拉出來。比如,我們向腳本中傳入了幾個參數,然后進行插入操作,插入之前我們有一個鉤子(before_insert)正在listen,當函數執行到這個鉤子時,就會去通過鉤子去調里面的一塊代碼,我們在這部分鉤子函數里面可以對數據進行驗證或者加工等,達到你想要的操作。

為什么使用鉤子

既然鉤子是一個監聽器,通過鉤子來調用一部分代碼做處理,那我直接在腳本里面另寫一個函數A然后在函數B執行過程中去調用A不就好了?

個人認為:鉤子函數相對于直接在函數中調用另外一個函數來說,更加安全方便。當我們需要修改擴展功能時,我們無需修改函數B中的鉤子,只需要修改鉤子里面的代碼塊即可,而如果直接修改函數A,則會對函數B所在類進行頻繁修改。違背了封閉原則。另一點,利用鉤子對后期的維護和功能擴展更加方便。

Thinkphp中使用鉤子(行為擴展)

在TP中利用鉤子,其實就是行為擴展

行為

行為(Behavior)是一個比較抽象的概念,你可以想象成在應用執行過程中的一個動作或者處理,在框架的執行流程中,各個位置都可以有行為產生,例如路由檢測是一個行為,靜態緩存是一個行為,用戶權限檢測也是行為,大到業務邏輯,小到瀏覽器檢測、多語言檢測等等都可以當做是一個行為,甚至說你希望給你的網站用戶的第一次訪問彈出Hello,world!這些都可以看成是一種行為,行為的存在讓你無需改動框架和應用,而在外圍通過擴展或者配置來改變或者增加一些功能。

而不同的行為之間也具有位置共同性,比如,有些行為的作用位置都是在應用執行前,有些行為都是在模板輸出之后,我們把這些行為發生作用的位置稱之為標簽(位)(tag),當應用程序運行到這個標簽的時候,就會被攔截下來,統一執行相關的行為

這里的行為相當于鉤子函數,想要”使用”它,我們就必須借助鉤子Hook\

添加行為擴展

Thinkphp中有系統的行為擴展,在Library\Think\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_init’),Hook::listen(‘app_begin’); 相當于鉤子監聽的這些tags(這幾個tags是系統內置的行為,無需再注冊)。要觸發自定義行為,必須先注冊行為,ThinkPHP中提供了自動注冊和手動注冊 。

1.自動注冊

TP里面Library\Think目錄下面Think.class.php中有這兩行代碼

  // 加載模式行為定義
          if(isset($mode['tags'])) {
              Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']);
          }
          // 加載應用行為定義
          if(is_file(CONF_PATH.'tags.php'))
              // 允許應用增加開發模式配置定義
              Hook::import(include CONF_PATH.'tags.php');   

模式行為是系統內置的,我們可以在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', // 寫入靜態緩存
        ),
    ),

用戶自定義的行為擴展,需要在CONF_PATH.’tags.php’也就是/Common/conf/tags.php自行創建,

// 加載應用行為定義
if(is_file(CONF_PATH.'tags.php'))
// 允許應用增加開發模式配置定義
Hook::import(include CONF_PATH.'tags.php');

舉例:

<?php 
return array(
    "action_begin" => array('Home\\Behaviors\\testBehavior')
    //一個標簽可以有多個行為,我們也可以這樣
    "action_begin" => array('Home\Behaviors\test1Behavior','Home\\Behaviors\\test2Behavior')
);
 ?>

TP在運行時為自動加載這些行為。我們只需寫好自己的行為擴展,然后在某個地方監聽(Hook::listen(tags)),就可以觸發這些行為了

手動注冊

說到這,我們還是先看下Library\Think\Hook.class.php的代碼

 /**
     * 動態添加插件到某個標簽
     * @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;
        }
    }

代碼十分簡單,我們注冊行為只需要Hook::add(tags,name)

注意:這里面name必須是一個包含命名空間路徑的類,比如Home\Behavior\testBehavior ,類名后面必須是Behavior結尾,類中必須實現run方法。原因:請看Hook類中代碼

/**
     * 執行某個插件
     * @param string $name 插件名稱
     * @param string $tag 方法名 
     * @param Mixed $params 傳入的參數
     */
    static public function exec($name, $tag,&$params=NULL) {
        //這里截取后八位類名字符串,所以必須是Behavior
        if('Behavior' == substr($name,-8) ){ 
            // if('testBehavior' == substr($name,-12)
            // {
            //     $tag = 'test';
            // }
            // 行為擴展必須用run入口方法
               $tag    =   'run';
        }
        echo $name.'<br/>';
        $addon   = new $name();
        return $addon->$tag($params);
    }

當然,你也可以修改規則,比如我不想以Behavior為類名結尾,也不想調用run方法,這時候想修改只能在listen()方法里面進行判斷修改,如果直接在exec()里面修改,立馬報錯,因為系統的內置行為都是按這個規則來的啊!除非你把源碼中的行為類名和行為方法run改掉,當然我相信你不會這么傻

注意事項:

1.觸發行為的關鍵方法是Hook類中的listen方法,它通過遍歷某個行為標簽下的所有行為,依次實例化并調用run方法
2.listen方法中,如果之前在配置文件中開啟了DEBUG模式,則它會生成日志記錄你的行為,這里面牽涉到3.很多的IO操作,所以你的項目完成時建議取消DEBUG模式以提升速度
3.listen方法中,允許傳遞參數且只允許傳遞一個參數(傳多個可以用數組呢),不過這個參數是引用傳值,所以只能傳入變量,傳入常量會報錯
4.最后,Library\Think\Behavior.class.php,這個抽象類中只有一個抽象方法run(),在你的自己行為擴展中建議繼承它,盡管這不是必須的,但是這樣更加規范。。。不過TP內置的系統行為都沒繼承這個抽象類,也不知道鬧哪樣

流程與舉例

使用鉤子觸發行為擴展的流程:

1.自動注冊(Common/Conf/tags.php按格式自己添加),或者 手動注冊(類中方法如初始方法,調用Hook::add(tags,name));
2.寫好自己的行為類,類名以Behavior結尾,實現run方法
3.在需要添加行為的函數里 ,直接Hook::Listen(tags,prarm),注意一定要傳變量,不需要傳常量。

這樣,整個過程就結束了,下面舉個例子

舉例

最近寫了個人網站(小博客) 我需要記錄一下網站的PV,于是我在BaseController里面的初始化方法

     public function _initialize()
     {
         //記錄訪問
         //這里我是手動注冊的行為
         Hook::add('mark_pv','Home\Behaviors\testBehavior');//mark_pv為行為名稱 后面的是路徑
         hook::listen('mark_pv',$param);   //這個是觸發行為 $param為傳遞的參數 可自定義
     }
}

然后我再Home\Behaviors\testBehavior.class.php中

class testBehavior extends Behavior {

    public function run(&$param)  //$param為接收傳遞的參數
    {
        //記錄數據
        $data['client_ip'] = get_client_ip();
        $data['page_view'] = CONTROLLER_NAME.'/'.ACTION_NAME;
        $data['create_time'] = time();
        $data['status'] = 0;
        M('page_view')->add($data);
    }
}

運行后正常插入數據,而且如果后期我的個人網站數據庫由于訪問量太大(呵呵)頂不住壓力,不能再記錄PV了,我直接刪除鉤子函數里面的代碼,不需要動鉤子,就解決了問題,比較方便。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容