剖析 WordPress `do_action(‘deactivate_plugin’)` 的源码:此钩子在何时被触发。

各位观众老爷们,大家好!我是老码,今天咱们来聊聊 WordPress 里一个挺重要的钩子:do_action('deactivate_plugin')。这钩子,表面上看是插件停用的时候触发,但背后藏着不少细节,一不小心就容易踩坑。今天就来扒一扒它的源码,看看它到底是怎么工作的,以及什么时候会被触发。

一、钩子的定义和作用

首先,咱们先来明确一下,do_action('deactivate_plugin') 到底是个啥玩意儿。在 WordPress 的世界里,do_action() 是一个核心函数,用于触发 action hook(动作钩子)。你可以把它想象成一个“事件发射器”,当某个特定的事件发生时,它就会通知所有监听了这个事件的函数,让它们执行相应的操作。

do_action('deactivate_plugin') 这个钩子,顾名思义,就是用来在插件停用的时候触发的。它的作用是让插件开发者有机会在插件停用前执行一些清理工作,比如:

  • 清理数据库
  • 删除临时文件
  • 取消注册定时任务
  • 通知服务器

总之,就是把插件留下的“屁股”擦干净,免得影响 WordPress 的正常运行。

二、源码剖析:从 deactivate_plugins() 函数开始

要搞清楚 do_action('deactivate_plugin') 何时被触发,咱们得从 WordPress 的核心函数 deactivate_plugins() 入手。这个函数负责处理插件的停用逻辑。

咱们直接上代码,然后一行行地分析:

function deactivate_plugins( $plugins, $silent = false ) {
    global $wp_plugin_paths;

    if ( ! is_array( $plugins ) ) {
        $plugins = array( $plugins );
    }

    if ( empty( $plugins ) ) {
        return;
    }

    $current = get_option( 'active_plugins', array() );

    $deactivated = false;
    foreach ( $plugins as $plugin ) {
        $plugin = trim( $plugin );

        if ( ! validate_plugin( $plugin ) ) {
            continue;
        }

        $plugin = plugin_basename( $plugin );

        if ( false === in_array( $plugin, $current, true ) ) {
            continue;
        }

        /**
         * Fires before a plugin is deactivated.
         *
         * @since 3.0.0
         *
         * @param string $plugin Plugin file name.
         * @param bool   $silent Whether to prevent calling activation hooks. Default false.
         */
        do_action( 'deactivate_plugin', $plugin, $silent );

        $deactivated = true;
        $key         = array_search( $plugin, $current, true );
        if ( false !== $key ) {
            unset( $current[ $key ] );
        }
    }

    if ( $deactivated ) {
        update_option( 'active_plugins', array_values( $current ) );

        if ( ! $silent ) {
            foreach ( $plugins as $plugin ) {
                if ( ! validate_plugin( $plugin ) ) {
                    continue;
                }

                $plugin = plugin_basename( $plugin );

                /**
                 * Fires after a plugin is deactivated.
                 *
                 * @since 2.0.0
                 *
                 * @param string $plugin Plugin file name.
                 */
                do_action( 'after_plugin_deactivation', $plugin );
            }
        }
    }

    wp_cache_delete( 'plugins', 'plugins' );

    return true;
}

咱们来分段解读一下:

  1. 参数处理: 函数接收两个参数:

    • $plugins:要停用的插件列表,可以是一个插件文件名,也可以是一个包含多个插件文件名的数组。
    • $silent:一个布尔值,表示是否静默停用。如果为 true,则不会触发 after_plugin_deactivation 钩子。这个参数通常用在批量停用插件的时候。
  2. 插件验证: 遍历 $plugins 数组,对每个插件进行验证,确保插件文件存在且合法。validate_plugin() 函数负责验证插件的有效性。plugin_basename() 函数用于获取插件的文件名(例如:my-plugin/my-plugin.php)。

  3. 停用前的钩子: 关键的一步来了!在实际停用插件之前,会触发 do_action( 'deactivate_plugin', $plugin, $silent )。这里传递了两个参数:

    • $plugin:要停用的插件的文件名。
    • $silent:静默停用标志。

    这意味着,所有监听了 deactivate_plugin 钩子的函数,都会在这个时候被调用。

  4. 停用插件:active_plugins 选项中移除要停用的插件。get_option('active_plugins') 获取当前已激活的插件列表,update_option('active_plugins', ...) 更新 active_plugins 选项。

  5. 停用后的钩子: 如果不是静默停用,则会触发 do_action( 'after_plugin_deactivation', $plugin ) 钩子。这个钩子在插件停用之后触发,通常用于执行一些清理工作,或者发送通知。

  6. 清除缓存: 清除插件缓存,确保 WordPress 能够正确识别插件的状态。

三、触发时机总结

从上面的源码分析可以看出,do_action('deactivate_plugin') 钩子在以下时机被触发:

  • 函数: deactivate_plugins()
  • 时间: 在插件被从 active_plugins 选项中移除之前。
  • 条件: 插件文件必须存在且合法,且插件必须是已激活状态。
  • 参数: 传递给钩子的参数包括插件的文件名和静默停用标志。

用一个表格来总结一下:

