typecho_cover.jpg

Typecho 1.3.0 + PHP 8.x 兼容性踩坑记录:从搬家到全面修复

前言

最近把 Typecho 博客从旧服务器搬家到新环境,结果第二天后台就炸了——admin/index.php 直接报 500 错误,打开 debug 模式反而又正常了。一番排查下来,发现是 Typecho 1.3.0 和 PHP 8.x 的兼容性问题,涉及 6 个文件的修复。

这篇文章记录了完整的排查和修复过程,希望能帮到遇到类似问题的朋友。

环境信息

  • Typecho 版本:1.3.0
  • PHP 版本:8.5.x
  • 数据库:MySQL 5.7
  • Nginx:OpenResty(1Panel 部署)

问题一:admin/index.php 报 500 错误

现象

直接访问后台首页 admin/index.php 报 Server Error 500,但其他管理页面正常,前台也正常。开启 __TYPECHO_DEBUG__ 后又能正常访问。

排查过程

开启 debug 模式后发现是 Contents.php___path() 方法报错:

TypeError: Router::url(): Argument #1 ($name) must be of type string, null given

数据库中部分文章/评论的 type 字段为 NULL,而 PHP 8 严格类型要求 Router::url() 的第一个参数必须是 string。

修复

文件: var/Widget/Base/Contents.php

// ___path() 方法
// 修改前
return Router::url($this->type, $this);

// 修改后
return Router::url($this->type ?? 'post', $this);

问题二:getRouterParam 返回 null

现象

同样在 Contents.php 中,getRouterParam() 方法声明返回类型为 string,但 cidslug 可能为 null。

修复

// getRouterParam() 方法
// 修改前
return $this->cid;
return urlencode($this->slug);

// 修改后
return (string)$this->cid;
return urlencode((string)$this->slug);

问题三:strlen(null) 弃用警告

现象

PHP 8.1+ 不再允许向 strlen() 传递 null 值。文章密码字段为空时触发 Deprecated 警告。

修复

// ___hidden() 方法
// 修改前
strlen($this->password) > 0 &&

// 修改后
strlen($this->password ?? '') > 0 &&

问题四:Cookie 域名解析失败

现象

Cookie.phpsetPrefix() 方法中,parse_url() 解析域名失败时返回 null,直接赋值给 string 类型属性导致 TypeError。

修复

// setPrefix() 方法
// 修改前
self::$domain = $parsed['host'];

// 修改后
self::$domain = $parsed['host'] ?? '';

问题五:附件大小显示 0kb

现象

搬家后所有附件在后台显示为 0kb,图片无法预览。但数据库中 size 字段数据完整,磁盘文件也存在。

根本原因(重要)

这是搬家场景特有的问题。旧版 Typecho 使用 PHP 序列化格式 存储附件信息:

a:5:{s:4:"name";s:14:"image.png";s:4:"path";s:34:"/usr/uploads/2022/09/xxx.png";s:4:"size";i:45320;...}

但 Typecho 1.3.0 改成了用 json_decode() 解析:

$content = json_decode($this->row['text'], true);

json_decode() 遇到 PHP 序列化数据返回 null,导致所有附件属性(size、mime、name、path)全部为空。

修复

文件: var/Widget/Base/Contents.php

// ___attachment() 方法
$content = json_decode($this->row['text'], true);
// 新增:兼容旧版 PHP 序列化格式
if (!is_array($content)) {
    $content = @unserialize($this->row['text']);
}
if (!is_array($content)) {
    $content = [];
}

问题六:manage-medias.php TypeError

现象

访问「管理媒体」页面报错:

TypeError: Typecho\\Common::mimeIconType(): Argument #1 ($mime) must be of type string, null given

修复

// admin/manage-medias.php 第66行
// 修改前
mimeIconType($attachments->attachment->mime);

// 修改后
mimeIconType($attachments->attachment->mime ?? '');

admin/media.php 第20行同理。

修复文件汇总

文件问题
var/Typecho/Cookie.phpparse_url 返回 null
var/Widget/Base/Contents.php___path type null
var/Widget/Base/Contents.phpgetRouterParam 类型转换
var/Widget/Base/Contents.php___hidden strlen null
var/Widget/Base/Contents.phpattachment 0kb(序列化兼容)
admin/manage-medias.phpmimeIconType null
admin/media.phpmimeIconType null

经验总结

  1. 搬家必查数据格式:Typecho 不同版本的数据存储格式可能不同,搬家后要验证关键功能
  2. PHP 8 严格类型是把双刃剑:逼着你写更健壮的代码,但对老项目兼容性打击很大
  3. 备份备份备份:修改核心文件前一定要备份,我给每个文件都留了 .bak 文件

回滚方法

如果需要恢复原文件:

cd /path/to/typecho
cp var/Typecho/Cookie.php.bak var/Typecho/Cookie.php
cp var/Widget/Base/Contents.php.bak var/Widget/Base/Contents.php
cp admin/manage-medias.php.bak admin/manage-medias.php
cp admin/media.php.bak admin/media.php

本文记录了 2026 年 5 月在 Typecho 1.3.0 + PHP 8.5 环境下的实际修复经验。