WordPress源码深度解析之:`WordPress`的`deactivation hook`:`register_deactivation_hook()`的底层实现。

各位观众老爷,晚上好!我是你们的老朋友,代码搬运工,今天咱们来聊聊WordPress里一个不太起眼,但又至关重要的家伙——register_deactivation_hook()

咳咳,先清清嗓子,咱们进入正题。

一、什么是 deactivation hook

想象一下,你安装了一个插件,它在你网站上提供了一些超酷的功能,比如自定义文章类型、短代码或者其他的魔法。但是有一天,你决定不再使用这个插件了,你点了“停用”。

这个时候,如果这个插件仅仅是被停用,而没有做任何清理工作,可能会留下一些“烂摊子”,比如数据库里残留的数据,或者是一些不再需要的选项。

deactivation hook 就是用来解决这个问题的。它允许插件在被停用的时候执行一些代码,用来清理这些“烂摊子”,让你的网站保持干净整洁。

二、register_deactivation_hook() 的作用

register_deactivation_hook() 是 WordPress 提供的一个函数,用来注册一个函数,这个函数将在插件被停用的时候执行。 简单来说,就是告诉 WordPress:“嘿,当这个插件要被停用的时候,记得运行一下这个函数!”

三、register_deactivation_hook() 的用法

register_deactivation_hook() 函数的语法如下:

register_deactivation_hook( string $file, callable $function );
  • $file: 插件的主文件路径,通常是 __FILE__ 常量。
  • $function: 一个可调用的函数,可以是函数名、类方法,或者闭包。

下面是一个简单的例子:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A simple plugin that demonstrates the deactivation hook.
 */

// 插件激活时创建数据库表
function my_awesome_plugin_activate() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_awesome_table';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
        name varchar(200) NOT NULL,
        PRIMARY KEY  (id)
    ) $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );
}
register_activation_hook( __FILE__, 'my_awesome_plugin_activate' );

// 插件停用时删除数据库表
function my_awesome_plugin_deactivate() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_awesome_table';
    $sql = "DROP TABLE IF EXISTS $table_name";
    $wpdb->query( $sql );
}
register_deactivation_hook( __FILE__, 'my_awesome_plugin_deactivate' );

在这个例子中,my_awesome_plugin_deactivate() 函数会在 My Awesome Plugin 被停用时执行,它会删除插件创建的数据库表。

四、register_deactivation_hook() 的底层实现

好了,前面都是开胃小菜,现在才是重头戏。让我们一起深入 register_deactivation_hook() 的源码,看看它到底是怎么工作的。

首先,register_deactivation_hook() 函数位于 wp-includes/plugin.php 文件中。

function register_deactivation_hook( $file, $function ) {
    if ( ! is_string( $file ) ) {
        return;
    }

    if ( ! is_callable( $function ) ) {
        return;
    }

    $file = plugin_basename( $file );

    $GLOBALS['wp_filter']['deactivate_' . $file ] = array(
        99 => array(
            array(
                'function' => $function,
                'accepted_args' => 1,
            ),
        ),
    );
}

代码看起来很简单,对不对?我们来一步一步分解:

  1. 参数验证:

    • is_string( $file ): 确保 $file 是一个字符串,也就是插件的主文件路径。
    • is_callable( $function ): 确保 $function 是一个可调用的函数。

    如果任何一个验证失败,函数会直接返回,不做任何操作。这是一种防御性编程的体现,可以防止因为错误的参数导致程序出错。

  2. 获取插件 basename:

    • $file = plugin_basename( $file ): plugin_basename() 函数用于从插件的主文件路径中提取插件的 basename。例如,如果 $file/path/to/wp-content/plugins/my-awesome-plugin/my-awesome-plugin.php,那么 $file 就会变成 my-awesome-plugin/my-awesome-plugin.php
    • plugin_basename()函数内部会使用wp_normalize_path()对路径进行标准化处理,保证路径格式一致,避免因路径格式问题导致Hook失效。
  3. 注册钩子:

    • $GLOBALS['wp_filter']['deactivate_' . $file ] = ...: 这行代码是核心。它将 $function 注册到 WordPress 的全局 $wp_filter 数组中。

    • $wp_filter 是 WordPress 用于存储所有钩子的一个全局数组。它的结构比较复杂,但我们可以简单地理解为:

      $wp_filter = [
          'hook_name' => [
              priority => [
                  [
                      'function' => 'callback_function',
                      'accepted_args' => number_of_arguments,
                  ],
              ],
          ],
      ];
      • hook_name 是钩子的名称,例如 deactivate_my-awesome-plugin/my-awesome-plugin.php
      • priority 是钩子的优先级,数字越小,优先级越高。
      • callback_function 是钩子对应的回调函数。
      • accepted_args 是回调函数接受的参数个数。
    • 在这行代码中,我们将 $function 注册到 deactivate_ 开头的钩子上,优先级为 99accepted_args1。这意味着当插件被停用时,WordPress 会执行所有 deactivate_ 开头的钩子,并且会按照优先级从低到高执行。

