本文约 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,比等用户报错有效得多。
常见踩坑
- 生产环境开
WP_DEBUG_DISPLAY:会把路径、表名直接暴露给访客,属于信息泄露。必须false。 - 日志文件没做访问控制:
debug.log在wp-content下是可被 HTTP 直连的,包含大量内部信息,一定要在服务器层 deny。 - 只修 Fatal,不修 Deprecated:弃用提示平均领先移除 2–5 个大版本,修弃用提示等于提前两三年买保险。
- 用
function_exists()包住整个逻辑块:会导致 WordPress 老版本下该功能直接静默消失,用户不知道为什么少了东西。应该只在调用点做最小判断。 - 案例: 某主题在
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 升级要分开做回归测试。 - 用子主题覆盖父主题模板文件后不再跟随父主题更新:父主题修了弃用,子主题里的旧副本仍在。子主题应只覆盖必要的最小片段。
- 忽略
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)。前两类必须在下一个大版本发布前修完。这套流程与 自己维护主题的版本管理与变更记录 直接衔接,弃用清单应当作为每次版本发布的必查项;适配完成后再用 主题性能优化的度量方式 验证一遍,确认改动没有引入性能回退。
