天天看點

懂得鈎子Hook以及在Thinkphp下利用鈎子使用行為擴充什麼是鈎子函數為什麼使用鈎子Thinkphp中使用鈎子(行為擴充)流程與舉例

了解鈎子Hook以及在Thinkphp下利用鈎子使用行為擴充

什麼是鈎子函數

個人了解:鈎子就像一個”陷阱”、”監聽器”,當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中有系統的行為擴充,在Lib\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中提供了自動注冊和手動注冊 。

自動注冊

TP裡面Lib\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');   
           

模式行為是系統内置的,我們可以在Lib\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自行建立,舉例

<?php 
return array(
    "action_begin" => array('Home\\Behaviors\\testBehavior')
    //一個标簽可以有多個行為,我們也可以這樣
    "action_begin" => array('Home\Behaviors\test1Behavior','Home\\Behaviors\\test2Behavior')
);
 ?>
           
TP在運作時為自動加載這些行為。我們隻需寫好自己的行為擴充,然後在某個地方監聽(Hook::listen(tags)),就可以觸發這些行為了

手動注冊

說到這,我們還是先看下Lib\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,-) ){ 
            // 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改掉,當然我相信你不會這麼傻

其他事項:

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

流程與舉例

使用鈎子觸發行為擴充的流程:

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

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

舉例

鄙人最近寫了個人網站(小部落格) http://www.huangtianer.com/ 

我需要記錄一下網站的PV,于是我在BaseController裡面的初始化方法

class BaseController extends Controller{
     public function _initialize()
     {
         //記錄通路
         //這裡我是手動注冊的行為
         Hook::add('mark_pv','Home\Behaviors\testBehavior');
         hook::listen('mark_pv');   
     }
}
           

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

class testBehavior extends Behavior {

    public function run(&$param)
    {
        //記錄資料
        $data['client_ip'] = get_client_ip();
        $data['page_view'] = CONTROLLER_NAME.'/'.ACTION_NAME;
        $data['create_time'] = time();
        $data['status'] = ;
        M('page_view')->add($data);
    }
}
           

運作後正常插入資料,而且如果後期我的個人網站資料庫由于通路量太大(呵呵)頂不住壓力,不能再記錄PV了,我直接删除鈎子函數裡面的代碼,不需要動鈎子,就解決了問題,比較友善。

結束語:如有BUG或者建議,歡迎指正

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。

源自

http://www.myexception.cn/php/1969593.html