thinkphp5无法使用hook::call 调用失败问题及自动注册Hook机制钩子扩展案例

2019-03-2717:47:00后端程序开发Comments3,018 views字数 11977阅读模式

修复linux 环境下无法使用hook::call 调用失败问题文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

请先安装thinkphp5,包里的文件可直接使用,
application\、application\index\controller\ 文件都已经把文件名改成.bak,.bak文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

钩子机制我这里分为两种使用方法,注:二者只能选其一
1、采用thinkphp5内置的钩子调用方案。 配置参数 jntoo_hook_call 为true 情况下才有效,为了兼容已经写好了钩子机制的使用
2、采用自有的钩子调用方案,此方法支持钩子分类,钩子权重自动排序

个人不建议使用think内置钩子作为系统的扩展。

如何写自动加载文件:
在模块下新建一个hook目录,并在目录下创建文件,如下图:
thinkphp5无法使用hook::call 调用失败问题及自动注册Hook机制钩子扩展案例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

类名等于钩子类型,方法名等于钩子名称。(注:类型只存在于钩子机制方法2。方法1只有方法名生效)
如图所示:
thinkphp5无法使用hook::call 调用失败问题及自动注册Hook机制钩子扩展案例
我们创建了 Category 类型的钩子
里面有两个方法:
index 和 index_5文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

方法1的调用方法:\think\Hook::listen('index'); 就可以直接调用到Category 类 index 方法。
输出结果:
thinkphp5无法使用hook::call 调用失败问题及自动注册Hook机制钩子扩展案例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

方法2的调用方法:\app\common\Hook::call('Category','index'); 就可以调用到 Category 类 index 方法和index_5 方法
输出结果:
thinkphp5无法使用hook::call 调用失败问题及自动注册Hook机制钩子扩展案例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

方法2为什么能 调用 钩子时会调用到_5 这个钩子呢?这个我们后面进行一个讲解。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

讲个小故事
有一天客户说要求实现一个会员的注册模块。
我这里键盘敲了半天完成了代码会员的注册模块。
第二天客户说“现在客户注册没问题了,我想能不能在注册完成后给他添加50积分,让他能进行一些消费。”,我说“没问题”
敲敲敲半天代码我完成了客户的要求。现在注册会员后,有赠送50积分了。
第三天同事问我你前两天不是在系统里弄了个注册模块和赠送积分?我说是啊。同事说“把你这个提交一下svn库,我这边也有个客户想弄个注册模块和赠送积分,还要添加发送邮件验证”,我:“好的,不过你要自己添加发送邮件验证。”代码提交到svn库,
同事敲了半天代码,完成了发送邮件。
第四天我找同事让他提交一下SVN库,让我进行一下代码同步。
第五天另一个客户问我“你们的注册模块有没有会员注册验证手机”,这个我当然只能说有(实际上是没有短信认证),客户说”能装好环境让我测试一下?“,我只能说:“抱歉先生,我们现在还没装好环境,这个我明天给您装好,您在测试一下”,客户说“好”
蹭蹭蹭敲了半天代码在会员注册模块上面加了会员注册手机验证。
第六天客户觉得挺好的。
故事讲到这里。
假设以上会员注册模块的形成是这样的一段代码片段:namespace app\index\controller;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

class member{
function register($username , $password , $tel , $email){
// 第五天加的代码:
if(是否短信验证)
VerifTel($tel , 验证码);
// 第一天写的代码:
if(table('member')->where('username' , $username)->count())
{
$this->error = '用户名已存在'
return false;
}
$uid = 写入数据库得到新的用户UID
....省略大部分代码
// 第二天加的代码:
if(赠送积分)
sendPoints($uid . 赠送积分); // 发送积分50
// 第三天
if(发送邮件)
sendEmail($uid , 邮件);
return true;
}
}从以上我们可以看出,当写完一个功能模块的时候,并不知道客户一些需求的情况,我们会在当前模块中加入代码。
如果一直下去加下去,回头看代码的时候你会发现你的代码实在是太多了。
这个代码是不是可以变得优雅一些文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

