为什么还在跑 PHP 5.6 的老站该升级了
打开 phpinfo 一看,版本还停在 PHP 5.6 的网站比想象中多得多。这些站大多是几年前搭建的个人博客、企业展示站,当年部署完就一直没动过。PHP 5.6 在 2018 年 12 月就停止了官方安全支持,意味着此后发现的任何安全漏洞都不会再被修复——对一个暴露在公网、天天被扫描器问候的网站来说,这等于门户大开。除了安全,性能差距也很大:PHP 7 相对 5.6 有接近一倍的性能提升,PHP 8 在 7 的基础上又快了 20% 到 30%,同样是那台 1G 内存的小 VPS,升级后能扛住的并发直接翻倍。
很多站长不敢升级,是担心老程序、老主题不兼容,一升级网站就白屏。这个担心合理,但完全可以通过「先排查、后升级、可回滚」的流程化解。本文以 LNMP 环境为例(Nginx + PHP-FPM),完整演示如何把一套老站从 PHP 5.6 平稳迁移到 PHP 8.x,包括兼容性排查、多版本共存切换和常见报错修复。整个过程不需要重装系统,也不需要动数据库。
升级前:盘点环境与程序兼容性
动手之前先摸清家底,三条命令搞定:
php -v # 当前 PHP 版本
php -m # 已安装的扩展列表
php -i | grep -i 'extension_dir' # 扩展目录位置记下扩展列表,升级后要按同样的清单装齐(mysql、gd、curl、mbstring、openssl 这些最常见)。然后确认你的网站程序对 PHP 8 的支持情况:WordPress 官方对 PHP 8 支持完善,建议运行在 7.4 以上的版本;Typecho 1.2 可以在 PHP 8 上运行,但老主题和老插件常常出现弃用警告;如果是自己写的代码,重点排查下面几类「重灾区」语法。这一步的关键是搞清楚风险面:程序本体、主题、插件、以及你自己改过的代码,四部分都要过一遍。
强烈建议在动手前先做一次全量备份,至少包含网站文件和数据库:
tar czf site-backup.tar.gz /www/wwwroot/你的站点
mysqldump -u root -p 你的数据库名 > db-backup.sql备份文件下载到本地或对象存储里再开始升级,这是你所有操作的安全网。
兼容性重灾区:老代码最常见的四个坑
从 5.6 往上升级,绝大多数报错都集中在四个地方,提前排查能省掉 90% 的麻烦。
第一个坑:mysql_* 函数。PHP 7.0 直接移除了 mysql_connect、mysql_query 这一整组旧函数,程序里只要用到就会报「Call to undefined function mysql_connect()」。修法是把代码迁移到 mysqli 或 PDO,以最简的 mysqli 为例:
// 旧写法(PHP 7 起不可用)
$conn = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('mydb', $conn);
$res = mysql_query('SELECT * FROM posts', $conn);
// 新写法(mysqli)
$conn = mysqli_connect('localhost', 'user', 'pass', 'mydb');
$res = mysqli_query($conn, 'SELECT * FROM posts');如果程序里有几十处 mysql_ 调用,不想手动改,可以先用 PDO 封装一层,或者干脆考虑换用仍在维护的建站程序——老代码的维护成本往往比升级成本还高。
第二个坑:each() 和 create_function()。这两个函数在 PHP 7.2 被标记弃用、PHP 8.0 被彻底移除。each() 可以用 foreach 替代,create_function() 用匿名函数替代:
// 旧写法
$fn = create_function('$a', 'return $a * 2;');
// 新写法(匿名函数)
$fn = function ($a) {
return $a * 2;
};第三个坑:魔术引号和旧式构造函数。如果代码里还在用 get_magic_quotes_gpc 之类的函数,直接删掉相关逻辑——魔术引号在 PHP 5.4 就移除了,这些判断永远返回假。另外老代码里常见「方法名和类名相同」的旧式构造函数写法,PHP 8 会把它当成普通方法,需要在代码里改成 __construct。
第四个坑:动态属性。PHP 8.2 开始,给未声明属性直接赋值会触发弃用警告(Deprecated: Creation of dynamic property)。老代码里到处是 $obj->newField = xxx 这种写法,升级到 8.2 后日志会被警告刷屏。修法是在类里显式声明属性,或者临时给类加一行 #[AllowDynamicProperties] 注解过渡。
排查这些坑可以靠编辑器全局搜索关键词:mysql_、each(、create_function(、eregi(、mcrypt_ 等,逐个确认是否还有调用。也可以借助工具自动扫描,PHPCompatibility 标准配合 PHP_CodeSniffer 能按指定 PHP 版本扫出所有不兼容的语法点,Rector 甚至能自动改写大部分旧语法,个人站长把 PHPCompatibility 跑一遍就足够心里有数了。
正式升级:多版本 PHP 共存,随时可回滚
LNMP 环境升级 PHP 最稳妥的方式不是卸载旧的装新的,而是安装新版本和旧版本共存,切换 Nginx 的 fastcgi 配置指向新版本,出问题一键切回。以 Debian/Ubuntu 为例,可以用社区的 PHP 源(ondrej/php PPA 或 sury 源)安装多版本:
# Ubuntu 下添加 ondrej/php 源
add-apt-repository ppa:ondrej/php
apt update
# 安装 PHP 8.3 及常用扩展(按之前 php -m 的清单补齐)
apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd \
php8.3-mbstring php8.3-xml php8.3-zip安装完成后,系统里同时存在 php5.6-fpm 和 php8.3-fpm 两套服务,互不干扰。接着修改 Nginx 站点配置,把 fastcgi_pass 指向新版本的 socket:
# 旧:fastcgi_pass unix:/run/php/php5.6-fpm.sock;
# 新:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;修改前先复制一份原配置备份,然后执行:
nginx -t # 检查语法
nginx -s reload # 平滑重载,不断线
php8.3 -v # 确认命令行版本此时访问网站,如果一切正常就完成了切换;如果出现白屏或 500,马上把 fastcgi_pass 改回 php5.6 的 socket 再 reload,网站立刻恢复——整个过程只需要几秒钟,这就是多版本共存的最大好处。确认稳定运行几天后,再停掉旧版本服务、清理旧包:
systemctl stop php5.6-fpm
apt remove php5.6-* # 确认不再需要后执行用宝塔面板或 OneinStack 等一键环境的站长更简单:面板的软件商店里可以直接安装多版本 PHP,站点设置里下拉切换版本,原理和上面完全一样,只是界面化了。
升级后的收尾:看日志、开缓存、对比性能
切换完成后别急着走,三件事要做。第一,看错误日志。PHP-FPM 的日志路径一般在 /var/log/php8.3-fpm.log,程序自己的日志也要翻一翻,重点找 Deprecated 和 Fatal 两类:Deprecated 警告不影响运行但建议逐步清理,Fatal 错误必须马上处理。第二,确认 opcache 已开启。PHP 8 自带 opcache 且默认推荐开启,在 php.ini 里确认:
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=60opcache 能让 PHP 跳过重复的解析编译环节,是升级后性能提升的重要来源之一,没开等于白升级。第三,做一次简单的性能对比。升级前后各跑一次同样的压测(用 ab 即可),对比 QPS 和平均响应时间:
ab -n 1000 -c 20 https://www.example.com/正常情况下你会看到吞吐量明显提升、TTFB 下降。如果升级后性能反而变差,优先检查是不是有扩展没装齐导致 PHP 走了回退路径,或者 opcache 没生效。
常见报错速查表
最后附一张升级后最常见的报错对照表,遇到直接查:
Call to undefined function mysql_connect()
-> 改用 mysqli/PDO,或更换维护中的建站程序
Deprecated: Methods with the same name as their class...
-> 旧式构造函数,改成 __construct()
Deprecated: Creation of dynamic property X::$y
-> 显式声明属性,或临时加 #[AllowDynamicProperties]
Fatal error: Uncaught Error: Call to undefined function each()
-> 改用 foreach 遍历
Class 'mysqli' not found / Call to undefined function curl_init()
-> 扩展没装齐,apt install php8.3-mysql php8.3-curl 等升级常见问题答疑
把站长们升级时问得最多的问题集中回答一遍,遇到类似情况直接对照处理。
问:升级后网站白屏,但浏览器里看不到任何报错,怎么办?答:PHP 的报错被关了,先把错误显示打开定位问题。在 php.ini 里临时设置 display_errors=On 和 error_reporting=E_ALL,或者直接在入口文件开头加 ini_set('display_errors', '1');,白屏立刻会变成具体的报错信息。线上环境定位完记得关掉 display_errors,避免把路径等敏感信息暴露给访客。另一个信息源是 PHP-FPM 错误日志,默认路径在 /var/log/php8.3-fpm.log,里面的 Fatal error 是最直接的线索。
问:升到 8.2 后日志里全是动态属性弃用警告,一条条改太费劲,有过渡办法吗?答:有。给报错的类临时加上 #[AllowDynamicProperties] 注解,警告就会消失,等有空再逐个把属性改成显式声明。注意这个注解要加在类定义的上方,PHP 8.2 及以上版本支持,低版本会把它当普通注释忽略。
问:我用的是宝塔面板或 CentOS,没有 ondrej 源,怎么升级?答:宝塔用户直接在软件商店里安装 PHP 8.3,然后在站点设置里切换版本即可,面板会自动处理好依赖。CentOS 用户可以用 remi 源:yum install epel-release 之后启用 remi 的 php83 模块,再安装 php83-php-fpm 及对应扩展,切换 Nginx 的 fastcgi_pass 指向新 socket,原理和本文完全一致。
问:升级后感觉网站变慢了,是不是 PHP 8 反而更吃配置?答:先别急着下结论,大概率是缓存问题。切换 PHP 版本后,旧的 opcache 缓存可能还残留着旧字节码,执行 php-fpm 的重启命令(systemctl restart php8.3-fpm)清掉缓存再测。另外确认扩展是否装齐——缺了 mbstring 或 gd 时,一些程序会静默走纯 PHP 的降级实现,性能差好几倍。
问:版本是不是越新越好,直接上 8.4 行不行?答:对老站来说,稳定性优先于新特性。建议选发布超过一年、生态兼容性经过验证的版本,比如 8.3:既有长期安全支持,主流建站程序和插件对它的兼容性也最成熟。追求新特性再考虑更新的版本,但升级前务必确认你的程序和主题没有已知兼容问题。
升级 PHP 其实没有想象中那么可怕:备份打底、新旧版本共存、Nginx 秒级切换,这套流程保证了任何一步出错都能立刻回滚。花一个下午把跑了好几年的 5.6 老站升到 8.x,换来的是安全漏洞的彻底修复、接近翻倍的性能,以及未来几年不用再为版本发愁的安心。如果你的站还在 5.6 上,今天就是动手的最好时机。