其實就是設計模式裏面的觀察者模式?
把須要處理的事件綁定到一個處理方法,而後這個方法就自動觸發處理。
用這種方法能夠不須要改動原方法代碼,而使用普通方法則須要
例如
function abc(){
// doth
}
而咱們想執行abc後能夠自動執行efg方法
這樣就須要使用hook把efg綁定到abc
例如加一句 Hook::listen('abc','efg'');
這樣執行完 abc就能夠自動執行efg方法,而不須要改動abc代碼。
//http://blog.csdn.net/daiyan_csdn/article/details/52175654 class Test { public static function example() { Hook::exec("string"); echo "hello<br />"; Hook::exec("arr"); } } //鉤子類 class Hook { static public function exec($type,$model=' ') { if($model=' ') { $m = new hello(); } else { $m = new $model(); } if($type=='string') { $m->string(); } elseif($type=='arr') { $m->arr(); } } } abstract class lan { abstract function string() ; abstract function arr(); } class hello extends lan { public function string() { $str="I am a Hook test<br />"; echo "$str <br />"; } public function arr() { $arr =array(1,2,3,4,5,6); echo "<pre>"; print_r($arr); echo "</pre>"; } } Test::example();
PHP的HOOK是一種思想,而不是一種特定的函數 接口,HOOK的應用多爲插件形式。 其原理很簡單,就是先加載插件(即include 文件),通常寫一個函數來加載,add_hook, 而後要調用便可,調用的時候,不是直接調用插件中的函數,而是經過一個函數來間接調用,這個接口的一般寫法是run_hook 以我參與作的一個實際項目來分析吧: 牛域網 ,提供各種的域名註冊,包括全部的國別域名與各種的新頂級後綴。牛域網應用到了hook技術,把全部的hook文件都放在include/hook/ 目錄下,hook文件的寫法以下: //////////////////////////////// hookexample.php /////////////////////////////////////////// if (!defined("NIUYU")) die("不能夠直接訪問"); function hookexample($vars) { // 代碼 } add_hook("hookexample",1,"hookexample"); //////////////////////////////////////////////////////////////////////////// 細心的讀者會問了,add_hook怎麼寫呢?貼出代碼來分析: function add_hook($hook_name, $priority, $hook_function, $rollback_function = '') { global $hooks; // 註冊爲全局的,保存牛域網全部用的hooks if( !is_array($hooks) ) { $hooks = array( ); } if( !array_key_exists($hook_name, $hooks) ) { $hooks[$hook_name] = array( ); } array_push($hooks[$hook_name], array( 'priority' => $priority, 'hook_function' => $hook_function, 'rollback_function' => $rollback_function )); } 每一個hook能執行一系列函數,保存在$hooks[$hook_name] 中。 run_hook function run_hook($hook_name, $args) { global $hooks; if( !is_array($hooks) ) { $hooks = array( ); } if( !array_key_exists($hook_name, $hooks) ) { return array( ); } unset($rollbacks); $rollbacks = array( ); reset($hooks[$hook_name]); $results = array( ); while( list($key, $hook) = each($hooks[$hook_name]) ) { array_push($rollbacks, $hook['rollback_function']); if( function_exists($hook['hook_function']) ) { $res = call_user_func($hook['hook_function'], $args); if( $res ) { $results[] = $res; hook_log($hook_name, "Hook Completed - Returned True"); } else { hook_log($hook_name, "Hook Completed - Returned False"); } } else { hook_log($hook_name, "Hook Function %s Not Found", $hook['hook_function']); } } return $results; }
咱們先來回顧下本來的開發流程; 產品汪搞出了一堆需求; 當用戶註冊成功後須要發送短信、發送郵件等等; 而後聰明機智勇敢的程序猿們就一撲而上; 把這些需求轉換成代碼扔在 用戶註冊成功 和 跳轉到首頁 之間; 沒有什麼可以阻擋;充滿創造力的猿們; <?php class Test{ public function index(){ // 用戶註冊成功 /* 此處是一堆發送短信的代碼 */ /* 此處是一堆發送郵件的代碼 */ /* 此處是一堆其餘功能的代碼 */ // 前往網站首頁 } } $test=new Test(); $test->index(); PHP 若是每一個功能都由不一樣的猿完成的話; 首先面臨的就是代碼會很雜亂;配合起來會比較麻煩; 那封裝成函數吧;一方面會規範整潔寫;另外方便重複調用; 沒有什麼可以阻擋;充滿創造力的猿們; <?php class Test{ public function index(){ // 用戶註冊成功 // 發送短信 sendSms($phone); // 發送郵件 sendSms($email); // 其餘操做... // 前往網站首頁 } } /** * 發送短信通知 * @param integer $phone 手機號 */ function sendSMS($phone){ // 此處是發送短信的代碼 } /** * 發送郵件通知 * @param string $email 郵箱地址 */ function sendEmail($email){ // 此處是發送郵件的代碼 } PHP 這時候運營喵表示; 若是能在後臺點點按鈕就能設置是發郵件仍是發短信;那想必是極好的; 沒有什麼可以阻擋;充滿創造力的猿們; <?php class Test{ public function index(){ // 用戶註冊成功 if ('若是設置了發送短信') { // 發送短信 sendSms($phone); } if ('若是設置了發送郵件') { // 發送郵件 sendSms($email); } // 其餘操做... // 前往網站首頁 } } /** * 發送短信通知 * @param integer $phone 手機號 */ function sendSMS($phone){ // 此處是發送短信的代碼 } /** * 發送郵件通知 * @param string $email 郵箱地址 */ function sendEmail($email){ // 此處是發送郵件的代碼 } PHP 在一個封閉企業環境下這樣搞是沒有問題的; 然鵝;咱們還有一位開放無私的猿領導要把程序開源出去造福其餘猿類; 但願有更多的猿類來參與這個項目;共同開發功能; 若是你們都去改動這套程序;把本身的代碼扔在 用戶註冊成功 和 跳轉到首頁 之間; 這顯然是不靠譜的;想一想都混亂的一塌糊塗; 那可不能夠你們把本身寫的代碼放到某個目錄下; 而後系統自動的根據配置項把這些代碼加載到 用戶註冊成功 和 跳轉到首頁 之間呢? 好先定義以下目錄 ├─plugin // 插件目錄 │ ├─plugin1 // 插件1 │ │ ├─config.php // 插件1的配置項 │ │ ├─index.php // 插件1的程序處理內容 │ ├─plugin2 │ │ ├─config.php │ │ ├─index.php │ ├─plugin3 │ │ ├─config.php │ │ ├─index.php │ ├─... ├─index.php // 業務邏輯 PHP 業務邏輯的代碼: <?php class Test{ public function index(){ // 用戶註冊成功 // 獲取所有插件 $pluginList=scandir('./plugin/'); // 循環插件 // 排除. .. foreach ($pluginList as $k => $v) { if ($v=='.' || $v=='..') { unset($pluginList[$k]); } } echo "簡易後臺管理<hr>"; // 插件管理 foreach ($pluginList as $k => $v) { // 獲取配置項 $config=include './plugin/'.$v.'/config.php'; $word=$config['status']==1 ? '點擊關閉' : '點擊開啓'; echo $config['title'].'<a href="./index.php?change='.$v.'">'.$word.'</a><br />'; } echo '<hr>'; // 輸出插件內容 foreach ($pluginList as $k => $v) { // 獲取配置項 $config=include './plugin/'.$v.'/config.php'; if ($config['status']==1) { include './plugin/'.$v.'/index.php'; // 運行插件 Hook::run($v); } } // 前往網站首頁 } } // 插件類 class Hook{ // 註冊添加插件 public static function add($name,$func){ $GLOBALS['hookList'][$name][]=$func; } // 執行插件 public static function run($name,$params=null){ foreach ($GLOBALS['hookList'][$name] as $k => $v) { call_user_func($v,$params); } } } // 更改插件狀態 if (isset($_GET['change'])) { // 獲取到配置項 $config=include './plugin/plugin'.substr($_GET['change'],-1).'/config.php'; // 若是是開啓 那就關閉 若是是關閉 則開啓 $config['status']=$config['status']==1 ? 0: 1; // 將更改後的配置項寫入到文件中 $str="<?php \\r\\n return ".var_export($config,true).';'; file_put_contents('./plugin/'.$_GET['change'].'/config.php', $str); header('Location:./'); } $test=new Test(); $test->index(); PHP 插件配置項代碼: <?php return array ( 'status' => 1, // 定義狀態 1表示開啓 0表示關閉 'title' => '發送短信', // 插件的名稱 ); PHP 插件的內容: <?php Hook::add('plugin1',function(){ echo '發送短信的內容<br />'; }); PHP
demo簡單理解鉤子 咱們想一下項目的開發過程: 1.產品經理根據用戶需求(甲方乙方都行)搞了一大堆的需求. 2.當用戶註冊成功後須要發短信,郵箱等等驗證. 3.工程師一擁而上開始寫代碼 4.寫什麼?把產品經理提出的需求實現,轉換成代碼在"用戶註冊成功"和"跳轉頁面"之間 工程師ing.... class Demo{ public function index(){ //用戶註冊成功 /* 發送短信的代碼 */ /* 發送郵箱的代碼 */ /* 其餘功能balabla */ //頁面跳轉到網站首先等等 } } $demo = new Demo(); //new一個對象出來 $demo->index(); //調用執行就能夠了 若是這段代碼的幾塊功能塊由不一樣的工程師完成; 1.代碼混亂 2.配合麻煩 封裝成函數? 1.代碼會整潔一些 2.方便重複調用 工程師ing.... class Test{ public function index(){ // 用戶註冊成功 // 發送短信 sendSms($phone); // 發送郵件 sendSms($email); // 其餘操做... // 前往網站首頁 } } /** * 發送短信通知 * @param integer $phone 手機號 */ function sendSMS($phone){ // 此處是發送短信的代碼 } /** * 發送郵件通知 * @param string $email 郵箱地址 */ function sendEmail($email){ // 此處是發送郵件的代碼 } 這時甲方或不懂代碼的產品狗就會說,好難用,也理解不了大家這幫程序員居然說這樣好用?我會的就只有點擊,雙擊,鼠標中間都不會用.搞毛呢? 工程師ing.... <?php class Test{ public function index(){ // 用戶註冊成功 if ('若是設置了發送短信') { // 發送短信 sendSms($phone); } if ('若是設置了發送郵件') { // 發送郵件 sendSms($email); } // 其餘操做... // 前往網站首頁 } } /** * 發送短信通知 * @param integer $phone 手機號 */ function sendSMS($phone){ // 此處是發送短信的代碼 } /** * 發送郵件通知 * @param string $email 郵箱地址 */ function sendEmail($email){ // 此處是發送郵件的代碼 } 本身發開寫個簡單的文檔給產品狗或者甲方問題不大,同爲工程狗的咱們也能看和開發個差很少. 若是咱們想開源出去,想讓更多的人蔘與進來完善功能.這樣顯然就不合適了. 那可不能夠把本身寫的代碼放在某個目錄下? 而後系統自動的 根據配置項把這些代碼加載到"用戶註冊成功"和"跳轉到首頁"之間? 工程師ing.... ├─plugin // 插件目錄 │ ├─plugin1 // 插件1 │ │ ├─config.php // 插件1的配置項 │ │ ├─index.php // 插件1的程序處理內容 │ ├─plugin2 │ │ ├─config.php │ │ ├─index.php │ ├─plugin3 │ │ ├─config.php │ │ ├─index.php │ ├─... ├─index.php // 業務邏輯 業務邏輯: class Test{ public function index(){ // 用戶註冊成功 // 獲取所有插件 $pluginList=scandir('./plugin/'); // 循環插件 // 排除. .. foreach ($pluginList as $k => $v) { if ($v=='.' || $v=='..') { unset($pluginList[$k]); } } echo "簡易後臺管理<hr>"; // 插件管理 foreach ($pluginList as $k => $v) { // 獲取配置項 $config=include './plugin/'.$v.'/config.php'; $word=$config['status']==1 ? '點擊關閉' : '點擊開啓'; echo $config['title'].'<a href="./index.php?change='.$v.'">'.$word.'</a><br />'; } echo '<hr>'; // 輸出插件內容 foreach ($pluginList as $k => $v) { // 獲取配置項 $config=include './plugin/'.$v.'/config.php'; if ($config['status']==1) { include './plugin/'.$v.'/index.php'; // 運行插件 Hook::run($v); } } // 前往網站首頁 } } // 插件類 class Hook{ // 註冊添加插件 public static function add($name,$func){ $GLOBALS['hookList'][$name][]=$func; } // 執行插件 public static function run($name,$params=null){ foreach ($GLOBALS['hookList'][$name] as $k => $v) { call_user_func($v,$params); } } } // 更改插件狀態 if (isset($_GET['change'])) { // 獲取到配置項 $config=include './plugin/plugin'.substr($_GET['change'],-1).'/config.php'; // 若是是開啓 那就關閉 若是是關閉 則開啓 $config['status']=$config['status']==1 ? 0: 1; // 將更改後的配置項寫入到文件中 $str="<?php \r\n return ".var_export($config,true).';'; file_put_contents('./plugin/'.$_GET['change'].'/config.php', $str); header('Location:./'); } $test=new Test(); $test->index(); 插件配置項: return array ( 'status' => 1, // 定義狀態 1表示開啓 0表示關閉 'title' => '發送短信', // 插件的名稱 ); 插件內容: Hook::add('plugin1',function(){ echo '發送短信的內容<br />'; }); 固然,這個只是簡單的理解鉤子.像國內的discuz,wordpress等等這些源碼,都是很厲害,我我的使用的就是wordpress的blog.很是好用,切代碼寫的至關漂亮.
http://blog.163.com/zhu329599788@126/blog/static/66693350201699349703/
什麼是鉤子 你們想必聽過插件,wordpress插件特別多,這個就是用鉤子機制實現的。 當代碼在運行的過程當中,咱們預先在運行的幾個特殊點裏執行一些特殊方法:例如在運行方法(例如Blog::add的add方法)以前記錄輸入參數、運行方法以後記錄處理結果,這個de >運行方法以前de>、de >運行方法以後de>就是簡單的鉤子(掛載點),咱們在這個鉤子上放置鉤子函數(記錄輸入參數、記錄處理結果),執行一些和程序運行不相關的任務。 <?php class Blog extends Controller{ public function add(){ //some code $res = $data; return $res; } } $obj = new Blog(); Log::write($_REQUEST); $res = $obj->add(); Log::write(json_encode($res));de> 若是在de >運行方法以前de>放置的是一個de >OnBeforeRunActionCallback()de>的方法,這個方法可能最開始的時候是空的,但咱們之後就能夠不去修改原有代碼,直接在de >OnBeforeRunActionCallback()de>裏面加代碼邏輯就能夠了,例如記錄日誌、參數過濾等等。 de ><?php class Blog extends Controller{ public function add(){ //some code $res = $data; return $res; } } $obj = new Blog(); OnBeforeRunActionCallback($_REQUEST); $obj->add(); OnAfterRunActionCallback($res); function OnBeforeRunActionCallback($param){ Log::write($param); FilterParams($param); } function OnAfterRunActionCallback($res){ Log::write(json_encode($res)); }de> 在項目代碼中,你認爲要擴展(暫時不擴展)的地方放置一個鉤子函數,等須要擴展的時候,把須要實現的類和函數掛載到這個鉤子上,就能夠實現擴展了。 原理 實際的鉤子通常設計爲一個類Hook,該類提供註冊插件到鉤子(add_hook)、觸發鉤子方法(trigger_hook)。註冊插件的時候將插件所要運行的可執行方法存儲到鉤子對應的數組裏面。 de > $_listeners = array( 'OnBeforeRunAction' => array( 'callback1', 'callback2', 'callback3', ), ); //提早註冊插件到鉤子 add_hook('OnBeforeRunAction', 'callback4'); //特定地方執行鉤子 trigger_hook('OnBeforeRunAction');de> 當觸發鉤子的時候,將遍歷de >OnBeforeRunActionde>裏註冊的回調方法,執行對應的回調方法,實現動態擴展功能。註冊的鉤子方法通常是匿名函數: de >function trigger_hook($hook, $data=''){ //查看要實現的鉤子,是否在監聽數組之中 if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0) { // 循環調用開始 foreach ($this->_listeners[$hook] as $listener) { if(is_callable()){ call_user_func($listener, $data); }elseif(is_array($listener)){ // 取出插件對象的引用和方法 $class =& $listener[0]; $method = $listener[1]; if(method_exists($class,$method)) { // 動態調用插件的方法 $class->$method($data); } } } } }de> 如何實現 簡單的 一、插件類Hook:提供註冊插件和執行插件的方法,實際是往一個數組裏存掛載點對應的可執行方法。 2、在某個配置文件或者函數裏統一註冊插件。 de >class Hook { //action hooks array private static $actions = array(); /** * ads a function to an action hook * @param $hook * @param $function */ public static function add_action($hook,$function) { $hook=mb_strtolower($hook,CHARSET); // create an array of function handlers if it doesn't already exist if(!self::exists_action($hook)) { self::$actions[$hook] = array(); } // append the current function to the list of function handlers if (is_callable($function)) { self::$actions[$hook][] = $function; return TRUE; } return FALSE ; } /** * executes the functions for the given hook * @param string $hook * @param array $params * @return boolean true if a hook was setted */ public static function do_action($hook,$params=NULL) { $hook=mb_strtolower($hook,CHARSET); if(isset(self::$actions[$hook])) { // call each function handler associated with this hook foreach(self::$actions[$hook] as $function) { if (is_array($params)) { call_user_func_array($function,$params); } else { call_user_func($function); } //cant return anything since we are in a loop! dude! } return TRUE; } return FALSE; } /** * gets the functions for the given hook * @param string $hook * @return mixed */ public static function get_action($hook) { $hook=mb_strtolower($hook,CHARSET); return (isset(self::$actions[$hook]))? self::$actions[$hook]:FALSE; } /** * check exists the functions for the given hook * @param string $hook * @return boolean */ public static function exists_action($hook) { $hook=mb_strtolower($hook,CHARSET); return (isset(self::$actions[$hook]))? TRUE:FALSE; } } /** * Hooks Shortcuts not in class */ function add_action($hook,$function) { return Hook::add_action($hook,$function); } function do_action($hook) { return Hook::do_action($hook); }de> 用法舉例: de >//添加鉤子 Hook::add_action('unique_name_hook','some_class::hook_test'); //或使用快捷函數添加鉤子: add_action('unique_name_hook','other_class::hello'); add_action('unique_name_hook','some_public_function'); //執行鉤子 do_action('unique_name_hook');//也可使用 Hook::do_action();de> 帶安裝/卸載的 鉤子類初始化的時候去註冊已經開啓的插件(如數據庫記錄);全局合適的時候設置掛載點;運行到合適的時候觸發掛載點註冊的事件。 一、插件類Hook:提供註冊插件和執行插件的方法,實際是往一個數組裏存掛載點對應的可執行方法。 2、插件類增長一個初始化的方法,去數據查找已經安裝的插件,運行插件必須執行的註冊方法(reg),註冊插件方法註冊鉤子到掛載點。 3、固定把插件放在某個目錄,並安照必定規範寫配置文件。後臺有插件列表頁面,遍歷指定目錄下的插件。當安裝的時候,將插件信息記錄到數據庫,卸載的時候刪除數據庫記錄信息。 de ><?php /** * @file plugin.php * @brief 插件核心類 * @note 觀察者模式,註冊事件,觸發事件 */ class plugin extends IInterceptorBase { //默認開啓的插件列表 private static $defaultList = array("_verification","_goodsCategoryWidget","_authorization","_userInfo","_initData"); //已經註冊監聽 private static $_listen = array(); //加載插件 public static function init() { $pluginDB = new IModel('plugin'); $pluginList = $pluginDB->query("is_open = 1","class_name","sort asc"); //加載默認插件 foreach(self::$defaultList as $val) { $pluginList[]= array('class_name' => $val); } foreach($pluginList as $key => $val) { $className = $val['class_name']; $classFile = self::path().$className."/".$className.".php"; if(is_file($classFile)) { include_once($classFile); $pluginObj = new $className(); $pluginObj->reg(); } } } /** * @brief 註冊事件 * @param string $event 事件 * @param object ro function $classObj 類實例 或者 匿名函數 * @param string $method 方法名字 */ public static function reg($event,$classObj,$method = "") { if(!isset(self::$_listen[$event])) { self::$_listen[$event] = array(); } self::$_listen[$event][] = array($classObj,$method); } /** * @brief 顯示已註冊事件 * @param string $event 事件名稱 * @return array */ public static function get($event = '') { if($event) { if( isset(self::$_listen[$event]) ) { return self::$_listen[$event]; } return null; } return self::$_listen; } /** * @brief 觸發事件 * @param string $event 事件 * @param mixed $data 數據 * @notice 能夠調用匿名函數和方法 */ public static function trigger($event,$data = null) { $result = array(); if(isset(self::$_listen[$event])) { foreach(self::$_listen[$event] as $key => $val) { list($pluginObj,$pluginMethod) = $val; $result[$key] = is_callable($pluginObj) ? call_user_func($pluginObj,$data):call_user_func(array($pluginObj,$pluginMethod),$data); } } return isset($result[1]) ? $result : current($result); } /** * @brief 插件物理路徑 * @return string 路徑字符串 */ public static function path() { return IWeb::$app->getBasePath()."plugins/"; } /** * @brief 插件WEB路徑 * @return string 路徑字符串 */ public static function webPath() { return IUrl::creatUrl('')."plugins/"; } /** * @brief 獲取所有插件 * @param string $name 插件名字,若是爲空則獲取所有插件信息 * @return array 插件信息 array( "name" => 插件名字, "description" => 插件描述, "explain" => 使用說明, "class_name" => 插件ID, "is_open" => 是否開啓, "is_install" => 是否安裝, "config_name" => 默認插件參數結構, "config_param"=> 已經保存的插件參數, "sort" => 排序, ) */ public static function getItems($name = '') { $result = array(); $dirRes = opendir(self::path()); //遍歷目錄讀取配置文件 $pluginDB = new IModel('plugin'); while($dir = readdir($dirRes)) { if($dir[0] == "." || $dir[0] == "_") { continue; } if($name && $result) { break; } if($name && $dir != $name) { continue; } $pluginIndex = self::path().$dir."/".$dir.".php"; if(is_file($pluginIndex)) { include_once($pluginIndex); if(get_parent_class($dir) == "pluginBase") { $class_name = $dir; $pluginRow = $pluginDB->getObj('class_name = "'.$class_name.'"'); $is_open = $pluginRow ? $pluginRow['is_open'] : 0; $is_install = $pluginRow ? 1 : 0; $sort = $pluginRow ? $pluginRow['sort'] : 99; $config_param = array(); if($pluginRow && $pluginRow['config_param']) { $config_param = JSON::decode($pluginRow['config_param']); } $result[$dir] = array( "name" => $class_name::name(), "description" => $class_name::description(), "explain" => $class_name::explain(), "class_name" => $class_name, "is_open" => $is_open, "is_install" => $is_install, "config_name" => $class_name::configName(), "config_param"=> $config_param, "sort" => $sort, ); } } } if(!$name) { return $result; } return isset($result[$name]) ? $result[$name] : array(); } /** * @brief 系統內置的全部事件觸發 */ public static function onCreateApp(){plugin::init();plugin::trigger("onCreateApp");} public static function onFinishApp(){plugin::trigger("onFinishApp");} public static function onBeforeCreateController($ctrlId){plugin::trigger("onBeforeCreateController",$ctrlId);plugin::trigger("onBeforeCreateController@".$ctrlId);} public static function onCreateController($ctrlObj){plugin::trigger("onCreateController");plugin::trigger("onCreateController@".$ctrlObj->getId());} public static function onFinishController($ctrlObj){plugin::trigger("onFinishController");plugin::trigger("onFinishController@".$ctrlObj->getId());} public static function onBeforeCreateAction($ctrlObj,$actionId){plugin::trigger("onBeforeCreateAction",$actionId);plugin::trigger("onBeforeCreateAction@".$ctrlObj->getId());plugin::trigger("onBeforeCreateAction@".$ctrlObj->getId()."@".$actionId);} public static function onCreateAction($ctrlObj,$actionObj){plugin::trigger("onCreateAction");plugin::trigger("onCreateAction@".$ctrlObj->getId());plugin::trigger("onCreateAction@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onFinishAction($ctrlObj,$actionObj){plugin::trigger("onFinishAction");plugin::trigger("onFinishAction@".$ctrlObj->getId());plugin::trigger("onFinishAction@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onCreateView($ctrlObj,$actionObj){plugin::trigger("onCreateView");plugin::trigger("onCreateView@".$ctrlObj->getId());plugin::trigger("onCreateView@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onFinishView($ctrlObj,$actionObj){plugin::trigger("onFinishView");plugin::trigger("onFinishView@".$ctrlObj->getId());plugin::trigger("onFinishView@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onPhpShutDown(){plugin::trigger("onPhpShutDown");} } /** * @brief 插件基類,全部插件必須繼承此類 * @notice 必須實現3個抽象方法: reg(),name(),description() */ abstract class pluginBase extends IInterceptorBase { //錯誤信息 protected $error = array(); //註冊事件接口,內部經過調用payment::reg(事件,對象實例,方法); public function reg(){} /** * @brief 默認插件參數信息,寫入到plugin表config_param字段 * @return array("字段名" => array( "name" => "文字顯示", "type" => "數據類型【text,radio,checkbox,select】", "pattern" => "數據校驗【int,float,date,datetime,require,正則表達式】", "value" => "1,數組:枚舉數據【radio,checkbox,select】的預設值,array(名字=>數據); 2,字符串:【text】默認數據", )) */ public static function configName() { return array(); } /** * @brief 插件安裝 * @return boolean */ public static function install() { return true; } /** * @brief 插件卸載 * @return boolean */ public static function uninstall() { return true; } /** * @brief 插件名字 * @return string */ public static function name() { return "插件名稱"; } /** * @brief 插件功能描述 * @return string */ public static function description() { return "插件描述"; } /** * @brief 插件使用說明 * @return string */ public static function explain() { return ""; } /** * @brief 獲取DB中錄入的配置參數 * @return array */ public function config() { $className= get_class($this); $pluginDB = new IModel('plugin'); $dataRow = $pluginDB->getObj('class_name = "'.$className.'"'); if($dataRow && $dataRow['config_param']) { return JSON::decode($dataRow['config_param']); } return array(); } /** * @brief 返回錯誤信息 * @return array */ public function getError() { return $this->error ? join("\r\n",$this->error) : ""; } /** * @brief 寫入錯誤信息 * @return array */ public function setError($error) { $this->error[] = $error; } /** * @brief 插件視圖渲染有佈局 * @param string $view 視圖名字 * @param array $data 視圖裏面的數據 */ public function redirect($view,$data = array()) { if($data === true) { $this->controller()->redirect($view); } else { $__className = get_class($this); $__pluginViewPath = plugin::path().$__className."/".$view; $result = self::controller()->render($__pluginViewPath,$data); if($result === false) { IError::show($__className."/".$view."插件視圖不存在"); } } } /** * @brief 插件視圖渲染去掉佈局 * @param string $view 視圖名字 * @param array $data 視圖裏面的數據 */ public function view($view,$data = array()) { self::controller()->layout = ""; $this->redirect($view,$data); } /** * @brief 插件物理目錄 * @param string 插件路徑地址 */ public function path() { return plugin::path().get_class($this)."/"; } /** * @brief 插件WEB目錄 * @param string 插件路徑地址 */ public function webPath() { return plugin::webPath().get_class($this)."/"; } }
你們想必聽過插件,wordpress插件特別多,這個就是用鉤子機制實現的。php
當代碼在運行的過程當中,咱們預先在運行的幾個特殊點裏執行一些特殊方法:例如在運行方法(例如Blog::add的add方法)以前記錄輸入參數、運行方法以後記錄處理結果,這個de >運行方法以前de>、de >運行方法以後de>就是簡單的鉤子(掛載點),咱們在這個鉤子上放置鉤子函數(記錄輸入參數、記錄處理結果),執行一些和程序運行不相關的任務。程序員
de ><?php class Blog extends Controller{ public function add(){ //some code $res = $data; return $res; } } $obj = new Blog(); Log::write($_REQUEST); $res = $obj->add(); Log::write(json_encode($res));de>
若是在de >運行方法以前de>放置的是一個de >OnBeforeRunActionCallback()de>的方法,這個方法可能最開始的時候是空的,但咱們之後就能夠不去修改原有代碼,直接在de >OnBeforeRunActionCallback()de>裏面加代碼邏輯就能夠了,例如記錄日誌、參數過濾等等。web
de ><?php class Blog extends Controller{ public function add(){ //some code $res = $data; return $res; } } $obj = new Blog(); OnBeforeRunActionCallback($_REQUEST); $obj->add(); OnAfterRunActionCallback($res); function OnBeforeRunActionCallback($param){ Log::write($param); FilterParams($param); } function OnAfterRunActionCallback($res){ Log::write(json_encode($res)); }de>
在項目代碼中,你認爲要擴展(暫時不擴展)的地方放置一個鉤子函數,等須要擴展的時候,把須要實現的類和函數掛載到這個鉤子上,就能夠實現擴展了。正則表達式
實際的鉤子通常設計爲一個類Hook,該類提供註冊插件到鉤子(add_hook)、觸發鉤子方法(trigger_hook)。註冊插件的時候將插件所要運行的可執行方法存儲到鉤子對應的數組裏面。數據庫
de > $_listeners = array( 'OnBeforeRunAction' => array( 'callback1', 'callback2', 'callback3', ), ); //提早註冊插件到鉤子 add_hook('OnBeforeRunAction', 'callback4'); //特定地方執行鉤子 trigger_hook('OnBeforeRunAction');de>
當觸發鉤子的時候,將遍歷de >OnBeforeRunActionde>裏註冊的回調方法,執行對應的回調方法,實現動態擴展功能。註冊的鉤子方法通常是匿名函數:json
de >function trigger_hook($hook,$data=''){ //查看要實現的鉤子,是否在監聽數組之中 if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0) { // 循環調用開始 foreach ($this->_listeners[$hook] as $listener) { if(is_callable()){ call_user_func($listener, $data); }elseif(is_array($listener)){ // 取出插件對象的引用和方法 $class =& $listener[0]; $method = $listener[1]; if(method_exists($class,$method)) { // 動態調用插件的方法 $class->$method($data); } } } } }de>
一、插件類Hook:提供註冊插件和執行插件的方法,實際是往一個數組裏存掛載點對應的可執行方法。
二、在某個配置文件或者函數裏統一註冊插件。設計模式
de >class Hook { //action hooks array private static $actions = array(); /** * ads a function to an action hook * @param$hook * @param$function */ public static function add_action($hook,$function){ $hook=mb_strtolower($hook,CHARSET); // create an array of function handlers if it doesn't already exist if(!self::exists_action($hook)) { self::$actions[$hook] = array(); } // append the current function to the list of function handlers if (is_callable($function)) { self::$actions[$hook][] = $function; return TRUE; } return FALSE ; } /** * executes the functions for the given hook * @paramstring $hook * @paramarray $params * @return boolean true if a hook was setted */ public static function do_action($hook,$params=NULL){ $hook=mb_strtolower($hook,CHARSET); if(isset(self::$actions[$hook])) { // call each function handler associated with this hook foreach(self::$actions[$hook] as $function) { if (is_array($params)) { call_user_func_array($function,$params); } else { call_user_func($function); } //cant return anything since we are in a loop! dude! } return TRUE; } return FALSE; } /** * gets the functions for the given hook * @paramstring $hook * @return mixed */ public static function get_action($hook){ $hook=mb_strtolower($hook,CHARSET); return (isset(self::$actions[$hook]))? self::$actions[$hook]:FALSE; } /** * check exists the functions for the given hook * @paramstring $hook * @return boolean */ public static function exists_action($hook){ $hook=mb_strtolower($hook,CHARSET); return (isset(self::$actions[$hook]))? TRUE:FALSE; } } /** * Hooks Shortcuts not in class */ function add_action($hook,$function){ return Hook::add_action($hook,$function); } function do_action($hook){ return Hook::do_action($hook); }de>
用法舉例:數組
de >//添加鉤子 Hook::add_action('unique_name_hook','some_class::hook_test'); //或使用快捷函數添加鉤子: add_action('unique_name_hook','other_class::hello'); add_action('unique_name_hook','some_public_function'); //執行鉤子 do_action('unique_name_hook');//也可使用 Hook::do_action();de>
鉤子類初始化的時候去註冊已經開啓的插件(如數據庫記錄);全局合適的時候設置掛載點;運行到合適的時候觸發掛載點註冊的事件。app
一、插件類Hook:提供註冊插件和執行插件的方法,實際是往一個數組裏存掛載點對應的可執行方法。
二、插件類增長一個初始化的方法,去數據查找已經安裝的插件,運行插件必須執行的註冊方法(reg),註冊插件方法註冊鉤子到掛載點。
三、固定把插件放在某個目錄,並安照必定規範寫配置文件。後臺有插件列表頁面,遍歷指定目錄下的插件。當安裝的時候,將插件信息記錄到數據庫,卸載的時候刪除數據庫記錄信息。wordpress
de ><?php /** * @fileplugin.php * @brief 插件核心類 * @note 觀察者模式,註冊事件,觸發事件 */ class plugin extends IInterceptorBase { //默認開啓的插件列表 private static $defaultList = array("_verification","_goodsCategoryWidget","_authorization","_userInfo","_initData"); //已經註冊監聽 private static $_listen = array(); //加載插件 public static function init(){ $pluginDB = new IModel('plugin'); $pluginList = $pluginDB->query("is_open = 1","class_name","sort asc"); //加載默認插件 foreach(self::$defaultList as $val) { $pluginList[]= array('class_name' => $val); } foreach($pluginList as $key => $val) { $className = $val['class_name']; $classFile = self::path().$className."/".$className.".php"; if(is_file($classFile)) { include_once($classFile); $pluginObj = new $className(); $pluginObj->reg(); } } } /** * @brief 註冊事件 * @paramstring $event 事件 * @paramobject ro function $classObj 類實例 或者 匿名函數 * @paramstring $method 方法名字 */ public static function reg($event,$classObj,$method = ""){ if(!isset(self::$_listen[$event])) { self::$_listen[$event] = array(); } self::$_listen[$event][] = array($classObj,$method); } /** * @brief 顯示已註冊事件 * @paramstring $event 事件名稱 * @return array */ public static function get($event = ''){ if($event) { if( isset(self::$_listen[$event]) ) { return self::$_listen[$event]; } return null; } return self::$_listen; } /** * @brief 觸發事件 * @paramstring $event 事件 * @parammixed $data 數據 * @notice 能夠調用匿名函數和方法 */ public static function trigger($event,$data = null){ $result = array(); if(isset(self::$_listen[$event])) { foreach(self::$_listen[$event] as $key => $val) { list($pluginObj,$pluginMethod) = $val; $result[$key] = is_callable($pluginObj) ? call_user_func($pluginObj,$data):call_user_func(array($pluginObj,$pluginMethod),$data); } } return isset($result[1]) ? $result : current($result); } /** * @brief 插件物理路徑 * @return string 路徑字符串 */ public static function path(){ return IWeb::$app->getBasePath()."plugins/"; } /** * @brief 插件WEB路徑 * @return string 路徑字符串 */ public static function webPath(){ return IUrl::creatUrl('')."plugins/"; } /** * @brief 獲取所有插件 * @paramstring $name 插件名字,若是爲空則獲取所有插件信息 * @return array 插件信息 array( "name" => 插件名字, "description" => 插件描述, "explain" => 使用說明, "class_name" => 插件ID, "is_open" => 是否開啓, "is_install" => 是否安裝, "config_name" => 默認插件參數結構, "config_param"=> 已經保存的插件參數, "sort" => 排序, ) */ public static function getItems($name = ''){ $result = array(); $dirRes = opendir(self::path()); //遍歷目錄讀取配置文件 $pluginDB = new IModel('plugin'); while($dir = readdir($dirRes)) { if($dir[0] == "." || $dir[0] == "_") { continue; } if($name && $result) { break; } if($name && $dir != $name) { continue; } $pluginIndex = self::path().$dir."/".$dir.".php"; if(is_file($pluginIndex)) { include_once($pluginIndex); if(get_parent_class($dir) == "pluginBase") { $class_name = $dir; $pluginRow = $pluginDB->getObj('class_name = "'.$class_name.'"'); $is_open = $pluginRow ? $pluginRow['is_open'] : 0; $is_install = $pluginRow ? 1 : 0; $sort = $pluginRow ? $pluginRow['sort'] : 99; $config_param = array(); if($pluginRow && $pluginRow['config_param']) { $config_param = JSON::decode($pluginRow['config_param']); } $result[$dir] = array( "name" => $class_name::name(), "description" => $class_name::description(), "explain" => $class_name::explain(), "class_name" => $class_name, "is_open" => $is_open, "is_install" => $is_install, "config_name" => $class_name::configName(), "config_param"=> $config_param, "sort" => $sort, ); } } } if(!$name) { return $result; } return isset($result[$name]) ? $result[$name] : array(); } /** * @brief 系統內置的全部事件觸發 */ public static function onCreateApp(){plugin::init();plugin::trigger("onCreateApp");} public static function onFinishApp(){plugin::trigger("onFinishApp");} public static function onBeforeCreateController($ctrlId){plugin::trigger("onBeforeCreateController",$ctrlId);plugin::trigger("onBeforeCreateController@".$ctrlId);} public static function onCreateController($ctrlObj){plugin::trigger("onCreateController");plugin::trigger("onCreateController@".$ctrlObj->getId());} public static function onFinishController($ctrlObj){plugin::trigger("onFinishController");plugin::trigger("onFinishController@".$ctrlObj->getId());} public static function onBeforeCreateAction($ctrlObj,$actionId){plugin::trigger("onBeforeCreateAction",$actionId);plugin::trigger("onBeforeCreateAction@".$ctrlObj->getId());plugin::trigger("onBeforeCreateAction@".$ctrlObj->getId()."@".$actionId);} public static function onCreateAction($ctrlObj,$actionObj){plugin::trigger("onCreateAction");plugin::trigger("onCreateAction@".$ctrlObj->getId());plugin::trigger("onCreateAction@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onFinishAction($ctrlObj,$actionObj){plugin::trigger("onFinishAction");plugin::trigger("onFinishAction@".$ctrlObj->getId());plugin::trigger("onFinishAction@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onCreateView($ctrlObj,$actionObj){plugin::trigger("onCreateView");plugin::trigger("onCreateView@".$ctrlObj->getId());plugin::trigger("onCreateView@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onFinishView($ctrlObj,$actionObj){plugin::trigger("onFinishView");plugin::trigger("onFinishView@".$ctrlObj->getId());plugin::trigger("onFinishView@".$ctrlObj->getId()."@".$actionObj->getId());} public static function onPhpShutDown(){plugin::trigger("onPhpShutDown");} } /** * @brief 插件基類,全部插件必須繼承此類 * @notice 必須實現3個抽象方法: reg(),name(),description() */ abstract class pluginBase extends IInterceptorBase { //錯誤信息 protected $error = array(); //註冊事件接口,內部經過調用payment::reg(事件,對象實例,方法); public function reg(){} /** * @brief 默認插件參數信息,寫入到plugin表config_param字段 * @return array("字段名" => array( "name" => "文字顯示", "type" => "數據類型【text,radio,checkbox,select】", "pattern" => "數據校驗【int,float,date,datetime,require,正則表達式】", "value" => "1,數組:枚舉數據【radio,checkbox,select】的預設值,array(名字=>數據); 2,字符串:【text】默認數據", )) */ public static function configName(){ return array(); } /** * @brief 插件安裝 * @return boolean */ public static function install(){ return true; } /** * @brief 插件卸載 * @return boolean */ public static function uninstall(){ return true; } /** * @brief 插件名字 * @return string */ public static function name(){ return "插件名稱"; } /** * @brief 插件功能描述 * @return string */ public static function description(){ return "插件描述"; } /** * @brief 插件使用說明 * @return string */ public static function explain(){ return ""; } /** * @brief 獲取DB中錄入的配置參數 * @return array */ public function config(){ $className= get_class($this); $pluginDB = new IModel('plugin'); $dataRow = $pluginDB->getObj('class_name = "'.$className.'"'); if($dataRow && $dataRow['config_param']) { return JSON::decode($dataRow['config_param']); } return array(); } /** * @brief 返回錯誤信息 * @return array */ public function getError(){ return $this->error ? join("\r\n",$this->error) : ""; } /** * @brief 寫入錯誤信息 * @return array */ public function setError($error){ $this->error[] = $error; } /** * @brief 插件視圖渲染有佈局 * @paramstring $view 視圖名字 * @paramarray $data 視圖裏面的數據 */ public function redirect($view,$data = array()){ if($data === true) { $this->controller()->redirect($view); } else { $__className = get_class($this); $__pluginViewPath = plugin::path().$__className."/".$view; $result = self::controller()->render($__pluginViewPath,$data); if($result === false) { IError::show($__className."/".$view."插件視圖不存在"); } } } /** * @brief 插件視圖渲染去掉佈局 * @paramstring $view 視圖名字 * @paramarray $data 視圖裏面的數據 */ public function view($view,$data = array()){ self::controller()->layout = ""; $this->redirect($view,$data); } /** * @brief 插件物理目錄 * @paramstring 插件路徑地址 */ public function path(){ return plugin::path().get_class($this)."/"; } /** * @brief 插件WEB目錄 * @paramstring 插件路徑地址 */ public function webPath(){ return plugin::webPath().get_class($this)."/"; } }de>