各位观众老爷们,大家好!我是老码,今天咱们来聊聊 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;
}
咱们来分段解读一下:
-
参数处理: 函数接收两个参数:
$plugins
:要停用的插件列表,可以是一个插件文件名,也可以是一个包含多个插件文件名的数组。$silent
:一个布尔值,表示是否静默停用。如果为true
,则不会触发after_plugin_deactivation
钩子。这个参数通常用在批量停用插件的时候。
-
插件验证: 遍历
$plugins
数组,对每个插件进行验证,确保插件文件存在且合法。validate_plugin()
函数负责验证插件的有效性。plugin_basename()
函数用于获取插件的文件名(例如:my-plugin/my-plugin.php
)。 -
停用前的钩子: 关键的一步来了!在实际停用插件之前,会触发
do_action( 'deactivate_plugin', $plugin, $silent )
。这里传递了两个参数:$plugin
:要停用的插件的文件名。$silent
:静默停用标志。
这意味着,所有监听了
deactivate_plugin
钩子的函数,都会在这个时候被调用。 -
停用插件: 从
active_plugins
选项中移除要停用的插件。get_option('active_plugins')
获取当前已激活的插件列表,update_option('active_plugins', ...)
更新active_plugins
选项。 -
停用后的钩子: 如果不是静默停用,则会触发
do_action( 'after_plugin_deactivation', $plugin )
钩子。这个钩子在插件停用之后触发,通常用于执行一些清理工作,或者发送通知。 -
清除缓存: 清除插件缓存,确保 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" );
// 还可以做其他的清理工作,比如删除临时文件,取消注册定时任务等等
// ...
}
这段代码做了以下几件事情:
- 注册停用钩子: 使用
register_deactivation_hook()
函数,将my_awesome_plugin_deactivation_hook()
函数注册为插件停用时要执行的函数。__FILE__
常量表示当前插件的文件路径。 - 定义停用函数: 定义
my_awesome_plugin_deactivation_hook()
函数,这个函数会在插件停用时被调用。 - 执行清理操作: 在停用函数中,执行清理数据库的操作。这里使用了
$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 插件。
好了,今天的讲座就到这里。感谢大家的收听!如果大家有什么问题,欢迎在评论区留言。咱们下期再见!