紧接上一篇 PHP 7.4 vs 8.0 全面对比,本篇聚焦 8.0 → 8.1 的升级路线。和 7→8 那种"范式级跨越"不同,8.0→8.1 是一次增量型升级:破坏性变更极少,新特性以"补齐 8.0 未覆盖的语法糖"为主,但枚举(Enums)和 Fiber(协程)两个重量级特性的引入,让 8.1 在工程化能力上了一个台阶。
PHP 8.1 发布于 2021 年 11 月,Bugfix 支持到 2022 年 6 月,安全修复到 2024 年 12 月。写本文时(2026 年 10 月)8.1 也已经 EOL 快两年了。但和 8.0 一样,8.1 仍然是很多"老项目想升到新范式但又不想一步跨到 8.2"的中间停靠站——尤其是 8.1 引入的枚举和 readonly 属性,很多框架(Laravel 9+ / Symfony 6+ / ThinkPHP 8+)都已经在核心代码里使用。
一、核心差异速查表
| 维度 | PHP 8.0 | PHP 8.1 | 影响程度 |
|---|---|---|---|
| 枚举 (Enums) | ❌ 无,需用常量/自定义 class 模拟 | ✅ 原生支持,带方法、类型、cases() | 代码层重磅 |
| readonly 属性 | ❌ 无 | ✅ 类属性声明后只读,构造器赋值后不可变 | 可选改进 |
| Fiber 协程 | ❌ 无(只有 ext-swoole/amp 等 PECL 扩展) | ✅ 内置 Fiber 类,可挂起/恢复执行 | 性能/架构 |
| never 返回类型 | ❌ 只有 void | ✅ 原生 never 表示"永不返回" |
可选改进 |
| 第一类可调用语法 | ['Foo', 'bar'] / Foo::class . '::bar' |
Foo::bar(...) 直接获取 Closure |
语法糖 |
| 数组辅助函数 | array_key_first/last(7.3 引入) |
新增 array_is_list()、array_diff_assoc_key() |
可选改进 |
| Serializable 接口 | ✅ 正常工作 | ⚠️ Deprecated,8.2 起改用 __serialize/__unserialize |
破坏性 |
| JIT | 基础支持,缓冲区固定 | 改进:支持函数级别 JIT 配置,profile 模式优化 | 性能 |
| 对象复制 | clone 全量拷贝 |
readonly 对象 clone 后属性不可变(构造器只读一次) | 语义变更 |
| 支持状态(2026-10) | ❌ 完全 EOL(2023-11) | ❌ 完全 EOL(2024-12) | 安全风险 |
二、PHP 8.1 三大重磅新特性
1. 枚举(Enums)—— 8.1 最值得升级的理由
在枚举出现之前,PHP 开发者表示"一组有限的常量值"有三种方式:define 常量、class 常量(const)、或者自己写一个带静态属性的类。三种都有缺陷——常量没有类型、无法 switch、不能带方法。8.1 原生枚举把这个问题一次性解决了:
// PHP 8.0:用常量模拟枚举
class OrderStatus {
const PENDING = 'pending';
const PAID = 'paid';
const SHIPPED = 'shipped';
const CANCELLED = 'cancelled';
}
// 问题:没有类型系统,任何字符串都能传进来
public function updateStatus(string $newStatus) {} // 谁能保证传的一定是合法值?
// PHP 8.1:原生枚举
enum OrderStatus: string {
case PENDING = 'pending';
case PAID = 'paid';
case SHIPPED = 'shipped';
case CANCELLED = 'cancelled';
// 枚举可以带方法
public function isFinal(): bool {
return match($this) {
self::SHIPPED, self::CANCELLED => true,
default => false,
};
}
// 可以带静态工厂
public static function fromLabel(string $label): self {
foreach (self::cases() as $case) {
if ($case->label() === $label) return $case;
}
throw new InvalidArgumentException("Unknown label: $label");
}
}
// 使用:类型安全、IDE 自动补全、switch 可穷举检查
public function updateStatus(OrderStatus $status): void {
if ($status->isFinal()) {
throw new LogicException('Cannot change a final status');
}
$this->status = $status; // 存的是 enum 对象,不是字符串
}
$status = OrderStatus::PAID;
echo $status->value; // "paid"(string 类型枚举)
echo $status->name; // "PAID"
OrderStatus::cases(); // [OrderStatus::PENDING, ...]
OrderStatus::from('paid'); // 反向查找(backed enum 可用)
枚举分两种:纯枚举(Pure Enum)不带值、Backed 枚举绑定 string/int。Backed 枚举自动提供 from() / tryFrom() 静态方法做反向查找(从存储值还原枚举对象),这在数据库反序列化、API 参数校验场景下特别好用。
Laravel 9+ 的 Model 里 castAsEnum() 方法就是为 8.1 枚举准备的,Symfony 的 EnumType 表单组件也在 6.1 加了原生支持。如果你现在还在项目里用常量模拟枚举,8.1 是彻底告别这种反模式的好时机。
2. readonly 属性 —— 不可变对象的第一步
8.1 引入的 readonly 关键字,让类属性在构造器赋值后就不能再修改,实现了"构造时一次性写入"的语义:
class ProductVO {
public readonly string $name;
public readonly float $price;
public readonly string $sku;
public function __construct(string $name, float $price, string $sku) {
$this->name = $name;
$this->price = $price;
$this->sku = $sku;
}
}
$p = new ProductVO('T-shirt', 99.9, 'SKU-001');
$p->name = 'Polo'; // Error: Cannot modify readonly property ProductVO::$name
readonly 和 DDD(领域驱动设计)里的 Value Object 概念完美契合:一个 VO 创建后就应该保持不可变,任何"修改"操作都应该返回一个新实例而不是原地修改。readonly 不是"语法糖"——它是 PHP 语言层面强制不可变语义,能帮你把很多隐式 bug(比如意外修改了作为数组 key 的对象、或者异步任务里多个协程同时修改一个状态对象)消灭在编译期。
配合 8.1 的构造器属性提升,VO 的写法可以再简化一层:
readonly class ProductVO {
// readonly 类:所有属性自动 readonly
public function __construct(
public string $name,
public float $price,
public string $sku,
) {}
}
// ↑ 这在 8.2 才正式支持 readonly class 语法(8.1 只能逐个属性写 readonly)
// 8.1 的写法:
class ProductVO {
public readonly string $name;
public readonly float $price;
public readonly string $sku;
public function __construct(string $name, float $price, string $sku) {
$this->name = $name;
$this->price = $price;
$this->sku = $sku;
}
}
3. Fiber —— PHP 内置协程时代的开端
Fiber 是 PHP 8.1 被讨论最多的新特性,也是最容易被误解的特性。先说 Fiber 是什么:它是一个语言级的、可以被挂起和恢复的执行上下文,让 PHP 代码在 不修改现有语法 的情况下实现协程调度。
和 swoole、amphp 等"需要换 runtime、需要用 yield 或 async/await"的协程方案不同,Fiber 让你可以写这样的代码:
// PHP 8.1 Fiber 基本用法
$fiber = new Fiber(function (int $a, int $b): int {
$sum = $a + $b;
Fiber::suspend($sum); // 挂起并返回中间值
return $sum * 2;
});
$result1 = $fiber->start(3, 4); // 返回 7(suspend 的值)
$result2 = $fiber->resume(10); // 返回 14(最终值)
但 Fiber 本身只是基础原语,它不提供事件循环、不提供 HTTP 客户端、不提供数据库驱动——这些需要由上层库(比如 amphp 或 reactphp 的 Fiber 分支)来实现。换句话说,8.1 引入 Fiber 是把"能不能做协程"这个问题从"换 swoole runtime"变成了"不换 runtime 也能做",但离"开箱即用的异步框架"还有几步。
所以现实建议是:不要为了 Fiber 单独升 8.1。等 8.2/8.3 时代,当主流框架(Laravel/Symfony)的 Fiber 生态成熟了,再一起升不迟。但 8.1 引入 Fiber 这个事实本身很重要——它意味着 PHP 官方终于承认了"并发不是扩展的事,是语言的事"。
三、8.0→8.1 破坏性变更清单
好消息:8.0→8.1 的破坏性变更非常有限,大部分是 Deprecated 警告级别的。下面列的是升级过程中最有可能撞到的几个。
1. 父类方法签名变化(Contravariance 进一步收紧)
// PHP 8.0:子类参数类型可以更严格(Contravariance 还没完全实施)
class Base {
public function find($id) {} // 无类型
}
class Child extends Base {
public function find(int $id) {} // 8.0 警告,8.1 变成 Fatal Error
}
// 正确做法:子类参数类型必须"不窄于"父类
// 即父类参数是 A,子类可以用 A 或 A 的父类,但不能用 A 的子类
class Child extends Base {
public function find(int|string $id) {} // ✅
public function find(mixed $id) {} // ✅ 最宽松
}
2. Serializable 接口 Deprecated
// PHP 8.0:可以这样写
class MyClass implements Serializable {
public function serialize() { return serialize($this->data); }
public function unserialize($data) { $this->data = unserialize($data); }
}
// PHP 8.1:触发 Deprecated
// PHP 8.2:正式移除 Serializable 接口,必须改用 Magic Methods
class MyClass {
public function __serialize(): array { return ['data' => $this->data]; }
public function __unserialize(array $data): void { $this->data = $data['data']; }
}
8.1 只是给 Deprecated 警告,所以你有足够时间去改。Rector 有现成的规则可以一键把所有 Serializable 实现转换成新写法。
3. 内置函数参数默认值变化
少数函数的默认行为在 8.1 有调整,最可能撞到的两个:
strftime()的 locale 处理在 Windows 上有变化(底层用了不同的 C runtime);imagecreatefromstring()对非法图片数据的处理,8.0 返回false,8.1 在某些场景下会触发ValueError。
这类变更频率很低,建议用 Rector 的 php81 规则集自动扫一遍。
四、升级实操指南
8.0→8.1 的升级流程比 7→8 简单得多,大多数项目只要改 composer.json 再 update 一次就能跑起来。下面按场景分条列出。
路径 A:Composer 项目升级(90% 的情况,5 分钟搞定)
# 1. 修改 composer.json
# 把 "php": "^8.0.0" 改成 "^8.1.0"
# 同时更新 config.platform.php
# "config": { "platform": { "php": "8.1.31" } }
# 2. 升级
composer update --with-all-dependencies
composer install --no-dev # 如果是生产环境
# 3. 跑 Rector(可选,自动扫 Deprecated)
composer require rector/rector --dev
vendor/bin/rector process src --set php81
# 4. 清缓存 + 冒烟测试
php artisan optimize:clear # Laravel
php bin/console cache:clear # Symfony
# 然后访问首页、跑一遍自动化测试
Composer 项目升级 8.1 最容易踩的坑不是 PHP 本身的兼容性,而是依赖包的 composer.json 里写死了 "php": "~8.0" 这种版本约束。遇到这种情况:
# 方法 1:--ignore-platform-reqs 强制升级(最后手段)
composer update --ignore-platform-reqs
# 方法 2:升级那个依赖包到支持 8.1 的最新版本
composer update problematic/package --with-all-dependencies
# 方法 3:换包(如果原作者不再维护)
# composer require alternative/package
# composer remove problematic/package
路径 B:Windows 官方包升级
# 1. 下载 PHP 8.1 官方包
# https://windows.php.net/download/
# 选 VS16 x64 Non Thread Safe / Thread Safe(与你 8.0 包类型一致)
# 2. 备份旧 php.ini,解压新包到新目录
# 3. 修改 PATH 把 PHP 8.1 提到最前
# 4. 迁移扩展:把旧目录 ext/ 下自定义扩展(swoole 等)复制过去
# 注意扩展版本也要对应 8.1(旧版本的 .dll 可能不兼容)
# 5. 验证
php -v # 应该显示 PHP 8.1.x
php -m # 确认必要扩展都在
路径 C:源码编译升级
和 8.0 编译的 configure 参数几乎完全一样,只需要改版本号:
cd /usr/local/src
wget https://www.php.net/distributions/php-8.1.31.tar.xz
tar xvf php-8.1.31.tar.xz
cd php-8.1.31
# configure 参数基本与 8.0 相同
./configure \
--prefix=/usr/local/php81 \
--enable-fpm \
--enable-opcache \
--enable-jit \
--with-jit \
--enable-mysqlnd \
--with-pdo-mysql \
--enable-mbstring \
--with-curl \
--with-zip \
--with-intl \
--with-openssl \
--enable-bcmath
make -j$(nproc)
sudo make install
# 切换默认 PHP(用 update-alternatives 或直接改 PATH)
# systemctl restart php81-fpm # 如果是 FPM 模式
五、8.0→8.1 与 7.4→8.0 的难度对比
| 对比维度 | 7.4 → 8.0 | 8.0 → 8.1 |
|---|---|---|
| Composer constraint 变动 | 几乎所有包都要调 | 少数包写死 ~8.0 的需要调 |
| 代码改动量(估算) | 5%~20%(动态属性、内建函数签名、字符串偏移等) | 0%~3%(几乎不需要改) |
| Rector 覆盖度 | 高(大部分破坏点可自动修复) | 极高(Deprecated 扫描为主,修复点很少) |
| 升级后可能的线上事故 | 函数调用签名、ReturnTypeError、动态属性 Error | 几乎没有 |
| 升级时间(中小项目) | 半天 ~ 2 天 | 10 分钟 ~ 2 小时 |
| 值不值 | 必须升(7.4 已 EOL) | 看需求(想用到枚举/readonly/Fiber 再升) |
六、要不要升 8.1?还是直接跳 8.2?
在 2026 年这个节点上,直接升 8.2 或 8.3 可能比停留在 8.1 更划算。原因:
- 8.1 自身也已经 EOL 两年了,再升的话下一次升级目标就是 8.3 或 8.4,8.1→8.2 的过渡反而多一层;
- Laravel 11、Symfony 7、ThinkPHP 8 等主流框架的最新版都明确要求 PHP 8.2+,停在 8.1 会错过框架更新;
- 8.2/8.3 相比 8.1 的增量(readonly class、first-class callables 正式化、property hooks 雏形),对工程化的提升比 8.0→8.1 那次更显著。
推荐路线:如果代码量不大、框架可升,直接从 8.0 一步跳到 8.2/8.3。如果有历史包袱要拆、或者想先"温和"过渡到新范式,那就 8.0→8.1→8.2 拆两步走——但第二步不要等太久。
参考链接
- PHP 8.1 Release Notes:https://www.php.net/releases/8.1/
- PHP 8.1 Migration Guide:https://www.php.net/manual/en/migration81.php
- PHP Enums RFC:https://wiki.php.net/rfc/enum
- PHP Fiber RFC:https://wiki.php.net/rfc/fibers