五、插件停用时钩子的执行

那么,插件停用时,这些钩子是怎么被执行的呢?

当你在 WordPress 后台停用一个插件时,WordPress 会调用 deactivate_plugins() 函数。这个函数位于 wp-admin/includes/plugin.php 文件中。

deactivate_plugins() 函数会遍历所有要停用的插件,然后对每个插件执行以下操作:

  1. 调用 do_action()

    • do_action( 'deactivate_' . $plugin ): 这行代码会触发所有 deactivate_ 开头的钩子。$plugin 是插件的 basename。
  2. do_action() 函数的内部机制:

    • do_action() 函数会从 $wp_filter 数组中找到所有与 'deactivate_' . $plugin 相关的钩子,然后按照优先级顺序执行它们。

    • do_action() 函数会根据 $wp_filter 数组中存储的信息,调用对应的回调函数,并且会将一些参数传递给回调函数。

    • 对于 deactivation hookdo_action() 函数会将插件的 basename 作为参数传递给回调函数。

六、总结

现在,我们已经深入了解了 register_deactivation_hook() 的底层实现。让我们来总结一下:

步骤 描述 代码示例
1. 注册 deactivation hook 使用 register_deactivation_hook() 函数注册一个函数,该函数将在插件停用时执行。 register_deactivation_hook( __FILE__, 'my_awesome_plugin_deactivate' );
2. register_deactivation_hook() 内部 register_deactivation_hook() 会将回调函数存储到 $wp_filter 全局数组中,钩子名称以 deactivate_ 开头。 $GLOBALS['wp_filter']['deactivate_' . $file ] = ...;
3. 插件停用时 当插件被停用时,WordPress 会调用 deactivate_plugins() 函数。 deactivate_plugins( $plugins, $silent, $network_wide );
4. 触发 deactivate 钩子 deactivate_plugins() 函数会使用 do_action( 'deactivate_' . $plugin ) 触发所有 deactivate_ 开头的钩子。 do_action( 'deactivate_' . $plugin );
5. 执行回调函数 do_action() 函数会从 $wp_filter 数组中找到所有与 'deactivate_' . $plugin 相关的钩子,并按照优先级顺序执行它们。 (内部实现涉及遍历 $wp_filter 数组,并调用 call_user_func_array() 执行回调函数)

七、注意事项

  • 避免长时间运行的任务: deactivation hook 应该快速完成,避免长时间运行的任务,否则可能会导致插件停用过程卡顿。如果需要执行长时间运行的任务,可以考虑使用异步处理,例如使用 WordPress 的 WP_Cron 或者消息队列。

  • 清理所有相关数据: deactivation hook 的目的是清理插件留下的所有“烂摊子”,包括数据库表、选项、缓存等等。确保清理所有相关数据,让你的网站保持干净整洁。

  • 考虑用户数据: 在清理数据之前,要考虑用户数据。如果插件存储了用户数据,应该提供一个选项,让用户可以选择是否删除这些数据。

  • 网络激活和单站点激活: 请注意区分网络激活 (Network Activate) 和单站点激活 (Single Site Activate)。如果是网络激活的插件,在停用时需要考虑如何处理所有站点的相关数据。

八、高级用法

  • 使用闭包: 你可以使用闭包来定义 deactivation hook,这样可以更方便地访问插件内部的变量和函数。

    register_deactivation_hook( __FILE__, function() {
        // 在这里访问插件内部的变量和函数
    } );
  • 使用类方法: 你可以使用类方法来定义 deactivation hook,这样可以更好地组织你的代码。

    class My_Awesome_Plugin {
        public static function deactivate() {
            // 在这里执行清理操作
        }
    }
    
    register_deactivation_hook( __FILE__, array( 'My_Awesome_Plugin', 'deactivate' ) );

九、总结的总结

总而言之,register_deactivation_hook() 是一个非常有用的函数,它可以让你在插件被停用时执行一些代码,清理插件留下的“烂摊子”。理解 register_deactivation_hook() 的底层实现,可以帮助你更好地使用它,并且可以让你更好地理解 WordPress 的插件机制。

好了,今天的讲座就到这里。希望大家有所收获!如果有什么问题,欢迎在评论区留言。咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注