什麼是鉤子?
大家想必聽過插件,wordpress插件特別多,這個就是用鉤子機制實現的。
當程式碼在運行的過程中,我們預先在運行的幾個特殊點裡執行一些特殊方法:例如在運行方法(例如Blog::add的add方法)之前記錄輸入參數、運行方法之後記錄處理結果,這個運行方法之前、運行方法之後就是簡單的鉤子(掛載點),我們在這個鉤子上放置鉤子函數(記錄輸入參數、記錄處理結果),執行一些和程序運行不相關的任務。
<?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));
如果在運行方法之前放置的是一個OnBeforeRunActionCallback()的方法,這個方法可能最開始的時候是空的,但我們以後就可以不去修改原有程式碼,直接在OnBeforeRunActionCallback()裡面加程式碼邏輯就可以了,例如記錄日誌、參數過濾等等。
<?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)); }
在專案程式碼中,你認為要擴充(暫時不擴充)的地方放置一個鉤子函數,等需要擴充的時候,把需要實作的類別和函數掛載到這個鉤子上,就可以實現擴展了。
原理
實際的鉤子一般設計為一個類別Hook,該類別提供註冊插件到鉤子(add_hook)、觸發鉤子方法(trigger_hook)。註冊插件的時候將插件所要運行的可執行方法儲存到鉤子對應的陣列裡面。
$_listeners = array( 'OnBeforeRunAction' => array( 'callback1', 'callback2', 'callback3', ), ); //提前注册插件到钩子 add_hook('OnBeforeRunAction', 'callback4'); //特定地方执行钩子 trigger_hook('OnBeforeRunAction');
當觸發鉤子的時候,將遍歷OnBeforeRunAction裡註冊的回調方法,執行對應的回調方法,實現動態擴充功能。註冊的鉤子方法一般是匿名函數:
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); } } } } }
如何實作?
簡單的
1、外掛程式類別Hook:提供註冊外掛程式和執行外掛程式的方法,實際上是往一個陣列裡存掛載點對應的可執行方法。
2、在某個設定檔或函數裡統一註冊外掛程式。
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'); //或使用快捷函数添加钩子: add_action('unique_name_hook','other_class::hello'); add_action('unique_name_hook','some_public_function'); //执行钩子 do_action('unique_name_hook');//也可以使用 Hook::do_action();
帶有安裝/卸載的
鉤子類別初始化的時候去註冊已經開啟的插件(如資料庫記錄) ;全域適當的時候設定掛載點;運行到適當的時候觸發掛載點註冊的事件。
1、外掛程式類別Hook:提供註冊外掛程式和執行外掛程式的方法,實際上是往一個陣列裡存掛載點對應的可執行方法。
2、插件類別增加一個初始化的方法,去資料尋找已經安裝的插件,運行插件必須執行的註冊方法(reg),註冊插件方法註冊鉤子到掛載點。
3、固定把插件放在某個目錄,並安照一定規範寫設定檔。後台有插件清單頁面,遍歷指定目錄下的插件。安裝的時候,將插件資訊記錄到資料庫,卸載的時候刪除資料庫記錄資訊。
<?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)."/"; } }
更多相關php知識,請造訪php教學!
以上是PHP鉤子機制原理及詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!