`register_activation_hook` 静默失败:我靠 `error_log` 在共享主机上"盲打"出三条铁律

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

上周帮客户迁移插件到某虚拟主机,后台显示"插件已启用",但数据表没建、默认选项没写、计划任务也没挂。没有 SSH,没有 `WP_DEBUG_LOG`,只有 cPanel 里一个灰扑扑的 Error Logs 按钮。折腾到凌晨,我总结出一套共享主机上的 activation hook 排雷流程,适合没服务器权限的兄弟参考。

坑一:`register_activation_hook` 的路径写法在符号链接面前"认怂"

客户用的是软链部署(`~/public_html` 指向 `~/releases/current`),我惯用的 `__FILE__` 在 activation hook 里直接失效:

// 本地 OK,软链环境挂掉
register_activation_hook( __FILE__, 'my_plugin_activate' );

// 换成 plugin_basename 配合真实路径才稳
$plugin_main_file = WP_PLUGIN_DIR . '/my-plugin/my-plugin.php';
register_activation_hook( $plugin_main_file, 'my_plugin_activate' );

更隐蔽的是:有些主机面板会自动把插件目录重命名为 `my-plugin-1` 之类,plugin_basename 反而比硬编码路径靠谱。我现在统一用 MY_PLUGIN_FILE 常量,在入口文件顶部定义,全插件引用。

坑二:activation hook 里抛异常 = 白屏 + 无日志,WordPress 会"吞掉"你的错误

我最初在激活回调里直接 throw new Exception('建表失败'),结果页面刷新后插件状态显示"已启用",实际啥也没干。WordPress 的插件激活流程用了 ob_start() 包裹,异常被缓冲层吃掉,前端只看到一个无声的跳转。

现在的写法是强制刷日志 + 降级处理:

function my_plugin_activate() {
    global $wpdb;
    
    // 共享主机救命稻草:直接写 error_log
    error_log( '[MyPlugin] Activation started at ' . current_time( 'mysql' ) );
    
    try {
        $result = my_plugin_create_tables();
        if ( $result === false ) {
            error_log( '[MyPlugin] DB Error: ' . $wpdb->last_error );
            // 不抛异常,改写入选项,下次 admin_init 时重试
            update_option( 'my_plugin_activation_pending', true );
        }
    } catch ( Throwable $e ) {
        error_log( '[MyPlugin] Fatal: ' . $e->getMessage() );
        // 同样不阻断,避免用户看到白屏
    }
}

有人觉得这样"藏错误"不地道,但在共享主机上,让用户能继续用其他功能,比展示一个无法解决的白屏更实际。pending 状态配合 admin notice 提示手动修复,是折中方案。

坑三:activation hook 里调用 `wp_schedule_event` 时机不对,cron 任务"假死"

我踩过最阴的坑:激活时注册 cron,用户立刻去"系统定时任务"里看,找不到。其实不是没注册,是 WordPress cron 的"伪定时"机制——没有真实系统 cron 触发,wp-cron.php 依赖访客访问才执行。

更坑的是,如果你在 activation hook 里直接 wp_schedule_event( time(), 'hourly', 'my_cron_hook' ),而插件之前装过没清干净,wp_next_scheduled 返回已有时间戳,你以为成功了,其实是旧任务的残留。

我现在强制"先清后加",且把首次执行时间往后挪 60 秒,避免激活瞬间的并发:

function my_plugin_schedule_cron() {
    $hook = 'my_plugin_hourly_sync';
    
    // 彻底清,不怕残留
    wp_clear_scheduled_hook( $hook );
    
    // 错开激活高峰
    $first_run = time() + 60;
    wp_schedule_event( $first_run, 'hourly', $hook );
    
    error_log( "[MyPlugin] Cron scheduled, first run: " . date( 'Y-m-d H:i:s', $first_run ) );
}

附:共享主机"盲调"三板斧

没 `var_dump` 权限的时候,我靠这三行活命:

// 1. 最原始的日志,99%主机支持
error_log( json_encode( compact( 'var1', 'var2' ) ) );

// 2. 写入 uploads 目录的临时文件(注意清理)
file_put_contents( 
    WP_CONTENT_DIR . '/uploads/.debug_' . date( 'Ymd' ) . '.log',
    print_r( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), true ),
    FILE_APPEND 
);

// 3. 极端情况:把状态写进 option,前端 AJAX 轮询读取
update_option( 'my_plugin_debug_buffer', array_slice( $logs, -50 ) );

最后一条是没办法的办法——某主机连 error_log 都重定向到黑洞,我只能让前端每 5 秒请求一次自己的 debug API,把 option 里的日志吐出来看。

有兄弟在更严苛的环境(比如 SAE、BAE 这种禁文件写入的 PaaS)调过 activation hook 吗?想听听你们的"土法炼钢"路子。

评论0
回复 · 0
还没有回复
微信客服 微信客服