搜索全站
主题 14 分钟阅读

WordPress主题前瞻性适配:提前处理弃用函数与版本兼容

WordPress 每次大版本更新后主题报错,多半是用了已被弃用的函数。本文讲清 _deprecated_function 的工作方式,给出用 WP_DEBUG、Query Monitor 和 deprecation 日志提前发现风险的完整流程与适配写法。

U
UFO_PF 2021-08-08 · 阅读 14 分钟
Wo 主题
阅读提示
本文约 5,233 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:升级当天白屏,才知道主题用了十年老函数

一个做了六年的企业站,在 WordPress 6.7 自动更新后首页白屏。打开错误日志看到一行:PHP Fatal error: Uncaught Error: Call to undefined function wp_get_sites()。这个函数在 WordPress 4.6 就被 get_sites() 取代,到 6.1 才被彻底移除——中间隔了五年,主题作者从未处理过那条弃用警告,因为它默认不开显示,日志也从来没人看。

WordPress 的弃用机制其实非常宽容:一个函数被标记弃用后,通常还会保留若干个大版本才真正删除,期间只要开了 WP_DEBUG 就会记录提示。问题在于绝大多数生产站点 WP_DEBUG 是关闭的,display_errors 也关着,于是这些预警全部沉到没人读的日志文件里,直到函数被删除那天集中爆发。

前瞻性适配的本质,就是把这条「静默预警通道」打开,并建立一套「发布前先看弃用清单」的固定动作。

原理分析:WordPress 的弃用机制怎么工作

WordPress 核心在移除一个函数或参数前,会先调用 _deprecated_function()、_deprecated_argument()、_deprecated_file()、_deprecated_hook() 这一类内部函数。它们的行为是:

函数 触发场景 默认表现
_deprecated_function() 调用了即将移除的函数 触发 deprecated_function_run 动作,写 error_log
_deprecated_argument() 传了已废弃的参数 同上,附加参数名
_deprecated_file() include 了废弃文件 同上,附加替代文件路径
_deprecated_hook() 用了废弃的钩子名 触发 deprecated_hook_run
apply_filters_deprecated() 过滤器整体废弃 同上

关键点有三个:一是它们只在 WP_DEBUG 为 true 时才记录,生产环境默认静默;二是它们会触发 deprecated_function_run 这个动作,可以被插件捕获并展示;三是 WP_DEBUG_DISPLAY 与 WP_DEBUG_LOG 是分开控制的,可以只写日志不显示。

WordPress 6.x 时代几个必须知道的变化:块主题全面转向 theme.json;wp_body_open() 成为主题必须调用的钩子(4.7 引入,缺了它很多插件注入失效);经典主题若仍用 wp_head() 之外的方式加载脚本会出问题;PHP 8.0 之后 create_function() 已被移除,PHP 8.2 起动态属性创建会触发 deprecation 警告(WordPress 自身大量代码为此重构过)。

保姆级实操

步骤 1:在预发布环境打开完整调试

生产环境不要开 WP_DEBUG_DISPLAY。在 wp-config.php 里这样配:

<?php
// 预发布环境:记录但不显示
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

// 额外捕获弃用信息,按天分文件
define( 'WP_DEBUG_LOG', WP_CONTENT_DIR . '/debug-' . date( 'Y-m-d' ) . '.log' );

日志会落在 wp-content/debug-2026-10-10.log。注意这个文件可被公开访问,务必在 nginx 层拒绝:

location ~* ^/wp-content/debug.*\.log$ {
    deny all;
    return 404;
}

步骤 2:把弃用警告单独挑出来

日志里的弃用条目有固定前缀,用命令行直接过滤:

grep -E "deprecated|Deprecated" wp-content/debug-*.log \
  | sed -E 's/.*(deprecated_function_run|_deprecated_[a-z]+).*/\1 &/' \
  | sort | uniq -c | sort -rn | head -50

这条命令会按出现频次排出最常被触发的弃用项,优先修排名靠前的。

步骤 3:用插件把弃用提示搬到后台可见位置

命令行不方便时,装 Query Monitor 插件,它的「PHP Errors」面板会单独归类 Deprecated / Deprecation 两类,并显示触发文件与行号,定位比翻日志快很多。注意 Query Monitor 只在当前登录用户是管理员时输出面板,生产环境也能安全使用。

步骤 4:写一个自己的弃用采集钩子

不想依赖插件的话,二十行代码就能把弃用信息收进数据库:

