您好,登錄后才能下訂單哦!
什么是鉤子?
大家想必聽過插件,wordpress插件特別多,這個就是用鉤子機(jī)制實(shí)現(xiàn)的。
當(dāng)代碼在運(yùn)行的過程中,我們預(yù)先在運(yùn)行的幾個特殊點(diǎn)里執(zhí)行一些特殊方法:例如在運(yùn)行方法(例如Blog::add的add方法)之前記錄輸入?yún)?shù)、運(yùn)行方法之后記錄處理結(jié)果,這個運(yùn)行方法之前、運(yùn)行方法之后就是簡單的鉤子(掛載點(diǎn)),我們在這個鉤子上放置鉤子函數(shù)(記錄輸入?yún)?shù)、記錄處理結(jié)果),執(zhí)行一些和程序運(yùn)行不相關(guān)的任務(wù)。
<?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));
如果在運(yùn)行方法之前放置的是一個OnBeforeRunActionCallback()的方法,這個方法可能最開始的時候是空的,但我們以后就可以不去修改原有代碼,直接在OnBeforeRunActionCallback()里面加代碼邏輯就可以了,例如記錄日志、參數(shù)過濾等等。
<?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)); }
在項目代碼中,你認(rèn)為要擴(kuò)展(暫時不擴(kuò)展)的地方放置一個鉤子函數(shù),等需要擴(kuò)展的時候,把需要實(shí)現(xiàn)的類和函數(shù)掛載到這個鉤子上,就可以實(shí)現(xiàn)擴(kuò)展了。
原理
實(shí)際的鉤子一般設(shè)計為一個類Hook,該類提供注冊插件到鉤子(add_hook)、觸發(fā)鉤子方法(trigger_hook)。注冊插件的時候?qū)⒉寮\(yùn)行的可執(zhí)行方法存儲到鉤子對應(yīng)的數(shù)組里面。
$_listeners = array( 'OnBeforeRunAction' => array( 'callback1', 'callback2', 'callback3', ), ); //提前注冊插件到鉤子 add_hook('OnBeforeRunAction', 'callback4'); //特定地方執(zhí)行鉤子 trigger_hook('OnBeforeRunAction');
當(dāng)觸發(fā)鉤子的時候,將遍歷OnBeforeRunAction里注冊的回調(diào)方法,執(zhí)行對應(yīng)的回調(diào)方法,實(shí)現(xiàn)動態(tài)擴(kuò)展功能。注冊的鉤子方法一般是匿名函數(shù):
function trigger_hook($hook, $data=''){ //查看要實(shí)現(xiàn)的鉤子,是否在監(jiān)聽數(shù)組之中 if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0) { // 循環(huán)調(diào)用開始 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)) { // 動態(tài)調(diào)用插件的方法 $class->$method($data); } } } } }
如何實(shí)現(xiàn)?
簡單的
1、插件類Hook:提供注冊插件和執(zhí)行插件的方法,實(shí)際是往一個數(shù)組里存掛載點(diǎn)對應(yīng)的可執(zhí)行方法。
2、在某個配置文件或者函數(shù)里統(tǒng)一注冊插件。
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); }
用法舉例:
//添加鉤子 Hook::add_action('unique_name_hook','some_class::hook_test'); //或使用快捷函數(shù)添加鉤子: add_action('unique_name_hook','other_class::hello'); add_action('unique_name_hook','some_public_function'); //執(zhí)行鉤子 do_action('unique_name_hook');//也可以使用 Hook::do_action();
帶安裝/卸載的
鉤子類初始化的時候去注冊已經(jīng)開啟的插件(如數(shù)據(jù)庫記錄);全局合適的時候設(shè)置掛載點(diǎn);運(yùn)行到合適的時候觸發(fā)掛載點(diǎn)注冊的事件。
1、插件類Hook:提供注冊插件和執(zhí)行插件的方法,實(shí)際是往一個數(shù)組里存掛載點(diǎn)對應(yīng)的可執(zhí)行方法。
2、插件類增加一個初始化的方法,去數(shù)據(jù)查找已經(jīng)安裝的插件,運(yùn)行插件必須執(zhí)行的注冊方法(reg),注冊插件方法注冊鉤子到掛載點(diǎn)。
3、固定把插件放在某個目錄,并安照一定規(guī)范寫配置文件。后臺有插件列表頁面,遍歷指定目錄下的插件。當(dāng)安裝的時候,將插件信息記錄到數(shù)據(jù)庫,卸載的時候刪除數(shù)據(jù)庫記錄信息。
<?php /** * @file plugin.php * @brief 插件核心類 * @note 觀察者模式,注冊事件,觸發(fā)事件 */ class plugin extends IInterceptorBase { //默認(rèn)開啟的插件列表 private static $defaultList = array("_verification","_goodsCategoryWidget","_authorization","_userInfo","_initData"); //已經(jīng)注冊監(jiān)聽 private static $_listen = array(); //加載插件 public static function init() { $pluginDB = new IModel('plugin'); $pluginList = $pluginDB->query("is_open = 1","class_name","sort asc"); //加載默認(rèn)插件 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 類實(shí)例 或者 匿名函數(shù) * @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 觸發(fā)事件 * @param string $event 事件 * @param mixed $data 數(shù)據(jù) * @notice 可以調(diào)用匿名函數(shù)和方法 */ 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" => 默認(rèn)插件參數(shù)結(jié)構(gòu), "config_param"=> 已經(jīng)保存的插件參數(shù), "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 系統(tǒng)內(nèi)置的所有事件觸發(fā) */ 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 必須實(shí)現(xiàn)3個抽象方法: reg(),name(),description() */ abstract class pluginBase extends IInterceptorBase { //錯誤信息 protected $error = array(); //注冊事件接口,內(nèi)部通過調(diào)用payment::reg(事件,對象實(shí)例,方法); public function reg(){} /** * @brief 默認(rèn)插件參數(shù)信息,寫入到plugin表config_param字段 * @return array("字段名" => array( "name" => "文字顯示", "type" => "數(shù)據(jù)類型【text,radio,checkbox,select】", "pattern" => "數(shù)據(jù)校驗(yàn)【int,float,date,datetime,require,正則表達(dá)式】", "value" => "1,數(shù)組:枚舉數(shù)據(jù)【radio,checkbox,select】的預(yù)設(shè)值,array(名字=>數(shù)據(jù)); 2,字符串:【text】默認(rèn)數(shù)據(jù)", )) */ 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中錄入的配置參數(shù) * @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 視圖里面的數(shù)據(jù) */ 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 視圖里面的數(shù)據(jù) */ 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)."/"; } }
以上就是PHP鉤子機(jī)制原理及詳解的詳細(xì)內(nèi)容,更多請關(guān)注億速云其它相關(guān)文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。