hook

 
  
其实就是设计模式里面的观察者模式?
把需要处理的事件绑定到一个处理方法,然后这个方法就自动触发处理。

用这种方法可以不需要改动原方法代码,而使用普通方法则需要

例如
function abc(){
 // doth
}

而我们想执行abc后可以自动执行efg方法
这样就需要使用hook把efg绑定到abc

例如加一句 Hook::listen('abc','efg'');

这样执行完 abc就可以自动执行efg方法,而不需要改动abc代码。
//
http://blog.csdn.net/daiyan_csdn/article/details/52175654 class Test { public static function example() { Hook::exec("string"); echo "hello<br />"; Hook::exec("arr"); } } //钩子类 class Hook { static public function exec($type,$model=' ') { if($model=' ') { $m = new hello(); } else { $m = new $model(); } if($type=='string') { $m->string(); } elseif($type=='arr') { $m->arr(); } } } abstract class lan { abstract function string() ; abstract function arr(); } class hello extends lan { public function string() { $str="I am a Hook test<br />"; echo "$str <br />"; } public function arr() { $arr =array(1,2,3,4,5,6); echo "<pre>"; print_r($arr); echo "</pre>"; } } Test::example();
PHP的HOOK是一种思想,而不是一种特定的函数 接口,HOOK的应用多为插件形式。
其原理很简单,就是先加载插件(即include 文件),一般写一个函数来加载,add_hook, 然后要调用即可,调用的时候,不是直接调用插件中的函数,而是通过一个函数来间接调用,这个接口的通常写法是run_hook
以我参与做的一个实际项目来分析吧: 牛域网  ,提供各类的域名注册,包括所有的国别域名与各类的新顶级后缀。牛域网应用到了hook技术,把所有的hook文件都放在include/hook/ 目录下,hook文件的写法如下:
//// hookexample.php ///
if (!defined("NIUYU"))
    die("不可以直接访问");

function hookexample($vars) {

    //  代码

}

add_hook("hookexample",1,"hookexample");
////
细心的读者会问了,add_hook怎么写呢?贴出代码来分析:

function add_hook($hook_name, $priority, $hook_function, $rollback_function = '')
{
    global $hooks; // 注册为全局的,保存牛域网所有用的hooks
    if( !is_array($hooks) )
    {
        $hooks = array(  );
    }
    if( !array_key_exists($hook_name, $hooks) )
    {
        $hooks[$hook_name] = array(  );
    }
    array_push($hooks[$hook_name], array( 'priority' => $priority, 'hook_function' => $hook_function, 'rollback_function' => $rollback_function ));
}

每个hook能执行一系列函数,保存在$hooks[$hook_name] 中。

run_hook
function run_hook($hook_name, $args)
{
    global $hooks;
    if( !is_array($hooks) )
    {
        $hooks = array(  );
    }

    if( !array_key_exists($hook_name, $hooks) )
    {
        return array(  );
    }
    unset($rollbacks);
    $rollbacks = array(  );
    reset($hooks[$hook_name]);
    $results = array(  );
    while( list($key, $hook) = each($hooks[$hook_name]) )
    {
        array_push($rollbacks, $hook['rollback_function']);
        if( function_exists($hook['hook_function']) )
        {

            $res = call_user_func($hook['hook_function'], $args);
            if( $res )
            {
                $results[] = $res;
                hook_log($hook_name, "Hook Completed - Returned True");
            }
            else
            {
                hook_log($hook_name, "Hook Completed - Returned False");
            }
        }
        else
        {
            hook_log($hook_name, "Hook Function %s Not Found", $hook['hook_function']);
        }
    }
    return $results;
}

 



我们先来回顾下原本的开发流程;
产品汪搞出了一堆需求;
当用户注册成功后需要发送短信、发送邮件等等;
然后聪明机智勇敢的程序猿们就一扑而上;
把这些需求转换成代码扔在 用户注册成功 和 跳转到首页 之间;

没有什么能够阻挡;充满创造力的猿们;

<?php
class Test{
    public function index(){
        // 用户注册成功
            /*
              此处是一堆发送短信的代码
            */

           /*
              此处是一堆发送邮件的代码
            */

           /*
              此处是一堆其他功能的代码
            */
        // 前往网站首页
    }
}
$test=new Test();
$test->index();
PHP
如果每个功能都由不同的猿完成的话;
首先面临的就是代码会很杂乱;配合起来会比较麻烦;
那封装成函数吧;一方面会规范整洁写;另外方便重复调用;

没有什么能够阻挡;充满创造力的猿们;

<?php
class Test{
    public function index(){
        // 用户注册成功
        // 发送短信
        sendSms($phone);
        // 发送邮件
        sendSms($email);
        // 其他操作...

        // 前往网站首页
    }
}
/**
 * 发送短信通知
 * @param  integer $phone 手机号
 */
function sendSMS($phone){
    // 此处是发送短信的代码
}
/**
 * 发送邮件通知
 * @param  string $email 邮箱地址
 */
function sendEmail($email){
    // 此处是发送邮件的代码
}
PHP
这时候运营喵表示;
如果能在后台点点按钮就能设置是发邮件还是发短信;那想必是极好的;

没有什么能够阻挡;充满创造力的猿们;

<?php
class Test{
    public function index(){
        // 用户注册成功
        if ('如果设置了发送短信') {
            // 发送短信
            sendSms($phone);
        }

        if ('如果设置了发送邮件') {
            // 发送邮件
            sendSms($email);
        }

        // 其他操作...

        // 前往网站首页
    }
}
/**
 * 发送短信通知
 * @param  integer $phone 手机号
 */
function sendSMS($phone){
    // 此处是发送短信的代码
}
/**
 * 发送邮件通知
 * @param  string $email 邮箱地址
 */
function sendEmail($email){
    // 此处是发送邮件的代码
}
PHP
在一个封闭企业环境下这样搞是没有问题的;
然鹅;我们还有一位开放无私的猿领导要把程序开源出去造福其他猿类;
希望有更多的猿类来参与这个项目;共同开发功能;
如果大家都去改动这套程序;把自己的代码扔在 用户注册成功 和 跳转到首页 之间;
这显然是不靠谱的;想想都混乱的一塌糊涂;

那可不可以大家把自己写的代码放到某个目录下;
然后系统自动的根据配置项把这些代码加载到 用户注册成功 和 跳转到首页 之间呢?
好先定义如下目录

├─plugin // 插件目录
│  ├─plugin1 // 插件1
│  │  ├─config.php // 插件1的配置项
│  │  ├─index.php // 插件1的程序处理内容
│  ├─plugin2
│  │  ├─config.php
│  │  ├─index.php
│  ├─plugin3
│  │  ├─config.php
│  │  ├─index.php
│  ├─...
├─index.php // 业务逻辑
PHP
业务逻辑的代码:

<?php
class Test{
    public function index(){
        // 用户注册成功

        // 获取全部插件
        $pluginList=scandir('./plugin/');
        // 循环插件 // 排除. ..
        foreach ($pluginList as $k => $v) {

            if ($v=='.' || $v=='..') {
                unset($pluginList[$k]);
            }
        }
        echo "简易后台管理<hr>";
        // 插件管理
        foreach ($pluginList as $k => $v) {
            // 获取配置项
            $config=include './plugin/'.$v.'/config.php';
            $word=$config['status']==1 ? '点击关闭' : '点击开启';
            echo $config['title'].'<a href="./index.php?change='.$v.'">'.$word.'</a><br />';
        }
        echo '<hr>';
        // 输出插件内容
        foreach ($pluginList as $k => $v) {
            // 获取配置项
            $config=include './plugin/'.$v.'/config.php';
            if ($config['status']==1) {
                include './plugin/'.$v.'/index.php';
                // 运行插件
                Hook::run($v);
            }
        }
        // 前往网站首页
    }
}
// 插件类
class Hook{
    // 注册添加插件
    public static function add($name,$func){
        $GLOBALS['hookList'][$name][]=$func;
    }
    // 执行插件
    public static function run($name,$params=null){
        foreach ($GLOBALS['hookList'][$name] as $k => $v) {
            call_user_func($v,$params);
        }
    }
}
// 更改插件状态
if (isset($_GET['change'])) {
    // 获取到配置项
    $config=include './plugin/plugin'.substr($_GET['change'],-1).'/config.php';
    // 如果是开启 那就关闭 如果是关闭 则开启
    $config['status']=$config['status']==1 ? 0: 1;
    // 将更改后的配置项写入到文件中
    $str="<?php \\r\\n return ".var_export($config,true).';';
    file_put_contents('./plugin/'.$_GET['change'].'/config.php', $str);
    header('Location:./');
}
$test=new Test();
$test->index();
PHP
插件配置项代码:

<?php
 return array (
  'status' => 1, // 定义状态 1表示开启  0表示关闭
  'title' => '发送短信', // 插件的名称
);
PHP
插件的内容:

<?php

Hook::add('plugin1',function(){
    echo '发送短信的内容<br />';
});
PHP

 

demo简单理解钩子

我们想一下项目的开发过程:

1.产品经理根据用户需求(甲方乙方都行)搞了一大堆的需求.
2.当用户注册成功后需要发短信,邮箱等等验证.
3.工程师蜂拥而上开始写代码
4.写什么?把产品经理提出的需求实现,转换成代码在"用户注册成功"和"跳转页面"之间

工程师ing....

class Demo{
    public function index(){
        //用户注册成功

        /*
            发送短信的代码
        */
        
        /*
            发送邮箱的代码
        */

        /*
            其他功能balabla
        */
    
        //页面跳转到网站首先等等
    }
}

$demo = new Demo(); //new一个对象出来
$demo->index();     //调用执行就可以了
如果这段代码的几块功能块由不同的工程师完成;
1.代码混乱
2.配合麻烦

封装成函数?
1.代码会整洁一些
2.方便重复调用

工程师ing....

class Test{
    public function index(){
        // 用户注册成功
 
        // 发送短信
        sendSms($phone);
        // 发送邮件
        sendSms($email);
        // 其他操作...
         
        // 前往网站首页
    }
}
 
/**
 * 发送短信通知
 * @param  integer $phone 手机号
 */
function sendSMS($phone){
    // 此处是发送短信的代码
}
 
/**
 * 发送邮件通知
 * @param  string $email 邮箱地址
 */
function sendEmail($email){
    // 此处是发送邮件的代码
}

这时甲方或不懂代码的产品狗就会说,好难用,也理解不了你们这帮程序员竟然说这样好用?我会的就只有点击,双击,鼠标中间都不会用.搞毛呢?

工程师ing....

<?php
 
class Test{
    public function index(){
        // 用户注册成功
 
        if ('如果设置了发送短信') {
            // 发送短信
            sendSms($phone);
        }
         
        if ('如果设置了发送邮件') {
            // 发送邮件
            sendSms($email);
        }
         
        // 其他操作...
         
        // 前往网站首页
    }
}
 
/**
 * 发送短信通知
 * @param  integer $phone 手机号
 */
function sendSMS($phone){
    // 此处是发送短信的代码
}
 
/**
 * 发送邮件通知
 * @param  string $email 邮箱地址
 */
function sendEmail($email){
    // 此处是发送邮件的代码
}
自己发开写个简单的文档给产品狗或者甲方问题不大,同为工程狗的我们也能看和开发个差不多.

如果我们想开源出去,想让更多的人参与进来完善功能.这样显然就不合适了.

那可不可以把自己写的代码放在某个目录下?
然后系统自动的 根据配置项把这些代码加载到"用户注册成功"和"跳转到首页"之间?

工程师ing....

├─plugin // 插件目录
│  ├─plugin1 // 插件1
│  │  ├─config.php // 插件1的配置项
│  │  ├─index.php // 插件1的程序处理内容
│  ├─plugin2
│  │  ├─config.php
│  │  ├─index.php
│  ├─plugin3
│  │  ├─config.php
│  │  ├─index.php
│  ├─...
├─index.php // 业务逻辑
业务逻辑:

class Test{
    public function index(){
        // 用户注册成功
             
        // 获取全部插件
        $pluginList=scandir('./plugin/');
        // 循环插件 // 排除. ..
        foreach ($pluginList as $k => $v) {
             
            if ($v=='.' || $v=='..') {
                unset($pluginList[$k]);
            }
        }
        echo "简易后台管理<hr>";
        // 插件管理
        foreach ($pluginList as $k => $v) {
            // 获取配置项
            $config=include './plugin/'.$v.'/config.php';
            $word=$config['status']==1 ? '点击关闭' : '点击开启';
            echo $config['title'].'<a href="./index.php?change='.$v.'">'.$word.'</a><br />';
        }
        echo '<hr>';
        // 输出插件内容
        foreach ($pluginList as $k => $v) {
            // 获取配置项
            $config=include './plugin/'.$v.'/config.php';
            if ($config['status']==1) {
                include './plugin/'.$v.'/index.php';
                // 运行插件
                Hook::run($v);
            }
        }
 
        // 前往网站首页
    }
}
 
// 插件类
class Hook{
    // 注册添加插件
    public static function add($name,$func){
        $GLOBALS['hookList'][$name][]=$func;
    }
 
    // 执行插件
    public static function run($name,$params=null){
        foreach ($GLOBALS['hookList'][$name] as $k => $v) {
            call_user_func($v,$params);
        }
    }
}
 
// 更改插件状态
if (isset($_GET['change'])) {
    // 获取到配置项
    $config=include './plugin/plugin'.substr($_GET['change'],-1).'/config.php';
    // 如果是开启 那就关闭 如果是关闭 则开启
    $config['status']=$config['status']==1 ? 0: 1;
    // 将更改后的配置项写入到文件中
    $str="<?php \r\n return ".var_export($config,true).';';
    file_put_contents('./plugin/'.$_GET['change'].'/config.php', $str);
    header('Location:./');
}
 
$test=new Test();
$test->index();
插件配置项:

return array (
  'status' => 1, // 定义状态 1表示开启  0表示关闭
  'title' => '发送短信', // 插件的名称
);
插件内容:

Hook::add('plugin1',function(){
    echo '发送短信的内容<br />';
});
当然,这个只是简单的理解钩子.像国内的discuz,wordpress等等这些源码,都是很厉害,我个人使用的就是wordpress的blog.非常好用,切代码写的相当漂亮.

 

http://blog.163.com/zhu329599788@126/blog/static/66693350201699349703/
什么是钩子 大家想必听过插件,wordpress插件特别多,这个就是用钩子机制实现的。 当代码在运行的过程中,我们预先在运行的几个特殊点里执行一些特殊方法:例如在运行方法(例如Blog
::add的add方法)之前记录输入参数、运行方法之后记录处理结果,这个de >运行方法之前de>、de >运行方法之后de>就是简单的钩子(挂载点),我们在这个钩子上放置钩子函数(记录输入参数、记录处理结果),执行一些和程序运行不相关的任务。 <?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));de> 如果在de >运行方法之前de>放置的是一个de >OnBeforeRunActionCallback()de>的方法,这个方法可能最开始的时候是空的,但我们以后就可以不去修改原有代码,直接在de >OnBeforeRunActionCallback()de>里面加代码逻辑就可以了,例如记录日志、参数过滤等等。 de ><?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)); }de> 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一个钩子函数,等需要扩展的时候,把需要实现的类和函数挂载到这个钩子上,就可以实现扩展了。 原理 实际的钩子一般设计为一个类Hook,该类提供注册插件到钩子(add_hook)、触发钩子方法(trigger_hook)。注册插件的时候将插件所要运行的可执行方法存储到钩子对应的数组里面。 de > $_listeners = array( 'OnBeforeRunAction' => array( 'callback1', 'callback2', 'callback3', ), ); //提前注册插件到钩子 add_hook('OnBeforeRunAction', 'callback4'); //特定地方执行钩子 trigger_hook('OnBeforeRunAction');de> 当触发钩子的时候,将遍历de >OnBeforeRunActionde>里注册的回调方法,执行对应的回调方法,实现动态扩展功能。注册的钩子方法一般是匿名函数: de >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); } } } } }de> 如何实现 简单的 1、插件类Hook:提供注册插件和执行插件的方法,实际是往一个数组里存挂载点对应的可执行方法。 2、在某个配置文件或者函数里统一注册插件。 de >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); }de> 用法举例: de >//添加钩子 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();de> 带安装/卸载的 钩子类初始化的时候去注册已经开启的插件(如数据库记录);全局合适的时候设置挂载点;运行到合适的时候触发挂载点注册的事件。 1、插件类Hook:提供注册插件和执行插件的方法,实际是往一个数组里存挂载点对应的可执行方法。 2、插件类增加一个初始化的方法,去数据查找已经安装的插件,运行插件必须执行的注册方法(reg),注册插件方法注册钩子到挂载点。 3、固定把插件放在某个目录,并安照一定规范写配置文件。后台有插件列表页面,遍历指定目录下的插件。当安装的时候,将插件信息记录到数据库,卸载的时候删除数据库记录信息。 de ><?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)."/"; } }

 

