2023 年 11 月 PHP 7.4 正式结束了 Bugfix 支持,进入安全模式;2024 年 12 月安全支持也告一段落。意味着从现在起,7.4 上发现的新 CVE 不会再有官方补丁。但现实是,至今仍有大量项目跑在 PHP 7.4 上——WordPress 官方统计在 2026 年中仍有约 15% 的用户停留在 7.4,国内中小项目比例可能更高。
升级到 8.0 是这些项目最直接、风险最低的选择(8.1/8.2/8.3 虽然已发布多年,但一次性跨两个大版本的破坏性变更更多)。本文分两步走:先帮你搞清楚 7.4 和 8.0 到底差在哪,再给一份从 7.4 升 8.0 的实操指南,覆盖 Composer 项目、源码编译和 Windows 官方包三种路线。
一、核心差异速查表
| 维度 | PHP 7.4 | PHP 8.0 | 影响程度 |
|---|---|---|---|
| JIT 编译 | ❌ 无 | ✅ 支持,opcache.jit_buffer_size=128M |
性能关键 |
| 类型系统 | 属性类型 + 返回类型 + mixed | 构造器属性提升 + union 类型 + nullsafe + 匹配表达式 | 代码层 |
| 错误模型 | 大量 Warning/Notice | 关键场景升级为 Error 异常(不可捕获) | 兼容性关键 |
| 命名参数 | ❌ 不支持 | ✅ 形参可按名传递,提升代码可读性 | 可选改进 |
| match 表达式 | ❌ 只有 switch | ✅ 返回值、非松散比较、可 return | 可选改进 |
| 原生 8 位字符串 | 字符串和数组共享偏移语法 | 字符串上 {$str[0]} 已废弃(Deprecated) |
破坏性 |
| 动态属性 | 任意对象可动态挂属性 | 8.0 起 Deprecated,8.2 起默认 Error | 破坏性 |
| 默认错误报告 | 生产常用 error_reporting = E_ALL & ~E_NOTICE |
建议 E_ALL,Notice 也需关注 |
运维配置 |
| 支持状态(2026-10) | ❌ 完全 EOL | ❌ 完全 EOL(2023-11 结束) | 安全风险 |
二、PHP 8.0 值得关注的新特性
1. 构造器属性提升(Constructor Property Promotion)
这是 8.0 最受欢迎的语法糖,把"声明属性 + 构造器参数赋值"两行代码压缩成一行:
// PHP 7.4 写法
class UserDTO {
public int $id;
public string $name;
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}
// PHP 8.0 写法(属性提升)
class UserDTO {
public function __construct(
public int $id,
public string $name,
) {}
}
DTO(Data Transfer Object)、VO(Value Object)、Service 类依赖注入等场景下,这个语法能减少 30%~50% 的样板代码。但注意兼容写法——如果项目要同时支持 7.4 和 8.0,就不能用这个特性。
2. union 类型与 mixed 类型
// PHP 7.4:只能用 docblock 标注
/**
* @param int|string $id
*/
public function find($id) {}
// PHP 8.0:原生联合类型
public function find(int|string $id): ?Model {
return $this->query->whereKey($id)->first();
}
// mixed 类型(8.0 新增,表示"任意类型")
public function setConfig(string $key, mixed $value): void {}
3. nullsafe 运算符 ?->
链式调用时如果中间环节是 null,7.4 会直接抛错,8.0 可以用 ?-> 安全跳过:
// PHP 7.4:需要层层判空
$country = $user->getAddress() ? $user->getAddress()->getCountry() : null;
// PHP 8.0:一行搞定
$country = $user?->getAddress()?->getCountry()?->getName();
这不是"语法糖"级别的改进——大型项目中依赖链条很长时,nullsafe 能显著减少空指针类 bug。
4. match 表达式
match 比 switch 强在三点:返回值、非松散比较、每个 case 是独立作用域:
// PHP 7.4 的 switch(松散比较、不返回值、需要 break)
$level = null;
switch ($score) {
case 100:
$level = 'S';
break;
case 90:
$level = 'A';
break;
default:
$level = 'C';
}
// PHP 8.0 的 match
$level = match(true) {
$score >= 100 => 'S',
$score >= 90 => 'A',
default => 'C',
};
5. JIT 即时编译
PHP 8.0 内置了 JIT(Just-In-Time)编译器,可以把热点函数编译成本地机器码,在 CPU 密集型场景下带来 1.5x~3x 的性能提升。但 JIT 有几个前提:
- 必须开启 Opcache:
opcache.enable=1、opcache.enable_cli=1(CLI 模式也生效) - 分配 JIT 缓冲区:
opcache.jit_buffer_size=128M(按项目大小调整) - Web 场景要注意 JIT 的内存开销,小站点可能收益不明显反而多占内存
数据库查询重的传统 Web 项目,JIT 收益通常不如预期大;但计算密集的 AI 推理、图像处理、纯算法项目,JIT 效果惊人。
三、破坏性变更清单(Breaking Changes)
这一节是升级前必须逐条过的"雷区地图"。经验教训表明,升级过程中 80% 的线上事故来自下面这些点。
1. 内部函数签名变更
多个内置函数的参数类型和返回值在 PHP 8.0 中被收窄或补齐。最常见的影响:
// sizeof() / count():以前传非 Countable 会 Warning 返回 1,现在直接 TypeError
// PHP 7.4:
count('hello'); // Warning + 返回 1
// PHP 8.0:
count('hello'); // TypeError: count(): Argument must be of type Countable|array
// str_starts_with / str_contains / str_ends_with:这三个是 8.0 新增函数,
// 但以前很多人用 polyfill(如 symfony/polyfill-php80)自己实现,升级后会产生冲突
// 解决:卸载 polyfill,用原生函数
2. 类的重写方法必须兼容签名
// PHP 7.4:子类可以随便改父类方法签名(松散)
class Base {
public function find($id) {}
}
class Child extends Base {
public function find(int $id): ?Model {} // 7.4 允许
}
// PHP 8.0:子类签名必须与父类/接口兼容(Contravariance)
// 参数可以更宽松,返回值可以更严格;反过来不行
class Child extends Base {
public function find(int|string $id): ?Model {} // ✅ 参数更宽
public function find(int $id): Model {} // ❌ 返回更窄,违反 Liskov
}
如果你的项目实现了 Iterator、Countable、ArrayAccess、Serializable 等内部接口,8.0 对这些接口方法的签名校验比 7.4 严格得多。升级前一定要跑一遍完整的单测。
3. 动态属性 Deprecated(8.0 起警告,8.2 起报错)
// PHP 7.4:随便挂,没人管
$obj->temporaryData = 'foo';
// PHP 8.0:触发 Deprecated 警告
// PHP 8.2:默认 Error:Attempt to assign property "temporaryData" on ...
历史项目里这种"随手挂个属性"的代码很常见。8.0 先给 Deprecated 提醒,所以在 8.0 上跑一遍、看错误日志就能扫出所有问题。
4. 字符串偏移访问语法变更
// PHP 7.4:花括号和方括号都行
$str{0}; // ✅
$str[0]; // ✅
// PHP 8.0:花括号触发 Deprecated
$str{0}; // ⚠️ Deprecated: String offset with { } is deprecated
$str[0]; // ✅ 继续用方括号
// PHP 8.1+:花括号完全移除,直接 Fatal Error
5. 默认 timezone 必须显式指定
// PHP 7.4:不设置也能跑,用系统默认 timezone
// PHP 8.0:不设置 date.timezone 会触发 Warning
date('Y-m-d'); // 8.0 下 Warning:date(): Invalid date.timezone setting
// 解决:php.ini 里设 date.timezone = 'Asia/Shanghai'
四、升级实操指南
路径 A:Composer 项目升级(推荐,最平滑)
这是大多数 Laravel / Symfony / ThinkPHP 项目的标准路线。
Step 1:升级前体检
# 检查当前依赖声明的 php 约束,看哪些包可能卡住
composer show --self | grep "requires\|php"
cat composer.json | grep "php"
# 运行 Rector(推荐工具:自动修 80% 破坏性变更)
# 安装:
composer require rector/rector --dev
# 生成基础配置:
vendor/bin/rector init
# 最稳妥的做法:把 PHP 7.4 规则跑完,再手动收尾
vendor/bin/rector process src --set php74
# 然后再跑 8.0 规则
vendor/bin/rector process src --set php80
Step 2:修改 composer.json 的 PHP 约束
{
"require": {
"php": "^8.0.0",
"...": "..."
},
"config": {
"platform": {
"php": "8.0.31"
}
}
}
config.platform.php 是关键——它告诉 Composer "把我当成 8.0.31 来解依赖",否则 Composer 会用当前运行环境的 PHP 版本判断,容易出现"在 7.4 机器上运行 Composer,装不上 8.0 包"的乌龙。
Step 3:更新依赖
# 先 update 所有核心框架包(Laravel/Symfony/ThinkPHP)
composer update laravel/framework --with-all-dependencies
composer update symfony/framework-bundle --with-all-dependencies
# 再 update 剩余依赖
composer update --no-dev
# 最后装 dev 依赖
composer install
# 清缓存
php artisan optimize:clear # Laravel
php bin/console cache:clear # Symfony
Step 4:在开发机上跑一遍,收集错误
# 把 php.ini 的 display_errors 打开,error_reporting 设 E_ALL
# 然后访问所有主要页面,看日志里的 Warning / Deprecated / Error
tail -f storage/logs/laravel*.log # Laravel
tail -f var/log/dev.log # Symfony
# 重点关注:
# - Deprecated 警告(未来版本会变 Error)
# - TypeError / ValueError(参数类型不对)
# - ReturnTypeError(返回值不对)
路径 B:Windows 官方包升级
如果你的项目是 Windows 开发环境 / 本地调试,直接去 php.net 下载对应包替换即可。
# 1. 备份当前 php 目录(比如 C:\php\7.4\)
# 2. 下载 PHP 8.0 的 x64 Non-Thread Safe 包
# https://windows.php.net/download/
# 选 "VS16 x64 Non Thread Safe" 或 "VS16 x86 Thread Safe"(与你之前装的一致)
# 3. 解压到新目录 C:\php\8.0\
# 4. 把旧目录的 php.ini-production 复制过去改名为 php.ini
# 修改: extension_dir、date.timezone、opcache 相关配置
# 5. 把旧目录的 /ext 下自定义扩展(如 swoole、xlswriter)复制过去
# 6. 修改 PATH 环境变量,把 C:\php\8.0 提到最前
# 7. 验证:
php -v
php -m
路径 C:源码编译升级(Linux / macOS 通用)
# 依赖安装(以 Ubuntu/Debian 为例)
sudo apt-get update
sudo apt-get install -y build-essential libxml2-dev libssl-dev libcurl4-openssl-dev \
libjpeg-dev libpng-dev libfreetype6-dev libzip-dev libonig-dev libicu-dev \
libsqlite3-dev libbz2-dev libreadline-dev
# 下载并编译(建议通过 phpenv / phpx / brew 等工具管理)
cd /usr/local/src
wget https://www.php.net/distributions/php-8.0.31.tar.xz
tar xvf php-8.0.31.tar.xz
cd php-8.0.31
./configure \
--prefix=/usr/local/php80 \
--enable-fpm \
--enable-opcache \
--with-jit \
--enable-mysqlnd \
--with-pdo-mysql \
--with-freetype \
--with-jpeg \
--with-zlib \
--with-curl \
--with-bz2 \
--with-readline \
--with-zip \
--with-intl \
--with-openssl \
--enable-mbstring \
--disable-cgi
make -j$(nproc)
sudo make install
# 验证
/usr/local/php80/bin/php -v
五、升级前后检查清单
✅ 升级前
- 全站代码在 7.4 下无 Fatal Error
- 单元测试全部通过(至少关键业务路径)
- 生产环境数据库已完整备份
- composer.json 里的 PHP 版本约束已调整
- 所有 polyfill 包(symfony/polyfill-php80 等)已准备好卸载
- phpstudy / docker-compose / Dockerfile 等基础设施文件已同步更新 PHP 版本
✅ 升级后(开发机上验证)
php -v确认版本 8.0php -m确认所有必要扩展都加载成功- CLI 模式下跑一遍主入口脚本无报错
- Web 访问首页、列表页、详情页、登录页均正常
- 后台管理登录、增删改查均正常
- cron / 定时任务命令正常执行
- 错误日志里无 Fatal/TypeError,Deprecated 警告可接受
六、踩过的坑
下面三个坑几乎是每一次 7.4→8.0 升级都会踩到的,提前知道就能避开。
坑 1:composer.lock 把 polyfill 锁死了
symfony/polyfill-php80(以及 81、82)提供了 str_starts_with、str_contains 等函数在旧版 PHP 上的兼容实现。升级到 8.0 后,这些函数已经内置了,但 polyfill 还在——会产生"函数重复定义"Fatal Error。解法:composer remove symfony/polyfill-php80(以及对应的 php71/72/73 polyfill),只保留需要的(polyfill-php83/polyfill-php84 等)。
坑 2:json_encode 默认行为变了
PHP 8.0 的 json_encode 对数组中包含非法 UTF-8 字符时的处理方式,和 7.4 有细微差异。7.4 会静默返回 false 或空字符串,8.0 在某些场景下会直接触发 Warning。解法:所有 json_encode 调用点加 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES 参数,并在返回 false 时记录日志。
坑 3:preg_replace 的 /e 修饰符彻底移除
PHP 7.0 起就 Deprecated,8.0 开始直接 Error。preg_replace($pattern, '/e', $replacement, $subject) 这种代码必须改成 preg_replace_callback。Rector 可以自动处理这类转换。
写在最后:要不要直接升 8.0?
坦诚说,在 2026 年这个节点上,直接升 8.0 未必是最优解——8.0 自身也已经 EOL 三年了。如果你的项目代码量不大、框架版本跟得上,一步到位升 8.2 或 8.3 也是值得考虑的方案。Laravel 11+ / Symfony 7+ / ThinkPHP 8+ 都已经明确要求 PHP 8.2+。
但如果你在一个维护多年的老项目上、依赖链比较深、想把风险拆成两步(先 7→8.0 把破坏性变更修掉,再 8.0→8.2 走平滑升级),那 8.0 作为过渡版本依然是正确的选择。本文的对比和教程,对 8.0→8.1→8.2 的后续升级同样有参考价值——每跨一个小版本的破坏点增量其实很小。
参考链接
- PHP 8.0 Release Notes:https://www.php.net/releases/8.0/
- PHP 8.0 Migration Guide:https://www.php.net/manual/en/migration80.php
- Rector 自动升级工具:https://getrector.com/
- PHP 官方支持时间表:https://www.php.net/supported-versions.php