<?php
add_action( 'deprecated_function_run', 'mmf_log_deprecation', 10, 3 );
function mmf_log_deprecation( $function, $replacement, $version ) {
    $key  = 'mmf_deprecation_log';
    $data = get_option( $key, array() );
    $id   = $function . '|' . ( $replacement ?: '(none)' );

    if ( ! isset( $data[ $id ] ) ) {
        $data[ $id ] = array(
            'function'    => $function,
            'replacement' => $replacement,
            'since'       => $version,
            'count'       => 0,
            'last'        => '',
        );
    }

    $data[ $id ]['count']++;
    $data[ $id ]['last'] = current_time( 'mysql' );

    if ( count( $data ) > 200 ) {
        $data = array_slice( $data, -200, null, true );
    }

    update_option( $key, $data, false );
}

update_option() 第三个参数传 false 表示不自动加载,避免这个诊断数据被每次请求都读进内存。

步骤 5:按标准模式改写弃用调用

以几个真实存在过的替换关系为例:

<?php
// 旧:wp_get_sites() → 新:get_sites()
$sites = get_sites( array( 'fields' => 'ids', 'number' => 100 ) );

// 旧:get_blog_list() → 新:get_sites() + switch_to_blog()
// 旧:create_function()(PHP 8.0 移除)→ 新:匿名函数
$callback = function( $a ) { return $a * 2; };

// 旧:直接访问 $post->post_title 之外,还用 query_posts()
// 新:WP_Query / pre_get_posts
$query = new WP_Query( array(
    'post_type'      => 'product',
    'posts_per_page' => 12,
    'no_found_rows'  => true,
) );

// 旧:wp_title() 拼标题 → 新:add_theme_support( 'title-tag' )
add_theme_support( 'title-tag' );

版本判断则用 function_exists() / method_exists() 兜底,保证主题在老版本上也不炸:

<?php
if ( function_exists( 'wp_body_open' ) ) {
    wp_body_open();
}

步骤 6:把 PHP 版本也纳入适配范围

php -v
php -d error_reporting=E_ALL -d display_errors=1 -l wp-content/themes/my-theme/functions.php

php -l 只做语法检查,运行时弃用要用 PHPStan 或 Psalm 配合 WordPress 扩展扫描。CI 里固定跑一个 phpcs --standard=WordPress 加一个 PHP 版本矩阵(8.1 / 8.2 / 8.3),能在升级前一个月就发现风险。

步骤 7:订阅官方变更渠道

WordPress 每个大版本的 Field Guide(字段指南)会在发布前后公布开发者级变更,make.wordpress.org/core 上的 dev notes 是最权威来源。把这两个源加进 RSS,比等用户报错有效得多。

常见踩坑

  1. 生产环境开 WP_DEBUG_DISPLAY:会把路径、表名直接暴露给访客,属于信息泄露。必须 false。
  2. 日志文件没做访问控制:debug.log 在 wp-content 下是可被 HTTP 直连的,包含大量内部信息,一定要在服务器层 deny。
  3. 只修 Fatal,不修 Deprecated:弃用提示平均领先移除 2–5 个大版本,修弃用提示等于提前两三年买保险。
  4. 用 function_exists() 包住整个逻辑块:会导致 WordPress 老版本下该功能直接静默消失,用户不知道为什么少了东西。应该只在调用点做最小判断。
  5. 案例: 某主题在 functions.php 里用了 screen_icon()(WordPress 3.8 弃用、4.0 移除)和 $wpdb->escape()(3.7 弃用)。站长一直没开调试,直到主机商把 PHP 升到 8.1,两处同时报 Fatal。更隐蔽的是主题里还有 each() 循环(PHP 7.2 弃用、8.0 移除),这部分错误只在特定页面触发,上线后第三天才有用户反馈「某个分类页打不开」。教训是:PHP 升级和 WordPress 升级要分开做回归测试。
  6. 用子主题覆盖父主题模板文件后不再跟随父主题更新:父主题修了弃用,子主题里的旧副本仍在。子主题应只覆盖必要的最小片段。
  7. 忽略 wp_body_open():主题缺这个钩子时,很多依赖它在 <body> 后注入内容的插件会静默失效,表现是「插件装了但没效果」。

总结

前瞻性适配可以固化成四个动作:预发布环境常开 WP_DEBUG + WP_DEBUG_LOG、日志按天切分并在服务器层禁止访问;用 Query Monitor 或自写的 deprecated_function_run 钩子把弃用信息集中展示;每次大版本发布前跑一遍 WPCS 与目标 PHP 版本的语法/静态检查;订阅 make.wordpress.org/core 的 dev notes。

判断优先级的标准很简单:已经被移除的(Fatal)> 明确给了替代函数的(Deprecated with replacement)> 只标记了参数废弃的(Argument deprecated)。前两类必须在下一个大版本发布前修完。这套流程与 自己维护主题的版本管理与变更记录 直接衔接,弃用清单应当作为每次版本发布的必查项;适配完成后再用 主题性能优化的度量方式 验证一遍,确认改动没有引入性能回退。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。