如果我们在这个代码中加入钩子机制会是怎么样的呢?
看如下代码namespace app\index\controller;
class member{
function register($username , $password , $tel , $email){
// 我不知道以后会变成什么样,先加个前置钩子,参数:$username,$password,$tel,$email
$param = [ $username,$password,$tel,$email ];
\app\common\Hook::call('member','registerPre',$param);
// 第一天写的代码:
if(table('member')->where('username' , $username)->count())
{
$this->error = '用户名已存在'
return false;
}
$uid = 写入数据库得到新的用户UID
....省略大部分代码
// 我不知道以后会变成什么样,先加个注册成功后的钩子,参数 $uid
$param = [ $uid ];
\app\common\Hook::call('member','registerSuccess',$param);
return true;
}
}
那么问题来了我们怎么去解决钩子的问题?
假设我们的类都已经注册进了钩子里:
我们来完成第二天的事情,如下代码:namespace app\index\hook;
class member{
// 钩子类型,挂入 member 类型钩子,中的registerSuccess钩子
function registerSuccess($uid)
{
if(赠送积分)
sendPoints($uid . 赠送积分); // 发送积分50
}
}
第三天的时候同事要用我的代码怎么办?那么我会告诉他:“我这边已经下了两个钩子,一个是注册前置钩子,一个是注册成功后的钩子”并告诉他钩子类型,名称,还有相应的参数,同事的代码将变成这样namespace app\tongshi\hook;
class member{
// 钩子类型,挂入 member 类型钩子,中的registerSuccess钩子
function registerSuccess($uid)
{
if(发送邮件)
sendEmail($uid , 邮件);
}
}
第五天的时候这样的:namespace app\index\hook;
class member{
// 挂入 member 类型钩子,中的registerSuccess钩子
function registerSuccess($uid)
{
if(赠送积分)
sendPoints($uid . 赠送积分); // 发送积分50
}
// 挂入member 类型钩子中的registerPre钩子
function registerPre()
{
if(是否短信验证)
VerifTel($tel , 验证码);
}
}
回到我们上面讲的“为什么能 调用 钩子时会调用到_5 这个钩子呢?”文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

在钩子机制设计时想到钩子有可能有先后顺序,而设计了一个能写入权重排序的数字,在尾部添加数字,排序从小到大排序,实际的钩子注册会自动删除尾部的“_数字”:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

一:先来讲讲钩子机制
在项目代码中,你认为要扩展(暂时不扩展)的地方放置一个钩子函数,等需要扩展的时候,把需要实现的类和函数挂载到这个钩子上,就可以实现扩展了。
思想就是这样听起来比较笼统。
在二次开发别人写的代码时,如果有钩子机制,作者会告诉你哪些地方给下了钩子,当你扩展时就可以在不改动原代码的情况下进行一个升级扩展。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

本插件扩展配置参数信息:1、jntoo_hook_cache
逻辑值,是否开启钩子编译缓存,开启后只需要编译一次,以后都将成为惰性加载,如果安装了新的钩子,需要先调用Hook::clearCache() 清除缓存
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

2、jntoo_hook_call
逻辑值,是否使用think钩子系统。值为真使用think钩子后,将无法使用权重排序和钩子自动分类,只有类中的方法将自动注册到think钩子机制中文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

3、jntoo_hook_path
某个文件夹下的PHP文件都自动为其注册钩子
配置实现
jntoo_hook_path => [
[
'path'=>'你的路径', // 路径尾部必须加斜杠 "/"
'pattern'=> '规则,类的匹配规则' 例如:'/plugin\\\\module\\\\hook\\\\([0-9a-zA-Z_]+)/'
],
....
]文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