什么是钩子

大家想必听过插件,wordpress插件特别多,这个就是用钩子机制实现的。

当代码在运行的过程中,我们预先在运行的几个特殊点里执行一些特殊方法:例如在运行方法(例如Blog::add的add方法)之前记录输入参数、运行方法之后记录处理结果,这个de >运行方法之前de>、de >运行方法之后de>就是简单的钩子(挂载点),我们在这个钩子上放置钩子函数(记录输入参数、记录处理结果),执行一些和程序运行不相关的任务。

de  ><?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));de>

如果在de >运行方法之前de>放置的是一个de >OnBeforeRunActionCallback()de>的方法,这个方法可能最开始的时候是空的,但我们以后就可以不去修改原有代码,直接在de >OnBeforeRunActionCallback()de>里面加代码逻辑就可以了,例如记录日志、参数过滤等等。

de  ><?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));
}de>

在项目代码中,你认为要扩展(暂时不扩展)的地方放置一个钩子函数,等需要扩展的时候,把需要实现的类和函数挂载到这个钩子上,就可以实现扩展了。

原理

实际的钩子一般设计为一个类Hook,该类提供注册插件到钩子(add_hook)、触发钩子方法(trigger_hook)。注册插件的时候将插件所要运行的可执行方法存储到钩子对应的数组里面。

