写一个php钩子类,监听操作行为、扩展应用,php-hook钩子开发原理,钩子学习看这个就够了!!!

一个产品经常需要修改需求、增加功能,就需要频繁修改逻辑,多个人开发的话一个地点频繁操作可能还冲突、看着心烦~哈哈哈哈哈

所以,钩子监听这时候就很有趣了,在行为前后都可以加上需要的钩子监听,分工合作,废话少说,先上来一个hook类(类似tp的简单版):

<?php

/**
 * 钩子中心-核心部分
 * Class Hook
 */
class Hook
{
    //注册后保存下来
    public static $hooks = [];

    //注册插件
    public static function add($hook_name, $class)
    {
        $hook_name = strtolower($hook_name);
        if (isset(self::$hooks[$hook_name])) {
            self::$hooks[$hook_name] = array_merge(self::$hooks[$hook_name], $class);
        } else {
            self::$hooks[$hook_name][] = $class;
        }
        self::$hooks[$hook_name] = array_unique(self::$hooks[$hook_name]);
    }

    //批量导入
    public static function batch_add($plugins = [])
    {
        if (!empty($plugins)) {
            //以新的key覆盖旧的hook
            self::$hooks = $plugins + self::$hooks;
            //去重
            if (self::$hooks){
                foreach (self::$hooks as $hook=>&$class){
                    $class = array_unique($class);
                }
                unset($class);
            }
        }
    }

    //获取插件值
    public static function get($hook_name)
    {
        $hook_name = strtolower($hook_name);
        if (!isset(self::$hooks[$hook_name])) {
            return false;
        }
        return self::$hooks[$hook_name];
    }

    //监听,返回一个数组结果
    public static function listen($hook_name, &$param = null)
    {
        $hook_name = strtolower($hook_name);
        if (!self::get($hook_name)) {
            return false;
        }

        $result = [];
        foreach (self::get($hook_name) as $key => $class) {
            $result[$key] = static::exec($class, !empty($param['method']) ? $param['method'] : 'main', $param);
            if ($result[$key] === false) {
                break;
            }
        }
        return $result;
    }
    
    //引用模式,可以修改参数
        public static function listen_p($hook_name, &$param = null)
        {
            $hook_name = strtolower($hook_name);
            if (!self::get($hook_name)) {
                return false;
            }
            $class = self::get($hook_name)[0];
        
            return static::exec($class, !empty($param['method']) ? $param['method'] : 'main', $param);
        }

    //执行插件行为
    public static function exec($class, $function = 'main', $param = null)
    {
        if (is_object($class)) {
            if (!is_callable([$class, $function])) {
                $ret = false;
            } else {
                $ret = $class->$function($param);
            }
        } else {
            if (!class_exists($class)){
                return false;
            }
            $obj = new $class();

            if (!is_callable([$class, $function])) {
                $ret = false;
            } else {

                $ret = $obj->$function($param);
            }
        }
        return $ret;
    }

}


原理其实很简单,就是先载入文件(最好是类,本案例用类做扩展),然后根据监听的参数实例化对象并执行


所以,我写了个简单的加载目下xxx.php文件的文件loadfile.php:

<?php
$dir = __DIR__.'/plugins/';
$files = scandir($dir);

if (!empty($files)){
    foreach ($files as $file){
        if ($file=='.' || $file=='..'){
            continue;
        }
        if (is_file(str_replace("\\",'/',$dir.$file))){
            include_once str_replace("\\",'/',$dir.$file);
        }
    }
}

新建plugins目录,新增Login.php:

<?php

namespace hook\plugins;
class Login
{
    //默认执行的方法
    public function main($param = [])
    {
        if (!empty($param['way'])) {
            if ($param['way'] == 'test') {
                return 'test';
            }
        }
        return '';
    }

    public function before($param = [])
    {
        echo 'This is before'.PHP_EOL;
    }

    public function after($param=[])
    {
        echo 'This is after'.PHP_EOL;
    }
}

在plugins目录新增Message.php类:

<?php


namespace hook\plugins;


class Message
{
    public function main($params=[])
    {
        if (!empty($params['call'])&&$params['call']=='sms'){
            return $this->send_sms($params);
        }else{
            //todo
            echo '这是消息钩子'.PHP_EOL;
        }

    }

    //发送短信
    public function send_sms($param=null)
    {
        echo '开始发送短信啦'.PHP_EOL;
        return 'ok';
    }
}


上面两个文件作为测试文件写好了基本的测试函数,然后测试看看,新建index.php:

<?php
//开启全报错
error_reporting(E_ALL);
//引入文件
include_once __DIR__.'/loadfile.php';
//引入核心钩子类
include_once __DIR__.'/Hook.php';


//注册login钩子,监听\\hook\\plugins\\Login

Hook::add('Login',"\\hook\\plugins\\Login");

//测试一下
$param = [
    'method'=>'before'
];
Hook::listen('login',$param);

$param = [
    'method'=>'after'
];
Hook::listen('login',$param);

//注册一个message钩子,监听一下\\hook\\plugins\\Message类
Hook::add('message',"\\hook\\plugins\\Message");

//默认监听,没有参数会执行main函数
Hook::listen('message');
//发送个短信吧
$param = [
    'call'=>'sms'
];
Hook::listen('message',$param);


demo的目录结构如下:

微信截图_20210805225057.png



执行看看效果:



微信截图_20210805224804.png


说明:一个钩子可以监听多个类,要实例化就先要确保被加载到解析器中,也就是先include或者require进来,然后才能根据你需要监听的类实例化,最后执行指定的方法


如果系统有些固定的类需要监听,可以先定义一下plugin,然后初始化时批量注册到钩子,后面监听就可以了,参考tp添加了一个批量添加的简单方法在Hook.php:

//批量导入
public static function batch_add($plugins = [])
{
    if (!empty($plugins)) {
        //以新的key覆盖旧的hook
        self::$hooks = $plugins + self::$hooks;
        //去重
        if (self::$hooks){
            foreach (self::$hooks as $hook=>&$class){
                $class = array_unique($class);
            }
            unset($class);
        }
    }
}

然后可以定义一下钩子数组:

$plugins = array (
    'init' =>
        array (
            0=>'\\hook\\plugins\\Init'
    ),
    'login' =>
        array (
            0 => '\\hook\\plugins\\Login',
        ),
    'message' =>
        array (
            0 => '\\hook\\plugins\\Message',
        ),
);
//批量添加注册
Hook::batch_add($plugins);
//测试监听
Hook::listen('init');//在demo附件有哦



附件demo:

hook_demo.zip


开发复杂点的钩子就支持复杂的应用开发了,比如我们系统的插件功能:

微信截图_20210805232418.png

评论/留言