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 结束) 安全风险
⚠️ 一个常被忽略的事实:截止 2026 年 10 月,PHP 8.0 自身也已经结束了官方支持(2023 年 11 月)。8.1 也仅剩安全修复窗口。但从 7.4 升 8.0 依然是很多项目的现实选择——7→8 的巨大跨越中,8.0 是第一个"新范式"版本,先升到这里,后续升到 8.2/8.3 的增量就很平滑了。

二、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.0
  • php -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 的后续升级同样有参考价值——每跨一个小版本的破坏点增量其实很小。

参考链接