4、jntoo_hook_plugin
多模块目录自动编译,在模块文件夹下加入hook目录,此目录下的php文件会自动注册钩子
配置实现:
'jntoo_hook_plugin' => [
[
'path'=>'你的app模块路径',
'pattern'=> '规则,类的匹配规则' 例如:'/plugin\\\\([0-9a-zA-Z_]+)\\\\hook\\\\([0-9a-zA-Z_]+)/'
],
....
]
请在application/
'app_init'行为处加上:'\\app\\common\\Hook'
例如:
// 应用行为扩展定义文件
return [
// 应用初始化
'app_init' => [
'\\app\\common\\Hook'
],
// 应用开始
'app_begin' => [],
// 模块初始化
'module_init' => [],
// 操作开始执行
'action_begin' => [],
// 视图内容过滤
'view_filter' => [],
// 日志写入
'log_write' => [],
// 应用结束
'app_end' => [],
];
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

请自行创建文件:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

application\index\controller\下钩子文件namespace app\index\controller;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

use app\common\Hook;
use think\Hook AS thinkHook;
class Index
{
public function index()
{
Hook::call('Category' , 'index');
}
}application\index\hook\钩子文件<?php
/**
* Created by PhpStorm.
* User: JnToo
* Date: 2016/11/12
* Time: 1:11
*/
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

