阐述 WordPress `register_activation_hook()` 函数源码:它如何将钩子函数注册到 `shutdown` 钩子中,以在插件激活时执行一次。

各位程序猿/媛们,早上好/下午好/晚上好!今天咱们来聊聊 WordPress 插件激活时的一个神奇的函数:register_activation_hook()。别看它名字长,其实它干的事情非常简单,就是让你在插件被激活的时候,能执行一次你自定义的代码。

咱们先从一个简单的例子开始,然后一点点深入到源码里,看看 WordPress 到底是怎么实现这个功能的。

一、 简单的例子:你好,世界!

假设我们有一个插件,名字叫 "Hello World Plugin"。我们希望在插件激活的时候,在数据库里创建一个表,用来记录一些数据。

<?php
/**
 * Plugin Name: Hello World Plugin
 * Description: A simple plugin to demonstrate activation hook.
 * Version: 1.0.0
 * Author: Your Name
 */

// 激活时执行的函数
function hello_world_activate() {
  global $wpdb;
  $table_name = $wpdb->prefix . 'hello_world';

  $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(255) NOT NULL,
    PRIMARY KEY  (id)
  ) $charset_collate;";

  require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
  dbDelta( $sql );

  add_option( 'hello_world_version', '1.0.0' ); // 记录插件版本
}

// 注册激活钩子
register_activation_hook( __FILE__, 'hello_world_activate' );

// 卸载时执行的函数 (可选,但强烈建议)
function hello_world_deactivate() {
  // 清理工作,比如删除数据库表、删除选项等
  global $wpdb;
  $table_name = $wpdb->prefix . 'hello_world';
  $sql = "DROP TABLE IF EXISTS $table_name";
  $wpdb->query( $sql );

  delete_option( 'hello_world_version' );
}

register_deactivation_hook( __FILE__, 'hello_world_deactivate' );

这段代码做了什么呢?

  1. hello_world_activate() 函数:这个函数包含了插件激活时要执行的代码,这里是创建了一个数据库表。
  2. register_activation_hook( __FILE__, 'hello_world_activate' ):这行代码是关键!它告诉 WordPress,当 "Hello World Plugin" 被激活的时候,执行 hello_world_activate() 函数。
  3. hello_world_deactivate() 函数:这个函数包含了插件停用时要执行的代码,这里是删除了数据库表。
  4. register_deactivation_hook( __FILE__, 'hello_world_deactivate' ):这行代码是关键!它告诉 WordPress,当 "Hello World Plugin" 被停用的时候,执行 hello_world_deactivate() 函数。

很简单,对吧? 现在,让我们深入到 register_activation_hook() 的源码里,看看 WordPress 是如何实现这个功能的。

二、 register_activation_hook() 源码剖析

register_activation_hook() 函数位于 wp-includes/plugin.php 文件中。 它的源码如下:

/**
 * Registers a plugin's activation hook.
 *
 * @since 2.0.0
 *
 * @param string   $file      The filename of the plugin requesting the activation hook.
 * @param callable $function  The name of the function to call on activation.
 */
function register_activation_hook( string $file, callable $function ): void {
  if ( is_array( $function ) || is_string( $function ) && strpos( $function, '::' ) !== false ) {
    $callable = $function;
  } else {
    $callable = strtolower( $function );
  }

  $GLOBALS['wp_filter']['activate_' . plugin_basename( $file )][] = array(
    'function' => $callable,
    'accepted_args' => 0
  );
}