钩子 触发函数 触发时间 条件 参数
deactivate_plugin deactivate_plugins() 插件从 active_plugins 移除之前 插件文件存在且合法,插件已激活 插件文件名,静默停用标志
after_plugin_deactivation deactivate_plugins() 插件从 active_plugins 移除之后 插件文件存在且合法,插件已激活,且不是静默停用 插件文件名

四、实际应用:如何使用 deactivate_plugin 钩子

现在咱们知道了 deactivate_plugin 钩子何时被触发,接下来看看如何在实际开发中使用它。

假设你的插件需要在停用时清理数据库中的一些数据,你可以这样做:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 */

// 注册停用钩子
register_deactivation_hook( __FILE__, 'my_awesome_plugin_deactivation_hook' );

/**
 * 插件停用时执行的函数
 */
function my_awesome_plugin_deactivation_hook() {
    // 执行清理数据库的操作
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_awesome_table';
    $wpdb->query( "DROP TABLE IF EXISTS $table_name" );

    // 还可以做其他的清理工作,比如删除临时文件,取消注册定时任务等等
    // ...
}

这段代码做了以下几件事情:

  1. 注册停用钩子: 使用 register_deactivation_hook() 函数,将 my_awesome_plugin_deactivation_hook() 函数注册为插件停用时要执行的函数。__FILE__ 常量表示当前插件的文件路径。
  2. 定义停用函数: 定义 my_awesome_plugin_deactivation_hook() 函数,这个函数会在插件停用时被调用。
  3. 执行清理操作: 在停用函数中,执行清理数据库的操作。这里使用了 $wpdb 全局对象来执行 SQL 查询,删除插件创建的表。

注意: register_deactivation_hook() 函数只能在插件的主文件中调用,而且必须在全局作用域中调用。

五、register_deactivation_hook() 函数的原理

可能有人会问,register_deactivation_hook() 函数是怎么把我们的函数注册到 deactivate_plugin 钩子上的呢?咱们来扒一扒它的源码:

function register_deactivation_hook( $file, $function ) {
    $file = plugin_basename( $file );
    add_action( 'deactivate_' . $file, $function );
}

很简单,register_deactivation_hook() 函数实际上是调用了 add_action() 函数,将我们的函数添加到 deactivate_{$plugin} 钩子上。其中,{$plugin} 是插件的文件名。

也就是说,当你调用 register_deactivation_hook( __FILE__, 'my_awesome_plugin_deactivation_hook' ) 时,实际上相当于调用了 add_action( 'deactivate_my-awesome-plugin/my-awesome-plugin.php', 'my_awesome_plugin_deactivation_hook' )

那么,deactivate_{$plugin} 钩子又是何时被触发的呢?其实,它是在 deactivate_plugins() 函数中,在触发 deactivate_plugin 钩子之前触发的。

咱们再来看一下 deactivate_plugins() 函数的简化版本:

function deactivate_plugins( $plugins, $silent = false ) {
    foreach ( $plugins as $plugin ) {
        $plugin = plugin_basename( $plugin );

        /**
         * Fires before a specific plugin is deactivated.
         *
         * @since 2.5.0
         *
         * @param string $plugin Plugin file name.
         */
        do_action( 'deactivate_' . $plugin, $plugin );

        /**
         * Fires before a plugin is deactivated.
         *
         * @since 3.0.0
         *
         * @param string $plugin Plugin file name.
         * @param bool   $silent Whether to prevent calling activation hooks. Default false.
         */
        do_action( 'deactivate_plugin', $plugin, $silent );

        // ...
    }
}

可以看到,在触发 deactivate_plugin 钩子之前,会先触发 deactivate_{$plugin} 钩子。

六、deactivate_plugin vs deactivate_{$plugin}:有什么区别?

既然有两个类似的钩子,那么它们有什么区别呢?

  • deactivate_plugin:这是一个通用的钩子,所有插件停用时都会触发。你可以使用它来执行一些通用的清理工作,比如清理缓存,或者发送通知。
  • deactivate_{$plugin}:这是一个针对特定插件的钩子,只有该插件停用时才会触发。你可以使用它来执行一些特定于该插件的清理工作,比如删除插件创建的表,或者删除插件上传的文件。

七、注意事项

在使用 deactivate_plugin 钩子时,需要注意以下几点:

  • 耗时操作: 避免在停用函数中执行耗时操作,因为这会影响 WordPress 的性能。如果需要执行耗时操作,可以考虑使用异步任务队列。
  • 错误处理: 在停用函数中,要做好错误处理,避免因为一个错误导致整个停用过程失败。
  • 兼容性: 确保你的停用函数在不同的 WordPress 版本和不同的 PHP 版本下都能正常工作。
  • 安全: 避免在停用函数中执行不安全的操作,比如执行任意代码。

八、总结

今天咱们深入剖析了 WordPress 的 do_action('deactivate_plugin') 钩子,从源码分析到实际应用,相信大家对这个钩子已经有了更深入的了解。

总而言之,do_action('deactivate_plugin') 钩子是一个非常重要的钩子,它可以让插件开发者在插件停用前执行一些清理工作,确保 WordPress 的正常运行。希望大家在实际开发中能够灵活运用这个钩子,写出高质量的 WordPress 插件。

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

发表回复

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