什么是钩子

大家想必听过插件,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)."/";
}
}

哪些系统存在

1、wordpress

2、Discuz:Discuz! 插件制作教程_Discuz! 资料库

3、ThinkPHP

4、OneThink 什么是钩子? - OneThink1.0开发手册

参考资料

1、php中的钩子(hook插件机制) - MasonZhang - 博客园

http://www.cnblogs.com/miketwais/articles/hook.html

2、PHP钩子系统 - ThinkPHP框架

http://www.thinkphp.cn/code/337.html

3、PHP中的插件机制原理和实例_php实例_脚本之家

http://www.jb51.net/article/51980.htm

PHP钩子机制的更多相关文章

  1. javascript钩子机制

    钩子机制是这样的,大家按照某一规则写一个方法(这个规则在方法名称上),然后页面加载完之前,统一执行所有的钩子函数. 注意callHooks方法,里面的局部变量s就是钩子函数名称中一定要有的内容.——这 ...

  2. 钩子机制(hook)

    钩子是编程惯用的一种手法,用来解决一种或多种特殊情况的处理. 简单来说,钩子就是适配器原理,或者说是表驱动原理,我们预先定义了一些钩子,在正常的代码逻辑中使用钩子去适配一些特殊的属性,样式或事件,这样 ...

  3. koahub.js 0.09 发布,新增钩子机制

    koahubjs发布0.09 新增钩子机制添加钩子机制,控制器钩子和函数钩子修复自动加载bug,实现除自动加载导出的default外,还能自动加载其他的方法记koahubjs钩子开发过程在使用koah ...

  4. [转] js中的钩子机制(hook)

    什么是钩子机制?使用钩子机制有什么好处? 钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子:这样做的好处就是提高了程序的执行效 ...

  5. jQuery 2.0.3 源码分析 钩子机制 - 属性操作

    jQuery提供了一些快捷函数来对dom对象的属性进行存取操作. 这一部分还是比较简单的. 根据API这章主要是分解5个方法 .attr()   获取匹配的元素集合中的第一个元素的属性的值  或 设置 ...

  6. jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容

    处理浏览器兼容问题实际上不是jQuery的精髓,毕竟让技术员想方设法取弥补浏览器的过错从而使得代码乱七八糟不是个好事.一些特殊情况的处理,完全实在浪费浏览器的性能:突兀的兼容解决使得的代码看起来既不美 ...

  7. thinkphp5 自动注册Hook机制钩子扩展

    Hook.php 文件已更新1.修复在linux环境下类的 \ 在basename 下无法获取到类名的问题2.修复linux 环境下无法使用hook::call 调用失败问题 请先安装thinkphp ...

  8. Thinkphp源码分析系列(五)–系统钩子实现

    Thinkphp的插件机制主要依靠的是Hook.class.php这个类,官方文档中在行为扩展也主要依靠这个类来实现.下面我们来具体看看tp是怎么利用这个类来实现行为扩展的. 首先,行为扩展是什么?有 ...

  9. typecho流程原理和插件机制浅析(第二弹)

    typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...

随机推荐

  1. python遍历一个网段的ip地址

    def ip2num(ip):#ip to int num lp = [int(x) for x in ip.split('.')] return lp[0] << 24 | lp[1] ...

  2. Node使用multiparty包上传文件

    var multiparty = require('multiparty'); var http = require('http'); var util = require('util'); var ...

  3. LeetCode 389. Find the Difference

    Given two strings s and t which consist of only lowercase letters. String t is generated by random s ...

  4. TFS 分支导致nuget项目依赖丢失

    问题: 项目的代码 在tfs上分支后,签出项目.编译时发现无法编译,原有的nuget来的包的dll都丢失了(项目签入时,默认会忽略dll) 在网上找了下,发现一个简单的解决方法: 在"程序包 ...

  5. IntelliJ IDEA 绝对好用快捷键

    最近根据自己的使用习惯整理了一下在windows下常用的一些快捷键,有些确实非常实用. 常用快捷键  键 作用 备注 Ctrl+F12 显示当前类的所有方法   F2 定位下一个错误位置   Alt ...

  6. Linux 用户添加sudo 权限

    编辑/etc/sudoers 搜索root 添加 账号 ALL=(ALL) ALL

  7. iOS Swift 3 open

    参考资料:http://stackoverflow.com/questions/38947101/what-is-the-open-keyword-in-swift

  8. java 随机生成身份证代码

    import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.I ...

  9. Chrome - 怎样独立窗口打开开发人员工具

    打开开发人员工具, 右上角找到下图红圈的键, 长按左键直到出现绿圈的键, 别松开鼠标, 把指针移到绿圈的键上面, 松开左键, 好了, 一个独立窗口粗线了. 转载请声明出处: http://www.cn ...

  10. Winform中创建超链接,点击跳转网页

    代码如下: System.Diagnostics.Process ie = new System.Diagnostics.Process();ie.StartInfo.FileName = " ...