DedeCMS PHP 8.x 兼容性修复笔记:文章保存500错误排查与解决
问题现象
DedeCMS 后台编辑文章点击保存后,页面返回 HTTP 500 错误。此前网站已从 PHP 5.x 升级到 PHP 8.x,大量旧代码存在兼容性问题。
排查过程
第一步:添加错误捕获
由于 PHP 8.x 的致命错误(Fatal Error)会直接中断执行并返回 500,且页面空白无提示,首先在 article_edit.php 的保存分支中添加 register_shutdown_function 捕获错误:
register_shutdown_function(function() {
$e = error_get_last();
if ($e && $e['type'] != E_NOTICE) {
error_log("article_edit SHUTDOWN: " . json_encode($e, JSON_UNESCAPED_UNICODE));
}
});
第二步:触发保存,查看错误日志
用户在后台执行保存操作后,Apache 错误日志输出:
PHP Fatal error: Uncaught TypeError: curl_close(): Argument #1 ($handle)
must be of type CurlHandle, null given in
/var/www/www.0cai.net/include/dedehttpdown.class.php:616
Stack trace:
#0 dedehttpdown.class.php(616): curl_close()
#1 inc_archives_functions.php(188): DedeHttpDown->Close()
#2 inc_archives_functions.php(647): GetCurContent()
#3 article_edit.php(173): AnalyseHtmlBody()
#4 {main}
第三步:定位根因
错误调用链清晰可见:article_edit.php → AnalyseHtmlBody() → GetCurContent() → DedeHttpDown->Close() → curl_close($ch)
打开 dedehttpdown.class.php 第613-619行,发现 Close() 方法:
function Close()
{
if (function_exists('curl_init') && function_exists('curl_exec')) {
curl_close($ch); // $ch 是未定义的局部变量!
}
@fclose($this->m_fp);
}
这是一个 DedeCMS 原始代码 Bug:$ch 是未定义的局部变量,正确的应该是 $this->m_ch(类成员变量)。
- PHP 7.x:curl_close(null) 只产生 Warning,不中断执行
- PHP 8.x:curl_close() 参数类型严格化,null 触发 TypeError 致命错误
修复方案
修复一:curl_close 变量名错误 + 类型守卫
文件:/var/www/www.0cai.net/include/dedehttpdown.class.php
// 修复前
function Close()
{
if (function_exists('curl_init') && function_exists('curl_exec')) {
curl_close($ch); // $ch 未定义
}
@fclose($this->m_fp);
}
// 修复后
function Close()
{
if ($this->m_ch && (is_resource($this->m_ch) || $this->m_ch instanceof \CurlHandle)) {
curl_close($this->m_ch);
$this->m_ch = null;
}
if (is_resource($this->m_fp)) {
@fclose($this->m_fp);
}
}
修复二:fclose 非资源参数
修复 curl_close 后再次保存,出现新错误:
fclose(): Argument #1 ($stream) must be of type resource, string given
原因:$this->m_fp 初始化为空字符串(var $m_fp = '';),当 fsockopen 未被调用或失败时,m_fp 不是 resource 类型。PHP 8.x 中 fclose('') 同样抛出 TypeError。
修复方案:对 m_fp 的所有 fclose、feof 调用添加 is_resource() 检查:
// Close() 方法
if (is_resource($this->m_fp)) { @fclose($this->m_fp); }
// 文件读取处
if (!is_resource($this->m_fp) || @feof($this->m_fp)) { ... }
while (is_resource($this->m_fp) && !feof($this->m_fp)) { ... }
PHP 7.x vs 8.x 关键差异总结
| 函数 | PHP 7.x 行为 | PHP 8.x 行为 | 修复方式 |
|---|---|---|---|
| curl_close(null) | Warning,继续执行 | TypeError 致命错误 | 添加 null/类型检查 |
| fclose('') | Warning,继续执行 | TypeError 致命错误 | is_resource() 检查 |
| feof('') | Warning,返回 false | TypeError 致命错误 | is_resource() 检查 |
完整修复清单
本次 PHP 8.x 兼容性修复涉及的文件和问题汇总:
| 文件 | 问题 | 修复 |
|---|---|---|
| include/dedehttpdown.class.php | curl_close($ch) 变量名错误 | 改为 $this->m_ch + 类型守卫 |
| include/dedehttpdown.class.php | fclose/feof 对非 resource 操作 | 添加 is_resource() 检查 |
| include/helpers/archive.helper.php | INSERT 引用不存在的 tag_alias 列 | 移除 tag_alias 字段 |
| include/dedesqli.class.php | mysqli_query 参数顺序错误 | 翻转参数顺序 |
| include/dedesqli.class.php | SetLongLink 懒加载连接 null | 自动 Init+Open |
| include/dedetemplate.class.php | 数组赋值属性报错 | 初始化为 FALSE + stdClass |
| include/datalistcp.class.php | each() 已废弃 | 改用 foreach |
| include/autoload.inc.php | __autoload() 已移除 | spl_autoload_register() |
| include/json.class.php 等 | 花括号数组语法 $str{N} | 改为 $str[N] |
| 43个类文件 | 动态属性报错 | 添加 #[AllowDynamicProperties] |
调试技巧
- register_shutdown_function:捕获致命错误,写入 error_log,适合排查白屏/500 问题
- set_error_handler:自定义错误处理,可以拦截 Warning 并记录
- try-catch \Throwable:PHP 7+ 可以捕获 Exception 和 Error,包裹可疑代码段
- Apache 错误日志:tail -f /var/log/apache2/0cai.net_error.log 实时监控
- PHP lint:php -l file.php 快速检查语法错误
总结
DedeCMS 是为 PHP 5.x 设计的老程序,升级到 PHP 8.x 后核心问题是:
- 类型严格化:null/空字符串/false 不能再传给期望 resource 或特定类型的函数
- 废弃函数移除:each()、__autoload()、mysql_* 系列等直接报错
- 动态属性限制:未声明的类属性需要 #[AllowDynamicProperties] 注解
- 静默错误升级:原来看不见的 Warning 变成致命 Error
排查思路:先加错误捕获拿到具体报错 → 沿调用栈定位 → 逐个修复类型不兼容代码。本文的 curl_close 变量名 bug 是 DedeCMS 原始代码缺陷,在 PHP 7.x 下隐藏了十几年,升级到 8.x 后才暴露。
- 上一篇:Linux上Excel导出为PDF:字体缺失与渲染差异的修复
- 下一篇:没有了