de  >
$_listeners = array(
    'OnBeforeRunAction' => array(
        'callback1',
        'callback2',
        'callback3',
    ),
);

//提前注册插件到钩子
add_hook('OnBeforeRunAction', 'callback4');

//特定地方执行钩子
trigger_hook('OnBeforeRunAction');de>

当触发钩子的时候,将遍历de >OnBeforeRunActionde>里注册的回调方法,执行对应的回调方法,实现动态扩展功能。注册的钩子方法一般是匿名函数:

de  >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);
                }
            }
        }
    }
}de>

如何实现

简单的

1、插件类Hook:提供注册插件和执行插件的方法,实际是往一个数组里存挂载点对应的可执行方法。
2、在某个配置文件或者函数里统一注册插件。

de  >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     * @paramstring $hook     * @paramarray $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     * @paramstring $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     * @paramstring $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);
    }de>

用法举例:

de  >//添加钩子
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();de>

带安装/卸载的

钩子类初始化的时候去注册已经开启的插件(如数据库记录);全局合适的时候设置挂载点;运行到合适的时候触发挂载点注册的事件。

1、插件类Hook:提供注册插件和执行插件的方法,实际是往一个数组里存挂载点对应的可执行方法。
2、插件类增加一个初始化的方法,去数据查找已经安装的插件,运行插件必须执行的注册方法(reg),注册插件方法注册钩子到挂载点。
3、固定把插件放在某个目录,并安照一定规范写配置文件。后台有插件列表页面,遍历指定目录下的插件。当安装的时候,将插件信息记录到数据库,卸载的时候删除数据库记录信息。

