插件卸载时 `uninstall.php` 与 `register_uninstall_hook` 二选一?我因为"双重注册"把用户数据清了个干净

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 76 浏览 0 回复

上周帮客户做插件交接,对方技术负责人盯着代码问了一句:"你们卸载逻辑怎么写了两份?"我当时一愣,回去翻仓库才发现——uninstall.php 里删了一遍表,register_uninstall_hook 的回调里又删了一遍。更绝的是,两份逻辑还不完全一致,一个清选项、一个清表,用户点卸载时到底执行哪份,全看 WordPress 当天心情。

这篇帖子不是讲"怎么写卸载",是讲"怎么写不互相打架"。

一、WordPress 的卸载执行优先级,很多人记反了

官方文档说得很含糊,实测行为是这样的:

// 情况A:只有 uninstall.php,且文件头部注释合规
// → 直接走文件,不触发 register_uninstall_hook

// 情况B:没有 uninstall.php,或有但注释不对
// → 才会去查 register_uninstall_hook 注册的回调

// 情况C:两者都有,且 uninstall.php 注释正确
// → 只执行 uninstall.php,hook 回调被完全忽略

但"情况C"有个致命盲区:如果 uninstall.php 存在但语法报错,或者权限问题导致 is_uninstallable_plugin() 返回 false,系统会静默 fallback 到 hook 回调。两份逻辑都会被执行——只是不在同一次请求里,而是"这次失败,下次补刀"。

我踩的坑更隐蔽:uninstall.php 开头忘了写标准的插件信息注释,导致 WordPress 不认它是"合规的卸载文件",于是每次都走 hook。后来补上了注释,但 hook 没删,测试环境因为缓存问题没触发双重执行,线上环境换了服务器配置后突然"生效"了。

二、建表时的"隐形契约":charset 与 collation 别硬编码

这是另一个血案。早期版本为了"兼容",我直接把建表语句写死了:

$sql = "CREATE TABLE {$wpdb->prefix}my_plugin_logs (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    content longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
    ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";

客户数据库是 utf8mb4_0900_ai_ci(MySQL 8.0 默认),插件激活时 dbDelta 对比结构,发现 collation 不一致,每次页面加载都尝试"修正"表结构。后台慢得像蜗牛,SHOW PROCESSLIST 里全是 ALTER TABLE

现在我的模板里固定这么写:

global $wpdb;
$charset_collate = $wpdb->get_charset_collate(); // 让 WordPress 去猜宿主环境

$sql = "CREATE TABLE {$wpdb->prefix}my_plugin_logs (
    id bigint(20) NOT NULL AUTO_INCREMENT,
    content longtext,
    ...
) $charset_collate;";

注意 content longtext 后面不再跟 CHARACTER SET ... COLLATE ...,让 $charset_collate 统一收口。如果某个字段确实需要特殊编码(比如存 emoji 的字段要 utf8mb4 而宿主是 utf8),单独声明并加注释说明原因。

三、升级脚本的"时间旅行"问题:版本号比较别用字符串

插件从 1.9 升级到 1.10,我的升级脚本没执行。调试半天发现:

// 错误示范
if ( $current_version < '1.10' ) { ... }

// PHP 字符串比较:'1.9' < '1.10' 返回 false
// 因为 '1.9' 的第二个字符 '9' > '1.10' 的第二个字符 '1'

现在全部改用 version_compare(),并且版本号入库时存的是"最后成功执行的升级版本",不是"当前插件版本":

$db_version = get_option( 'my_plugin_db_version', '0.0.0' );

$upgrades = [
    '1.2.0' => 'upgrade_120_add_index',
    '1.5.0' => 'upgrade_150_migrate_options',
    '1.10.0' => 'upgrade_1100_split_tables',
];

foreach ( $upgrades as $ver => $callback ) {
    if ( version_compare( $db_version, $ver, '
评论0
回复 · 0
还没有回复
微信客服 微信客服