namespace app\index\hook;
class Category
{
function index()
{
echo '我是Category类型钩子中的index方法<br>';
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

function index_5()
{
echo '我是Category类型钩子中的index方法,我的权重比较低<br>';
}
}application\common\主文件<?php
/**
* Created by PhpStorm.
* User: JnToo
* Date: 2016/11/11
* Time: 22:57
*/
namespace app\common;
use think\Config;
use think\Hook as thinkHook;
use think\Cache;
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 请在application/
* 'app_init'行为处加上:'\\app\\common\\Hook'
* 例如:
* // 应用行为扩展定义文件
return [
// 应用初始化
'app_init'     => [
'\\app\\common\\Hook'
],
// 应用开始
'app_begin'    => [],
// 模块初始化
'module_init'  => [],
// 操作开始执行
'action_begin' => [],
// 视图内容过滤
'view_filter'  => [],
// 日志写入
'log_write'    => [],
// 应用结束
'app_end'      => [],
];文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

* Class Hook
* @package app\common
*/文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

class Hook
{
/**
* 编译钩子时使用计数器
* @var int
*/
static protected $index = 0;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 添加引用计数
* @var int
*/
static protected $indexAdd = 1;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 已编译好的钩子列表
* @var array
*/
static protected $hookList = array();文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* application/ 文件中加入如下的配置信息
* @var array
*/
static protected $default =[
// 是否开启钩子编译缓存,开启后只需要编译一次,以后都将成为惰性加载,如果安装了新的钩子,需要先调用Hook::clearCache() 清除缓存
'jntoo_hook_cache'=>false,
// 钩子是否使用think钩子系统
'jntoo_hook_call'=>false ,
/**
* 某个文件夹下hook加载,配置文件方法实现
* jntoo_hook_path => [
*     [
*          'path'=>'你的路径', // 路径尾部必须加斜杠 "/"
*          'pattern'=> '规则,类的匹配规则' 例如:'/plugin\\\\module\\\\hook\\\\([0-9a-zA-Z_]+)/'
*     ],
*     ....
* ]
*/
'jntoo_hook_plugin'=>[],
/**
*  多模块目录下自动搜索,配置文件方法实现
* 'jntoo_hook_plugin' => [
*     [,
*          'path'=>'你的app模块路径'
*          'pattern'=> '规则,类的匹配规则' 例如:'/plugin\\\\([0-9a-zA-Z_]+)\\\\hook\\\\([0-9a-zA-Z_]+)/'
*     ],
*     ....
* ]
*/
'jntoo_hook_plugin'=>[],
];文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 提供行为调用
*/
public function run()
{
self::init();
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 注册钩子
* @param $type 钩子类型
* @param $name 钩子名称
* @param $param \Closure|array
*/
static public function add($type , $name , $param , $listorder = 1)
{
$key = strtolower($type .'_'.$name);
isset(self::$hookList[$key]) or self::$hookList[$key] = [];
self::$hookList[$key][$listorder.'_'.self::$indexAdd++] = $param;
ksort(self::$hookList[$key]);
// 兼容
if(Config::get('jntoo_hook_call'))
{
thinkHook::add($name , $param);
}
return;
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 清除编译钩子的缓存
*/
static public function clearCache()
{
// 清楚编译钩子缓存
if(Config::get('jntoo_hook_cache')){
cache('jntoo_hook_cache' , null);
}
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 执行钩子
* @param $type string
* @param $name string
* @param array $array
* @param mixe
*/
static public function call($type , $name , &$array = array())
{
static $_cls = array();文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

$ret = '';
if(Config::get('jntoo_hook_call')){
return thinkHook::listen($name , $array);
}else{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

$key = strtolower($type.'_'.$name);
// 自有的调用方案
if(isset(self::$hookList[$key]))
{文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

foreach(self::$hookList[$key] as $r){
// 闭包处理
$result = '';
if(is_callable($r)){
$result = call_user_func_array($r, $array);
}elseif(is_object($r)){
// 自己定义对象钩子
if(method_exists($r , $name)){
$result = call_user_func_array(array($r , $name), $array);
}
}else{
// 自动搜索出来的钩子
$class = $r['class'];
if(class_exists($class , false)){
// 如果不存在
if($r['filename'])require_once(ROOT_PATH.$r['filename']);
}
if(class_exists($class , false)){
if(!isset($_cls[$class])){
$_cls[$class] = new $class();
}
$func = $r['func'];
$result = call_user_func_array(array($_cls[$class] , $func), $array);
}
}
if($result)$ret.=$result;
}
}
}
return $ret;
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 初始化钩子
*/
static protected function init()
{
// 取钩子的缓存
self::$hookList = self::getCache();
if(!self::$hookList)
{
// 保存在当前变量中
$saveArray = [];文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

// 钩子不存在,先搜索app目录下的模块
//echo APP_PATH;
//echo ROOT_PATH;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

$result = self::searchDir(APP_PATH);
// 先编译此模块
self::compileHook($result , '/app\\\\([0-9a-zA-Z_]+)\\\\hook\\\\([0-9a-zA-Z_]+)/' , $saveArray);
//print_r($saveArray);
// 多模块实现搜索加载
$jntooHook = Config::get('jntoo_hook_plugin');
if($jntooHook){
foreach($jntooHook as $t){
$result = self::searchDir($t['path']);
self::compileHook($result , $t['pattern'] , $saveArray);
}
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

// 单个路径的模块搜索
$jntooHook = Config::get('jntoo_hook_path');
if($jntooHook){
foreach($jntooHook as $t){
$result = [];
self::searchHook($t['path'] , $result);
self::compileHook($result , $saveArray);
}
}
// 编译完成,现在进行一个权重排序
foreach($saveArray as $k=>$t){
ksort($saveArray[$k]);
}
self::setCache($saveArray);
self::$hookList = $saveArray;
}
//print_r(self::$hookList);
$calltype = Config::get('jntoo_hook_call');
// 检测他的调用方法,是否需要注册到think中,不建议注册到 think 中,
// 因为这个系统含有分类的形式,注册进去后将无法使用排序功能
if($calltype){
// 注册进think 钩子中
self::registorThink();
}else{
// 注册系统行为钩子
self::registorCall();
}
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 注册系统行为调用
*/
static protected function registorCall()
{
thinkHook::add('app_init' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'app_init' , $arg);
});
thinkHook::add('app_begin' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'app_begin' , $arg);
});
thinkHook::add('module_init' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'module_init' , $arg);
});
thinkHook::add('action_begin' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'action_begin' , $arg);
});
thinkHook::add('view_filter' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'view_filter' , $arg);
});
thinkHook::add('app_end' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'app_end' , $arg);
});
thinkHook::add('log_write' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'log_write' , $arg);
});
thinkHook::add('response_end' , function( &$params = null ){
$arg = [&$params];
Hook::call('system' , 'response_end' , $arg);
});
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 将钩子注册进thinkHook 钩子中
*/
static protected function registorThink()
{
foreach(self::$hookList as $key=>$list)
{
foreach($list as $r){
thinkHook::add($r['func'] , $r['class']);
}
}
}
/**
* 搜索目录下的钩子文件
* @param $path string
* @param $saveArray array 保存的文件路径
* @return null
*/
static protected function searchHook( $path , &$saveArray)
{
$fp = opendir($path);
if($fp){
while($file = readdir($fp))
{
if(substr($file , -4) == '.php')
{
$saveArray[] = $path.$file;
}
}
}
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 编译钩子,编译后直接保存在静态成员变量 self::$hookList
* @param $filelist array 文件路径
* @param $namespace string 命名空间规则
* @param $saveHook array 保存Hook
* @return null
*/
static protected function compileHook($filelist , $namespace , &$saveHook)
{
$root_path = strtr(ROOT_PATH,'\\' , '/');
//print_r($filelist);
// 当前引用计数
$index = self::$index;
$indexAdd = self::$indexAdd;
foreach ($filelist as $file)
{
require_once($file);
// 获取已经加载的类
$class_list = get_declared_classes();
// 搜索计数器
for($len = count($class_list);$index<$len;$index++)
{
$classname = $class_list[$index];
if(preg_match($namespace , $classname))
{
// 这个类满足我们的需求
$ec = new \ReflectionClass($classname);
// 钩子的类型
$type = basename(strtr($classname , '\\' , '/'));
foreach($ec->getMethods() as $r){
if($r->name[0] != '_' && $r->class == $classname){
// 暂时还不知道怎么实现排序 方法名后面有
$name = $r->name;
$listorder = 1;
if(strpos($name , '_') !== false){
// 存在排序
$temp = explode('_',$name);
$num  = array_pop($temp);
if(is_numeric($num)){
$name = implode('_' , $temp);
$listorder = $num;
}
}
$typename = strtolower($type.'_'.$name);
!isset($saveHook[$typename]) AND $saveHook[$typename] = [];文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

$saveHook[$typename][$listorder.'_'.$indexAdd++] = [
'filename'=>str_replace($root_path,'',$file), // 保存文件路径的好处是方便快速加载,无需在进行路径的查找
'class'=>$classname, // 保存类的名称
'func'=>$r->name,   // 保存方法名
'listorder'=>$listorder // 排序,编译完成后,进行一个权重的排序
];
}
}
}
}
}
self::$index = $index;
self::$indexAdd = $indexAdd;
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* @param $path 搜索模块路径
* @return array
*/
static protected function searchDir( $path )
{
// 目录自动补全
$path = strtr(realpath($path),'\\' , '/');
$char = substr($path,-1);
if( $char != '/' || $char != '\\' ){
$path .= '/';
}
$path .= '*';文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

$dirs = glob($path, GLOB_ONLYDIR );
$result = array();
foreach($dirs as $dir){
if(is_dir($dir .'/hook'))
{
self::searchHook($dir .'/hook/' , $result);
}
}
return $result;
}
/**
* 获取编译好的钩子
* @return bool|array
*/
static protected function getCache()
{
if(Config::get('jntoo_hook_cache')){
// 获取缓存
return cache('jntoo_hook_cache');
}
return false;
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

/**
* 保存编译的缓存
* @param $value array
* @return bool
*/
static protected function setCache( $value )
{
// 设置为永久缓存
if(Config::get('jntoo_hook_cache')){
cache('jntoo_hook_cache' , $value , null);
}
return true;
}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

}文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/10492.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/bc/10492.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定