Install
openclaw skills install @ebandao777-oss/empirecms7-php8-upgrade帝国CMS 7.5 升级到 PHP 8 兼容性修改完整清单。当用户需要将帝国CMS 7.5 升级到 PHP 8、修复 PHP 8 致命错误、或从 PHP 7.x 迁移到 PHP 8 时调用。
openclaw skills install @ebandao777-oss/empirecms7-php8-upgrade本技能提供帝国CMS 7.5 升级到完全兼容 PHP 8.x 的完整方法论。基于对可运行 PHP 8 兼容版本的逆向分析总结,覆盖语法兼容、语义兼容及运行时环境适配。
问题:未加引号的数组键如 $var[key] 在 PHP 8 中会被当作常量解析,常量未定义时抛出 Error: Undefined constant "key" 致命错误。这是 PHP 8 兼容的基础规范,修改量最大(11,000+ 处),遍布 200+ 个文件。
严重程度对比:
'key'(仅警告)Error: Undefined constant "key"(致命错误,白屏)必须加引号的情况(字符串字面量键名):
// 原代码(PHP 8 致命错误)→ 修复后(PHP 8 兼容)
$r[myorder]=0; → $r['myorder']=0;
$r[listorder]="id DESC"; → $r['listorder']="id DESC";
$_GET[classid] → $_GET['classid']
$_POST[from] → $_POST['from']
$class_r[$classid][featherclass] → $class_r[$classid]['featherclass']
不需要加引号的情况(切勿误改):
// 1. 变量键名 - 不加引号
$r[$key] // ✅ 正确(变量作为键名)
$r[$classid] // ✅ 正确
$r[$i] // ✅ 正确
// 2. 数字索引 - 不加引号
$r[0] // ✅ 正确
$arr[1] // ✅ 正确
$r[$i+1] // ✅ 正确(表达式)
// 3. 已加引号的 - 不修改
$r['key'] // ✅ 已正确
$r["key"] // ✅ 已正确
// 4. 已定义常量键名 - 不加引号
define('MY_CONST', 'value');
$r[MY_CONST] // ✅ 正确(是常量,不是字符串)
特殊情况(双引号字符串内):
"$r[key]" // ⚠️ PHP 7/8 都合法(先查常量,回退字符串)
"{$r['key']}" // ✅ 推荐写法(花括号语法)
遇到 $var[key] 时按以下顺序判断:
全项目所有 PHP 文件,重点文件:
修改统计:11,101+ 处(基于 Grep 搜索 \$\w+\[[a-zA-Z_]\w*\] 模式统计)
"$var[key]" 在 PHP 8 中仍合法,但建议改为 "{$var['key']}"$class_r[$classid]['featherclass']($classid 是变量不加,featherclass 是字符串要加)问题:在 SQL 语句(双引号字符串)中直接嵌入数组键时,'$var[key]' 在 PHP 8 中会触发未定义常量错误。
解决方案:使用花括号语法 {$var['key']}:
// 原代码(PHP 8 致命错误)
$sql = "... where bid='$cr[bid]'";
$sql = "... bname='$add[bname]'";
$sql = "... where userid='$user_r[userid]'";
// 修复后(PHP 8 兼容)
$sql = "... where bid='{$cr['bid']}'";
$sql = "... bname='{$add['bname']}'";
$sql = "... where userid='{$user_r['userid']}'";
判断流程:
$var[key] → 必须改为 {$var['key']}'$var[key]' → 必须改为 '{$var['key']}'$var[$key](变量键名)在双引号字符串中也需要花括号:{$var[$key]}问题:PHP 8 中 count() 对 null/非数组参数抛出 TypeError。
解决方案:在 e/class/connect.php 末尾(?> 之前)添加包装函数:
//为了count for php8
function php8_count($a){
return is_array($a) ? count($a) : 0;
}
应用范围:将以下文件中的 count($var) 替换为 php8_count($var):
e/class/*.php(functions.php、connect.php、hinfofun.php、com_functions.php 等)
e/admin/*.php 及其子目录
e/member/*.php
e/action/*.php
e/pl/*.php、e/search/*.php、e/ShopSys/*.php 等
修改统计:约 363 处调用(connect.php 29处、functions.php 46处、hinfofun.php 17处、com_functions.php 13处、cjfun.php 30处、tempfun.php 16处、moddofun.php 24处、classfun.php 9处、memberfun.php 7处、hplfun.php 7处等)
例外与性能注意:当变量确定是数组时保留原生 count()(如 count(array(1,2,3)) 或数据库结果集)。仅在变量来源不确定时使用 php8_count(),避免高频循环中的性能损耗。
问题:PHP 8 对未定义变量访问抛出 Warning,且在 isset() 之前直接访问变量名本身也可能触发警告。
解决方案:优先在函数签名中设置默认值,而非在函数体内赋值:
// 推荐:函数签名默认值
function RepClassid($classid = null) { ... }
// 备选:仅当无法修改签名时使用 func_get_arg
$classid = func_num_args() > 0 ? func_get_arg(0) : null
修改统计:约 944 处(e/admin/ 616处、e/class/ 248处、e/member/ 22处、e/ShopSys/ 6处等)
应用范围:e/class/.php、e/admin/.php、e/member/.php、e/ShopSys/.php 中使用 $classid 等未初始化变量的函数。
问题:get_magic_quotes_gpc() 在 PHP 8 中已移除。
解决方案:在 e/class/connect.php 中:
define('MAGIC_QUOTES_GPC', function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc());
问题:PHP 8 弃用警告会破坏输出。
解决方案:在 e/class/connect.php 中:
// 文件顶部
error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED);
// 后台页面
error_reporting(E_ALL & ~E_WARNING & ~E_DEPRECATED & ~E_USER_DEPRECATED);
问题:/e 修饰符在 PHP 7.2+ 中已移除。
解决方案:在 e/class/SendEmail.inc.php 和 e/class/connect.php 中:
$encoded = preg_replace_callback(
"/([\001-\010\013\014\016-\037\075\177-\377])/",
function($m) { return '='.sprintf('%02X', ord($m[1])); },
$encoded
);
特别注意:SendEmail.inc.php 第 1244、1247 行的 /e 修饰符必须手动确认修复状态。若未修复,邮件编码功能将完全不可用并抛出致命错误。
问题:each() 在 PHP 8 中已移除。
解决方案:在 e/class/doiconv.php、e/install/data/fun.php 中:
foreach($tmp as $key => $value) { ... }
问题:PHP 4 风格构造函数(方法名=类名)已弃用。
解决方案:在 e/class/SendEmail.inc.php、e/class/doiconv.php 中将同名方法改为 __construct()。
问题:mysql_* 扩展在 PHP 7+ 中已移除。
解决方案:
设置 e/config/config.php:$ecms_config['db']['usedb'] = 'mysqli';
如果 e/class/db/db_mysql.php 被加载,将所有 mysql_* 转换为 mysqli_*
注意参数顺序不同:mysql_query($sql, $link) 改为 mysqli_query($link, $sql)
问题:var $ 属性声明在 PHP 8.1+ 中已弃用。
解决方案:在 e/class/ftp.php、e/class/phpzip.inc.php 中:
class ftp {
public $ftpconnectid;
public $ftptranmode;
}
问题:PHP 8 改变了非严格比较规则。0 == "foo" 在 PHP 7 中为 true,在 PHP 8 中为 false。帝国CMS 大量使用 $classid == 0、$id != "" 等宽松比较来判断栏目ID或内容ID。
后果:当变量为空字符串 "" 或 "0" 时,条件判断结果反转,导致栏目导航丢失、分页失效或权限校验绕过。
解决方案:将关键业务逻辑中的 == / != 改为 === / !==,或对 $classid、$id 等核心变量强制 (int) 转型后再比较:
// 推荐:强制转型后严格比较
if ((int)$classid === 0) { ... }
// 推荐:严格不等于
if ($id !== '') { ... }
问题A:implode($pieces, $glue) 反向调用在 PHP 8 中已移除,直接抛出 Fatal Error。
问题B:create_function() 在 PHP 8 中完全移除,影响模板解析引擎和自定义标签处理。
解决方案:
// implode 修复:确保 glue 在前
$str = implode(',', $array);
// create_function 修复:替换为闭包
// 原代码
$callback = create_function('$matches', 'return strtoupper($matches[1]);');
// 修复后
$callback = function($matches) { return strtoupper($matches[1]); };
问题:split() 基于正则分割字符串,在 PHP 7.0 中已移除,PHP 8 中调用直接 Fatal Error。帝国CMS 部分旧版插件和自定义标签中仍在使用。
解决方案:统一替换为 explode()(按固定分隔符)或 preg_split()(按正则):
// 原代码
$arr = split(',', $str);
// 修复后(固定分隔符用 explode)
$arr = explode(',', $str);
// 原代码(正则分隔)
$arr = split('[,;|]', $str);
// 修复后
$arr = preg_split('/[,;|]/', $str);
问题:PHP 8 移除了 mbstring.func_overload 配置项。若服务器原先依赖该配置实现 GBK 站点中文处理,升级后会出现乱码或截取异常。帝国CMS 7.5 GBK 版受影响尤为严重。
解决方案:
确认 e/class/connect.php 中所有字符串操作使用 mb_substr/mb_strlen 时显式传入 'GBK' 编码参数
不再依赖 mbstring.func_overload 自动重载
UTF-8 版站点可忽略此项
问题:帝国CMS 的灵动标签、万能标签解析器内部使用 create_function() 动态生成回调,是第 12 条的特化场景。仅替换通用 create_function 可能遗漏标签解析器中的嵌套调用。
解决方案:重点检查以下文件中的 create_function 调用:
e/class/functions.php(ReplaceListVars、DoTempletTag 等函数)
e/class/t_functions.php
e/class/q_functions.php
将每个 create_function 逐一替换为匿名闭包,注意保持参数列表和返回值语义一致。
xcopy /E /I /Y "项目目录" "项目目录_php8备份"
编辑 e/class/connect.php:
对项目中的所有目标文件执行批量替换,将 count($var) 替换为 php8_count($var),注意保留确定数组类型的原生 count() 调用。
逐一检查 e/class、e/admin、e/member 目录下的函数,为缺少默认值的参数添加 null 默认值或使用 func_get_arg 兜底。
使用正则搜索 $(GET|POST|REQUEST|SESSION|COOKIE)[[a-zA-Z]+] 和 $classr[.*?][[a-zA-Z]+],为裸键名添加单引号。
全局搜索 create_function 和 split(,按第 12、13、15 条逐一替换为闭包和 explode/preg_split。