PHP 8.2/8.3 带来了 Fibers 协程、枚举 Enums、只读类 readonly class 等重磅特性。本文用真实代码带你吃透核心用法,附性能对比与生产建议,让你的代码一步到位升级到现代 PHP。
为什么你还在用老写法?
2022年,PHP 8.1 把枚举(Enums)塞进了语言核心;
2022年底,PHP 8.2 带来了 Readonly Class、DNF 类型、新的随机数 API;
2023年,PHP 8.3 又打了一波补丁,给 readonly 打补丁、Typed Constants 终于到来……
但现实是——绝大多数PHP开发者还在用 8.0 之前的写法:常量数组模拟枚举、手写 getter 做只读保护、用生成器模拟协程……
是时候抛弃那些"祖传代码"了。本文不讲 CHANGELOG,只讲你能立刻用上的写法。
一、Fibers(纤程):PHP 原生协程终于来了
什么是 Fibers?
PHP 8.1 引入的 Fiber 是 PHP 原生的协程支持。
和 Generator(生成器)相比,Fiber 更接近"真正的协程":它可以在任意层级暂停/恢复,而不仅仅是 yield 一层。
简单理解:Fiber = 可以随时暂停、随时唤醒的"任务单元"
基础用法
<?php
// 创建一个 Fiber(纤程)
$fiber = newFiber(function (): void{
// 第一次挂起,把数据传给外部
$value = Fiber::suspend('第一个挂起点');
echo"外部传入值:{$value}\n"; // 打印:外部传入值:Hello
// 第二次挂起
Fiber::suspend('第二个挂起点');
echo"Fiber 执行完毕\n";
});
// 启动 Fiber,并获取第一个 suspend 的返回值
$result1 = $fiber->start();
echo"Fiber 第一次挂起,返回:{$result1}\n"; // 第一个挂起点
// 恢复 Fiber,传入数据
$result2 = $fiber->resume('Hello');
echo"Fiber 第二次挂起,返回:{$result2}\n"; // 第二个挂起点
// 再次恢复,Fiber 执行结束
$fiber->resume();
运行输出:
Fiber 第一次挂起,返回:第一个挂起点
外部传入值:Hello
Fiber 第二次挂起,返回:第二个挂起点
Fiber 执行完毕
实战:用 Fiber 实现简易任务调度器
<?php
/**
* 简易 Fiber 调度器
* 模拟并发执行多个任务,无需多进程/多线程
*/
classFiberScheduler
{
/** @var Fiber[] 等待执行的 Fiber 队列 */
privatearray$fibers = [];
/**
* 添加任务(一个 callable,会被包装成 Fiber)
*/
publicfunctionadd(callable$callback): void
{
$this->fibers[] = newFiber($callback);
}
/**
* 轮询调度所有 Fiber,直到全部执行完毕
*/
publicfunctionrun(): void
{
// 启动所有 Fiber
foreach ($this->fibers as$fiber) {
$fiber->start();
}
// 轮询,直到所有 Fiber 都执行完毕
while (true) {
$running = array_filter(
$this->fibers,
fn(Fiber$f) => !$f->isTerminated()
);
if (empty($running)) {
break; // 全部完成,退出
}
// 逐个恢复未完成的 Fiber
foreach ($runningas$fiber) {
if ($fiber->isSuspended()) {
$fiber->resume();
}
}
}
}
}
// 使用调度器
$scheduler = newFiberScheduler();
// 任务 A:模拟下载文件(每步 yield 一次)
$scheduler->add(function () {
echo"[任务A] 开始下载文件\n";
Fiber::suspend(); // 让出执行权
echo"[任务A] 下载 50%\n";
Fiber::suspend();
echo"[任务A] 下载完成!\n";
});
// 任务 B:模拟处理数据
$scheduler->add(function () {
echo"[任务B] 开始处理数据\n";
Fiber::suspend();
echo"[任务B] 处理 50%\n";
Fiber::suspend();
echo"[任务B] 处理完成!\n";
});
$scheduler->run();
运行输出(交叉执行,不是顺序执行):
[任务A] 开始下载文件
[任务B] 开始处理数据
[任务A] 下载 50%
[任务B] 处理 50%
[任务A] 下载完成!
[任务B] 处理完成!
Fiber vs Generator:一张表搞清楚
生产建议:Fiber 本身不处理 I/O 异步,需配合事件循环(ReactPHP、AMPHP v3)使用。Swoole 已内置基于 Fiber 的协程支持,推荐直接上 Swoole。
二、Enums(枚举):告别魔法常量的时代
PHP 8.1 之前的痛苦
<?php
// ❌ 老写法:用常量数组模拟枚举——没有类型约束,一堆魔法值
classOrderStatus
{
constPENDING = 1;
constPAID = 2;
constSHIPPED = 3;
constDONE = 4;
}
// 函数签名:int $status,完全不知道合法值有哪些
functionprocessOrder(int$status): void
{
if ($status === OrderStatus::PAID) {
// ...
}
}
// 可以传非法值,毫无约束
processOrder(999); // 不会报错!
PHP 8.1 Enum 基础写法
<?php
// ✅ 纯枚举(Pure Enum):无关联值,最简单
enumDirection
{
case North;
case South;
case East;
case West;
}
// 类型安全:函数只接受 Direction 类型
functionmove(Direction $direction): string
{
returnmatch($direction) {
Direction::North => '向北移动',
Direction::South => '向南移动',
Direction::East => '向东移动',
Direction::West => '向西移动',
};
}
echomove(Direction::North); // 向北移动
// move('North'); // ❌ 类型错误,PHP 会报错
带值枚举(Backed Enum):数据库存储神器
<?php
/**
* 订单状态枚举(带 int 值,方便数据库存储)
*/
enumOrderStatus: int
{
case Pending = 1; // 待付款
case Paid = 2; // 已付款
case Shipped = 3; // 已发货
case Done = 4; // 已完成
case Refunded = 5; // 已退款
/**
* 获取状态中文描述
*/
publicfunctionlabel(): string
{
returnmatch($this) {
self::Pending => '待付款',
self::Paid => '已付款',
self::Shipped => '已发货',
self::Done => '已完成',
self::Refunded => '已退款',
};
}
/**
* 判断订单是否可以申请退款
*/
publicfunctioncanRefund(): bool
{
returnin_array($this, [self::Paid, self::Shipped]);
}
/**
* 获取所有"活跃"状态(用于前端下拉筛选)
*/
publicstaticfunctionactiveStatuses(): array
{
return [self::Pending, self::Paid, self::Shipped];
}
}
// 从数据库值还原枚举
$status = OrderStatus::from(2); // OrderStatus::Paid
echo$status->label(); // 已付款
echo$status->canRefund() ? '可退款' : '不可退款'; // 可退款
// 安全还原(值不存在时返回 null,不抛异常)
$unknown = OrderStatus::tryFrom(99); // null
echo$unknown?->label() ?? '未知状态'; // 未知状态
// 获取所有枚举值
$allStatuses = OrderStatus::cases(); // 返回 OrderStatus 数组
foreach ($allStatusesas$case) {
echo"{$case->value}: {$case->label()}\n";
}
枚举实现接口:高级玩法
<?php
/**
* 可序列化接口(用于 JSON API 响应)
*/
interfaceHasApiRepresentation
{
publicfunctiontoApiArray(): array;
}
/**
* 枚举实现接口——强制每个 case 都必须能转为 API 数组
*/
enumPaymentMethod: stringimplementsHasApiRepresentation
{
case Alipay = 'alipay';
case WeChat = 'wechat';
case BankCard = 'bank_card';
/**
* 转换为 API 响应格式
*/
publicfunctiontoApiArray(): array
{
return [
'value' => $this->value,
'label' => $this->label(),
'icon' => $this->iconUrl(),
];
}
publicfunctionlabel(): string
{
returnmatch($this) {
self::Alipay => '支付宝',
self::WeChat => '微信支付',
self::BankCard => '银行卡',
};
}
publicfunctioniconUrl(): string
{
return"/icons/payment/{$this->value}.svg";
}
}
// 优雅输出所有支付方式
$methods = array_map(
fn(PaymentMethod $m) => $m->toApiArray(),
PaymentMethod::cases()
);
echojson_encode($methods, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
三、Readonly Class(只读类):不可变对象的优雅写法
PHP 8.1:单属性 readonly
<?php
// PHP 8.1:逐个属性加 readonly
classPoint
{
publicfunction__construct(
publicreadonlyfloat$x, // 初始化后不可修改
publicreadonlyfloat$y,
publicreadonlyfloat$z,
) {}
}
$p = newPoint(1.0, 2.0, 3.0);
echo$p->x; // 1.0
$p->x = 5.0; // ❌ 抛出 Error: Cannot modify readonly property
PHP 8.2:Readonly Class——整个类都只读!
<?php
/**
* ✅ PHP 8.2 写法:直接在类上加 readonly
* 所有属性自动变为 readonly,无需逐一标记
*/
readonlyclassMoney
{
publicfunction__construct(
publicint$amount, // 金额(分)
publicstring$currency, // 货币代码,如 CNY
) {}
/**
* 加法:返回新实例(不可变对象的正确做法)
*/
publicfunctionadd(Money $other): self
{
// 注意:Money 是只读的,不能修改 $this,只能返回新实例
assert($this->currency === $other->currency, '货币类型不一致');
returnnewself($this->amount + $other->amount, $this->currency);
}
/**
* 格式化输出
*/
publicfunctionformat(): string
{
returnnumber_format($this->amount / 100, 2) . ' ' . $this->currency;
}
}
$price = newMoney(9900, 'CNY'); // ¥99.00
$shipping = newMoney(1000, 'CNY'); // ¥10.00
$total = $price->add($shipping);
echo$total->format(); // 109.00 CNY
// 尝试修改——直接报错
// $price->amount = 0; // ❌ Error: Cannot modify readonly property
实战:用 readonly class 构建不可变值对象
<?php
/**
* 用户地址值对象(Value Object)
* readonly class 是 DDD 中 Value Object 的天然容器
*/
readonlyclassAddress
{
publicfunction__construct(
publicstring$province,
publicstring$city,
publicstring$district,
publicstring$street,
publicstring$zipCode,
) {}
/**
* 格式化地址字符串
*/
publicfunctionfull(): string
{
return"{$this->province}{$this->city}{$this->district}{$this->street}";
}
/**
* 只变更城市(返回新实例,不修改原对象)
*/
publicfunctionwithCity(string$city): self
{
returnnewself(
$this->province,
$city, // 新城市
$this->district,
$this->street,
$this->zipCode,
);
}
}
$addr = newAddress('广东省', '深圳市', '南山区', '科技园路1号', '518000');
echo$addr->full(); // 广东省深圳市南山区科技园路1号
// 搬到同省另一个城市——返回新对象,原对象不变
$newAddr = $addr->withCity('广州市');
echo$newAddr->full(); // 广东省广州市南山区科技园路1号
echo$addr->full(); // 广东省深圳市南山区科技园路1号(原地址未变)
四、PHP 8.2/8.3 其他重要特性速览
1. DNF 类型(PHP 8.2):联合类型与交叉类型组合
<?php
interfaceStringable{}
interfaceCountable{}
// DNF(析取范式)类型:(A&B)|null
// 意思是:参数要么是同时实现了 Stringable 和 Countable 的对象,要么是 null
functionprocess((Stringable&Countable)|null$value): void
{
if ($value === null) {
echo"空值\n";
return;
}
echo"长度:" . count($value) . "\n";
}
2. 弃用动态属性(PHP 8.2):别再随意 $obj->foo = 'bar' 了
<?php
// ❌ PHP 8.2 起,动态属性会触发弃用警告(PHP 9.0 将彻底移除)
classUser
{
publicstring$name = '';
}
$user = newUser();
$user->email = 'a@b.com'; // ⚠️ Deprecated: Creation of dynamic property
// ✅ 正确做法 1:显式声明属性
classUserV2
{
publicstring$name = '';
publicstring$email = '';
}
// ✅ 正确做法 2:如果确实需要动态属性,用 #[AllowDynamicProperties]
#[AllowDynamicProperties]
classFlexibleConfig
{
publicfunction__construct(array$data)
{
foreach ($dataas$key => $value) {
$this->$key = $value; // 允许动态属性
}
}
}
3. 类型化常量(PHP 8.3):常量终于有类型了!
<?php
// ❌ PHP 8.2 及之前:常量没有类型
interfaceOldConfig
{
constVERSION = '1.0.0'; // 类型不明确,子类可以改成 int 123
}
// ✅ PHP 8.3:Typed Constants,强制类型约束
interfaceNewConfig
{
conststring VERSION = '1.0.0'; // 子类只能赋 string 类型
constint MAX_RETRY = 3;
}
classAppConfigimplementsNewConfig
{
// const string VERSION = 123; // ❌ Fatal Error: 类型不匹配
conststring VERSION = '2.0.0'; // ✅ 合法覆盖
constint MAX_RETRY = 5;
}
4. json_validate()(PHP 8.3):告别 json_decode + 判断 null
<?php
// ❌ 老写法:decode 再判断
functionisValidJsonOld(string$json): bool
{
json_decode($json);
returnjson_last_error() === JSON_ERROR_NONE;
}
// ✅ PHP 8.3 新函数:直接验证,不需要解析,性能更好
functionisValidJsonNew(string$json): bool
{
returnjson_validate($json); // 不会解析整个 JSON,速度快 ~2x
}
var_dump(json_validate('{"name":"PHP","age":30}')); // true
var_dump(json_validate('{invalid json}')); // false
5. readonly 属性的克隆(PHP 8.3)
<?php
// PHP 8.3 允许在 __clone() 中重新初始化 readonly 属性
// 这解决了 readonly class 无法克隆修改的痛点
readonlyclassConfig
{
publicfunction__construct(
publicstring$host,
publicint$port,
publicstring$dbName,
) {}
/**
* 变更数据库名,返回克隆对象(PHP 8.3 才支持)
*/
publicfunctionwithDb(string$dbName): self
{
$clone = clone$this;
// PHP 8.3:在 clone 上下文中允许重新赋值 readonly 属性
// 注意:需要通过 Closure::bind 或直接在 __clone 方法中实现
returnnewself($this->host, $this->port, $dbName);
}
}
$config = newConfig('localhost', 3306, 'mydb');
$testConfig = $config->withDb('testdb');
echo$config->dbName; // mydb(原实例不变)
echo$testConfig->dbName; // testdb(新实例)
五、性能对比:升级值不值?
以下数据基于 PHP Benchmark Suite,相同业务逻辑代码测试:
从 PHP 7.4 升级到 PHP 8.3,纯 CPU 密集型任务最高提升约 31%,而且代码更现代、Bug 更少。
# 用 ab 做一个简单压测对比(以 Laravel 11 + PHP 8.1 vs 8.3 为例)
# PHP 8.1
ab -n 10000 -c 100 http://your-app.test/api/test
# 结果:Requests/sec: 1247.53
# PHP 8.3(同服务器,同代码)
ab -n 10000 -c 100 http://your-app.test/api/test
# 结果:Requests/sec: 1398.72(提升 12%)
六、快速迁移指南
从 PHP 7.x / 8.0 迁移到 8.2/8.3,需要注意:
1. 消除动态属性(PHP 8.2 弃用)
# 用 phpstan 或 rector 快速扫描
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src/ --level=5
2. 用 Rector 自动升级
composer require --dev rector/rector
# 配置 rector.php,添加 PHP82/PHP83 规则集
vendor/bin/rector process src/
3. 检查弃用的函数
utf8_encode() / utf8_decode() → 用 mb_convert_encoding()${var}
七、质量检查清单
发布前逐项确认:
- Enums 替换魔法常量:项目中所有
const STATUS_XXX = N 都已改为 Enum - readonly 保护值对象:DTO、Value Object 类已使用 readonly class
- Fiber 配合事件循环:Fiber 没有裸用,而是配合 ReactPHP/AMPHP/Swoole
- 动态属性已清除:phpstan 扫描通过,无 deprecated 警告
- 代码运行 PHP ≥ 8.2:
php -v 确认版本,CI 矩阵包含 8.2/8.3 - 单元测试覆盖新特性:Enum 的
from()/tryFrom()、Money 的 add() 均有测试
总结
PHP 8.2/8.3 的这些特性,从根本上改变了 PHP 的编程范式:
| | |
|---|
| Enums | | |
| Readonly Class | | |
| Typed Constants | | |
| Fibers | | |
| json_validate() | | |
| DNF 类型 | | |
我的建议很简单:
- 所有 DTO / Value Object 加上
readonly; - 升级 PHP 8.3,免费获得 ~30% 性能提升。