看起来很短,但信息量很大。 让我们一行一行地分析:

  1. function register_activation_hook( string $file, callable $function ): void:定义了函数,接受两个参数:
    • $file:插件的主文件路径,通常是 __FILE__
    • $function:要执行的函数名 (或可调用对象)。
  2. if ( is_array( $function ) || is_string( $function ) && strpos( $function, '::' ) !== false ) { ... } else { ... }:这段代码检查 $function 是否是一个数组 (表示类方法) 或包含 :: 的字符串 (也表示类方法)。 如果是,则直接使用 $function;否则,将其转换为小写。 为什么要转换成小写? 这是为了兼容性,因为在 WordPress 的早期版本中,函数名不区分大小写。 虽然现在已经区分大小写了,但为了向后兼容,还是保留了这个转换。
  3. $GLOBALS['wp_filter']['activate_' . plugin_basename( $file )][] = array( ... ): 这是最关键的一行代码!它将激活钩子注册到 $GLOBALS['wp_filter'] 全局数组中。

    • $GLOBALS['wp_filter']: 是 WordPress 用于存储所有过滤器和动作的全局数组。
    • 'activate_' . plugin_basename( $file ): 这部分创建了一个唯一的钩子名称。 plugin_basename( $file ) 函数会从插件主文件路径中提取插件的文件名 (例如,对于 hello-world-plugin/hello-world-plugin.php,会提取 hello-world-plugin/hello-world-plugin.php)。 然后,在前面加上 activate_,就得到了一个唯一的激活钩子名称 (例如,activate_hello-world-plugin/hello-world-plugin.php)。
    • [] = array( ... ): 这表示将一个新的元素添加到 $GLOBALS['wp_filter']['activate_' . plugin_basename( $file )] 数组中。
    • array( 'function' => $callable, 'accepted_args' => 0 ): 这个数组包含了要执行的函数 ($callable) 和它接受的参数数量 (0,表示不接受任何参数)。

总结一下: register_activation_hook() 函数实际上并没有直接执行任何代码。 它只是将你的函数注册到了 $GLOBALS['wp_filter'] 全局数组中,等待 WordPress 在插件激活时去调用。

三、 插件激活时发生了什么?

那么,WordPress 是什么时候以及如何调用我们注册的激活钩子呢?

答案在 wp-admin/includes/plugin.php 文件中的 activate_plugin() 函数中。 这个函数负责激活插件。 让我们看看 activate_plugin() 函数的简化版:

function activate_plugin( string $plugin, string $redirect = '', bool $network_wide = false, bool $silent = false ): WP_Error|void {
  // ... 一些检查和验证 ...

  do_action( 'activate_' . $plugin, $network_wide );

  // ... 一些更新选项的操作 ...
}

看到了吗? 在 activate_plugin() 函数中,WordPress 使用 do_action() 函数来触发一个动作:'activate_' . $plugin。 这个 $plugin 就是插件的文件名 (例如,hello-world-plugin/hello-world-plugin.php)。

do_action() 函数会遍历 $GLOBALS['wp_filter'] 数组,找到所有与 'activate_' . $plugin 关联的函数,并依次执行它们。 这其中就包括了我们用 register_activation_hook() 注册的 hello_world_activate() 函数。

四、 register_deactivation_hook() 源码剖析

register_activation_hook() 类似,register_deactivation_hook() 注册插件停用时需要执行的函数。它也位于 wp-includes/plugin.php 文件中。 源码如下:

/**
 * Registers a plugin's deactivation hook.
 *
 * @since 2.0.0
 *
 * @param string   $file      The filename of the plugin requesting the deactivation hook.
 * @param callable $function  The name of the function to call on deactivation.
 */
function register_deactivation_hook( string $file, callable $function ): void {
  if ( is_array( $function ) || is_string( $function ) && strpos( $function, '::' ) !== false ) {
    $callable = $function;
  } else {
    $callable = strtolower( $function );
  }

  $GLOBALS['wp_filter']['deactivate_' . plugin_basename( $file )][] = array(
    'function' => $callable,
    'accepted_args' => 0
  );
}

register_activation_hook() 几乎一模一样,只是将钩子名称从 activate_ 换成了 deactivate_

五、 插件停用时发生了什么?

与激活类似,插件停用时,WordPress 会调用 deactivate_plugin() 函数。 这个函数也位于 wp-admin/includes/plugin.php 文件中。 deactivate_plugin() 函数的简化版如下:

