| 项 | 值 |
|---|---|
| 版本 | V1.0.3(version.php,末位数字 3 = 目录号 3) |
| 主题 | PHP 8.4 / 8.5 兼容性修复:消除「每请求级」弃用告警刷屏 + 8.5 新增弃用项 |
| 目标环境 | 手机端 WSToolbox 内置 PHP(实测 8.4+);IIS + 腾讯云函数(每 5 分钟触发 cron_trigger.php) |
| 涉及静态资源 | 无(未改 CSS / JS,故 sw.js 的 CACHE_VERSION 不需要递增,本包不含 sw.js) |
| 数据库结构变更 | 无(无表结构变更、无索引变更、无数据迁移) |
| 部署关键点 | 无配置项需改动;纯文件覆盖即可。新增 1 个文件 app/Core/SessionHandler.php,勿遗漏 |
| 兼容性 | 向下兼容 PHP 8.0+(项目最低要求),向上兼容 8.5;已在 8.4.25 与 8.5.10 真机验证 |
一、变更文件清单
| # | 文件 | 变更类型 | 问题编号 |
|---|---|---|---|
| 1 | app/Core/SessionHandler.php |
新增(全新文件) | H-2 |
| 2 | app/init.php |
第 41 行:6 个闭包 → 传对象 | H-2 |
| 3 | app/SplitDB/ViewCounter.php |
第 30 / 57 行:补 ?(2 处) |
M-4 |
| 4 | app/Helpers/CoverTemplate.php |
第 155 行前加 3 行多字节前置判断 | H-1 |
| 5 | app/Controllers/ApiController.php |
删 curl_close() / imagedestroy() 各 1 处 |
M-1 / M-2 |
| 6 | app/Helpers/Plugin.php |
删 curl_close() 1 处 |
M-1 |
| 7 | app/Helpers/Upload.php |
删 finfo_close() 1 处、imagedestroy() 3 处 |
M-2 / M-3 |
合计:7 个文件(1 新增 + 6 修改)、11 个代码位置。全部为「删除一个调用」或「补一个字符」级别,无逻辑重构。
二、逐项改动说明
1. H-2 app/init.php + app/Core/SessionHandler.php — 最严重项(每请求 1 条)
PHP 8.4 起弃用:session_set_save_handler() 若传入「独立回调」,必须改为传入一个
实现 SessionHandlerInterface 的对象。
线上实际报错(手机服务器):
Deprecated: session_set_save_handler(): Providing individual callbacks instead of
an object implementing SessionHandlerInterface is deprecated
in .../flinthub/app/init.php on line 41
改动:原 app/init.php:41 的 6 个闭包(open/close/read/write/destroy/gc)
整体迁入新增的 app/Core/SessionHandler.php,入口改为:
session_set_save_handler(new \app\Core\SessionHandler());
★ 关键设计决策(防行为漂移,请勿"优化"):
- 刻意【不】实现
SessionUpdateTimestampHandlerInterface(validateId/updateTimestamp)。 原写法只传了 6 个回调、未提供第 7/8 个;PHP 在session.use_strict_mode=1(本项目默认开启) 下走的是「read()返回空串即判定为新 ID」的默认路径。补上validateId会改变会话固定 攻击防护的判定路径,属隐性行为变更,故明确不补。 - 失败返回值与原实现逐一对齐:
read→''、write→false、destroy→false、gc→0。 - TTL 保持 1800 秒(
expires = time() + 1800),与原闭包完全一致。 - 沿用
Database::getSessionConnection()(独立 PDO 连接,与业务连接物理隔离), 避免 SplitDB 的 2014 游标冲突。
兼容性:
sessions表结构与数据格式未变,新旧实现可互相读取已存会话, 故本次改动不会导致任何在线用户掉线,回滚也不会。
2. M-4 app/SplitDB/ViewCounter.php — 每请求 2 条(8.4 与 8.5 都触发)
PHP 8.4 起弃用:参数类型前缺 ? 的「隐式可空」写法。
// 改前
public static function inc(int $topicId, PDO $mainDb = null): void
public static function flush(PDO $mainDb = null): int
// 改后
public static function inc(int $topicId, ?PDO $mainDb = null): void
public static function flush(?PDO $mainDb = null): int
为什么它很严重:index.php:280 注册的 register_shutdown_function 会无条件调用
ViewCounter::flush()(index.php:285),因此类文件 ViewCounter.php 每次请求都会被编译,
两个隐式可空参数就在编译期各发一条告警 → 每请求固定 2 条,
与是否启用 APCu、是否浏览主题都无关。
佐证:两处 docblock 早已写明 @param PDO|null $mainDb,即意图本就是可空,
只是语法漏了 ?。属纯补全,零行为变更。
3. H-1 app/Helpers/CoverTemplate.php:155 — 中文标题必刷屏(仅 8.5)
PHP 8.5 起弃用:ord() 传入非单字节字符串。
// 改前
private static function charUnits(string $ch): float
{
$o = \ord($ch); // ← 中文"中"是 3 字节,此处收到多字节字符串
if ($o < 0x80) { ... }
return 1.0;
}
// 改后(新增前置判断)
private static function charUnits(string $ch): float
{
if (\strlen($ch) > 1) {
return 1.0; // 多字节字符原本就走下面 return 1.0 分支
}
$o = \ord($ch);
if ($o < 0x80) { ... }
return 1.0;
}
触发条件:入参来自 preg_split('//u', $title, ...)(CoverTemplate.php:118),
即按 UTF-8 字符切分;中文/全角是 2–4 字节。任何含中文的标题在生成封面时都会触发。
零行为变更证明:\strlen($ch) === 1 时 ord() 必为单字节;多字节分支原本也返回 1.0。
实测 30 个 SVG(5 类标题 × 6 模板)改动前后逐字节一致(内容哈希
5b377287a9121a0687be01a0546b61e6d7d9ff99db4d1393955dcb87d92b2772)。
4. M-1 / M-2 / M-3 — 清理 8.5 弃用函数调用(共 7 处)
PHP 8.5 起弃用,且三者自 PHP 8.0 起均已无实际作用(对象自动释放),故直接删除:
| 编号 | 文件:行 | 删除的代码 | 备注 |
|---|---|---|---|
| M-1 | app/Controllers/ApiController.php:371 |
\curl_close($ch); |
句柄随 $ch 重赋值/作用域结束释放 |
| M-1 | app/Helpers/Plugin.php:201 |
curl_close($ch); |
curl_error() 已在上一行取完,不影响错误处理 |
| M-2 | app/Controllers/ApiController.php:500 |
imagedestroy($img); |
其后紧跟 exit,对象必然被回收 |
| M-2 | app/Helpers/Upload.php:220 |
\imagedestroy($src); |
仅删该调用,保留 return; |
| M-2 | app/Helpers/Upload.php:229 |
\imagedestroy($src); |
原图对象随作用域释放 |
| M-2 | app/Helpers/Upload.php:240 |
\imagedestroy($thumb); |
缩略图对象随作用域释放 |
| M-3 | app/Helpers/Upload.php:58 |
\finfo_close($finfo); |
$mime 已在上一行取完,不影响判断 |
删除安全性:这些都是「释放资源」的显式调用,PHP 8.0 起底层改为对象引用计数自动释放, 调用本身已成为 no-op。删除后功能实测全部正常(见 §4)。
三、部署步骤
- 备份(建议):备份
app/init.php、app/SplitDB/ViewCounter.php、app/Helpers/CoverTemplate.php、app/Helpers/Plugin.php、app/Helpers/Upload.php、app/Controllers/ApiController.php(6 个将被覆盖的文件)。 - 上传覆盖:把本包内
app/目录按原结构覆盖到站点根目录的app/。 - ★ 确认新增文件已上传:
app/Core/SessionHandler.php—— 这是本包唯一的新增文件,遗漏会导致站点致命错误(类不存在)。 - 更新版本号:用本包
version.php覆盖站点根目录的version.php(→V1.0.3)。 - 清缓存:
- 浏览器强刷(Ctrl+F5 / 手机端清站点数据)以刷新
sw.js缓存; - 若站点启用了 OPcache,重启 PHP 或等待 OPcache 过期(
opcache.revalidate_freq)。本包不含 CSS/JS 改动,
sw.js的CACHE_VERSION无需递增。
- 浏览器强刷(Ctrl+F5 / 手机端清站点数据)以刷新
- 无需:无需重启任何进程(本环境无常驻 worker)、无需改配置、无需执行迁移脚本、 无需访问任何升级 URL。
四、验证结果(真机实测,非静态分析)
验证环境:PHP 官方便携版 8.4.25 与 8.5.10(Windows NTS x64,
已启用 pdo_sqlite / sqlite3 / mbstring / fileinfo / openssl / curl / gd)。
4.1 全库编译期扫描(568 个 PHP 文件)
| PHP 版本 | 修复前 | 修复后 |
|---|---|---|
| 8.4.25 | 1 个文件 / 2 条告警 | 0 个文件 / 0 条 ✅ |
| 8.5.10 | 1 个文件 / 2 条告警 | 0 个文件 / 0 条 ✅ |
方法:对全库每个文件跑
php -l。隐式可空参数等弃用在编译期触发,php -l会直接报出, 比正则扫描权威。
4.2 站点路由冒烟(19 条路由)
| 路由 | 8.4.25 | 8.5.10 |
|---|---|---|
/、/index.php、/forum、/login、/register、/forgot-password |
200 | 200 |
/blog、/tags、/thread/1、/blog/1、/tag/1、/forum/category/1 |
200 | 200 |
/search、/message、/notifications、/profile、/post/new、/admin |
302(未登录跳转,正确) | 302 |
/assets/css/modern.css |
200 | 200 |
- 首页字节数:两版本均为 63,863 字节,与修复前完全一致。
protected/error.log在冒烟后为空 —— 修复前同样 19 条路由会产生 42 行告警。
4.3 逐项告警实测
| 项 | 修复前(8.4.25 / 8.5.10) | 修复后 |
|---|---|---|
H-2 session_set_save_handler() |
1 条 / 1 条 | 0 / 0 ✅ |
| M-4 隐式可空参数 | 2 条每请求 / 2 条每请求 | 0 / 0 ✅ |
H-1 ord() 中文标题 |
0 / 17 条 | 0 / 0 ✅ |
M-1 curl_close() |
0 / 1 条 | 0 / 0 ✅ |
M-2 imagedestroy() |
0 / 1 条 | 0 / 0 ✅ |
M-3 finfo_close() |
0 / 1 条 | 0 / 0 ✅ |
4.4 功能回归(确认删除调用未破坏功能)
| 功能路径 | 结果 | 弃用告警 |
|---|---|---|
Upload::makeThumbnail() 生成缩略图 |
✅ 返回 ok,缩略图 200×150、格式正确 |
0 |
finfo MIME 识别 |
✅ 正确识别为 image/png |
0 |
Plugin::http() 插件远程请求 |
✅ ok=true,取回 2701 字节 |
0 |
ApiController::fetchHopByHop() 拉图 |
✅ 取回 2701 字节 | 0 |
/api/captcha 路由 |
✅ 200,合法 PNG(魔数正确) | 0 |
/api/cover-preview(中文标题) |
✅ 200,合法 SVG,8.4 与 8.5 输出完全一致 | 0 |
CoverTemplate 30 个 SVG 输出 |
✅ 改动前后逐字节一致 | 0 |
4.5 语法检查
7 个文件在 PHP 8.0.2 / 8.4.25 / 8.5.10 三个版本下 php -l 均无语法错误、无弃用告警。
五、回滚方案
| 场景 | 回滚方式 |
|---|---|
| 任意文件出问题 | 用备份的旧文件覆盖即可。无 schema 变更、无数据迁移,无需任何数据库操作 |
| 仅 H-2 想回滚 | 恢复 app/init.php 旧版(6 个闭包版);app/Core/SessionHandler.php 可留着(无引用即无副作用)。sessions 表未变,在线用户不会掉线 |
| 版本号回滚 | 恢复旧 version.php |
回滚安全性:本包全部改动都是「消除告警」性质,不涉及业务逻辑、SQL、模板与静态资源, 回滚后行为与升级前完全一致。
六、附:为什么这些改动是安全的
- 零行为变更:H-1 与 M-4 属「语法补全」,输出已用哈希证明逐字节一致; M-1/M-2/M-3 删的是自 PHP 8.0 起就无效的 no-op 调用。
- H-2 是唯一的结构性改动,但已刻意保持 6 个方法与返回值语义 1:1 对齐,
且不补
validateId/updateTimestamp以维持原use_strict_mode行为。 - 不涉及数据库结构、索引、配置项、CSS/JS、插件文件。
- 已在 PHP 8.0.2 / 8.4.25 / 8.5.10 三个版本上验证,覆盖项目声明的最低版本与目标版本。