de  ><?php
/** * @fileplugin.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 注册事件     * @paramstring $event 事件     * @paramobject ro function $classObj 类实例 或者 匿名函数     * @paramstring $method 方法名字     */
    public static function reg($event,$classObj,$method = ""){
        if(!isset(self::$_listen[$event]))
        {
            self::$_listen[$event] = array();
        }
        self::$_listen[$event][] = array($classObj,$method);
    }

    /**     * @brief 显示已注册事件     * @paramstring $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 触发事件     * @paramstring $event 事件     * @parammixed  $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 获取全部插件     * @paramstring $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 插件视图渲染有布局     * @paramstring $view 视图名字     * @paramarray  $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 插件视图渲染去掉布局     * @paramstring $view 视图名字     * @paramarray  $data 视图里面的数据     */
    public function view($view,$data = array()){
        self::controller()->layout = "";
        $this->redirect($view,$data);
    }

    /**     * @brief 插件物理目录     * @paramstring 插件路径地址     */
    public function path(){
        return plugin::path().get_class($this)."/";
    }

    /**     * @brief 插件WEB目录     * @paramstring 插件路径地址     */
    public function webPath(){
        return plugin::webPath().get_class($this)."/";
    }
}de>

转载于:https://www.cnblogs.com/lichihua/p/8369117.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值