function deactivate_plugins( string|array $plugins, string $redirect = '', bool $network_wide = false, bool $silent = false ): WP_Error|void {
  // ... 一些检查和验证 ...

  foreach ( (array) $plugins as $plugin ) {
      do_action( 'deactivate_' . $plugin, $network_wide );
  }

  // ... 一些更新选项的操作 ...
}

同样,do_action() 函数会触发一个动作:'deactivate_' . $plugin,从而执行我们用 register_deactivation_hook() 注册的函数。

六、 shutdown 钩子:一个重要但容易被忽略的细节

你可能注意到了,我们一直没有提到 shutdown 钩子。 这是因为 register_activation_hook()register_deactivation_hook() 并不是直接将钩子函数添加到 shutdown 钩子中,而是通过 do_action() 实现了类似的效果。

虽然没有直接使用 shutdown 钩子,但是理解 shutdown 钩子的作用对于理解插件激活和停用过程仍然很有帮助。 shutdown 钩子会在 PHP 脚本执行完毕,即将关闭时被触发。 这通常是进行一些清理工作或者保存数据的最佳时机。

七、 为什么不直接在 activate_plugin()deactivate_plugin() 中调用函数?

你可能会问,为什么 WordPress 不直接在 activate_plugin()deactivate_plugin() 函数中调用我们注册的函数,而是要绕一个圈子,使用 do_action() 呢?

原因在于 灵活性可扩展性

通过使用 do_action(),WordPress 允许其他插件或主题在插件激活和停用过程中插入自己的代码。 例如,一个插件可以监听 activate_hello-world-plugin/hello-world-plugin.php 动作,并在 "Hello World Plugin" 激活时执行一些额外的操作。

这种机制使得 WordPress 具有很强的可定制性,允许开发者构建各种各样的插件和主题,而不会互相冲突。

八、 总结

让我们用一个表格来总结一下 register_activation_hook() 的工作原理:

步骤 描述 涉及函数
1 插件调用 register_activation_hook( __FILE__, 'your_function' ) register_activation_hook()
2 register_activation_hook() 将 ‘your_function’ 注册到 $GLOBALS['wp_filter']['activate_your-plugin/your-plugin.php'] register_activation_hook(), plugin_basename()
3 当插件被激活时,WordPress 调用 activate_plugin( 'your-plugin/your-plugin.php' ) activate_plugin()
4 activate_plugin() 函数触发 do_action( 'activate_your-plugin/your-plugin.php' ) do_action()
5 do_action() 遍历 $GLOBALS['wp_filter'],找到与 'activate_your-plugin/your-plugin.php' 关联的所有函数,并执行它们,包括 ‘your_function’ do_action()

register_deactivation_hook() 的流程与此类似,只是将 activate_ 替换为 deactivate_

九、 一些注意事项

  • 安全性: 在激活钩子中执行代码时,要特别注意安全性。 避免执行任何可能导致安全漏洞的操作,例如执行用户提供的 SQL 查询。
  • 错误处理: 确保你的激活函数能够正确处理错误。 如果激活失败,应该给出明确的提示,并尽可能回滚已执行的操作。
  • 性能: 激活函数应该尽可能快速地执行。 避免执行耗时的操作,例如下载大型文件或进行复杂的计算。
  • 版本控制: 使用 add_option() 函数记录插件的版本号,并在激活函数中检查当前版本号,以便在插件升级时执行必要的数据库迁移或数据更新。
  • 卸载钩子:强烈建议注册卸载钩子(register_uninstall_hook),删除插件数据,以保证用户的系统干净。

十、 最后的忠告

register_activation_hook() 是一个非常强大的函数,可以让你在插件激活时执行任何你想要的代码。 但是,也需要谨慎使用它,避免滥用或误用。 记住,良好的代码习惯和安全意识是构建高质量 WordPress 插件的关键。

希望今天的讲座能帮助你更好地理解 register_activation_hook() 的工作原理。 下次再见!

发表回复

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