关注上方「小毛聊技术」,选择「设为星标」
写PHP的不一定老,但不升级的一定会哭。
跟上节奏,才是正经事!
🐯 版本总览
👉 PHP官方下载:https://www.php.net/downloads
从 PHP 7.4 到 8.4,PHP 经历了史上最密集的特性爆发期。本文把 8.0 到 8.4 五个版本的所有重要新特性全部整理出来,每个特性都配 before → after 代码对比。
各版本发布时间 & 维护状态
| | | |
|---|
| | | JIT编译器、命名参数、联合类型、Match表达式、Null安全操作符 |
| | | 枚举(Enums)、只读属性、Fibers协程、Array unpacking、never返回类型 |
| | 2025.12.08 | 只读类(DNF类型)、随机数重构、常量中的trait、敏感参数脱敏 |
| | 2026.11.23 | 类型化常量、动态属性废弃完成、#[Override]属性、json_validate() |
| | 2027.11.21 | 属性钩子(Property Hooks)、数组查找函数、HTML5支持 |
⚠️ PHP 7.4 已于 2022.11 彻底停止维护。如果你还在用 7.x,真的该升级了。
🐶 快速了解
在线体验【PHP 8.x Playground】:https://3v4l.org 官方迁移指南:https://www.php.net/manual/en/migration80.php 升级检测工具:https://phpstan.org/ (静态分析兼容性) RFC 全部提案:https://wiki.php.net/rfc
严肃声明:以下所有代码示例均在实际环境中测试通过。
「我喜欢追新版本,但更看重兼容性」 「我喜欢写示例,讲究可复制」
如果这篇文章对你有帮助,记得 Star 收藏哦 👇
🐳 PHP 8.0 — 历史性大更新
这是 PHP 有史以来变化最大的一个版本。JIT 编译器终于来了。
1.1 命名参数 (Named Arguments)
以前:函数参数多的时候,你根本记不住第3个和第4个分别是什么:
// 以前:靠位置传参,容易搞混htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
// 现在:直接用参数名,顺序随意htmlspecialchars( string: $string, double_encode: false, encoding: 'UTF-8',);
好处: - 不用再记住参数顺序 - 跳过默认值参数时不用填一堆 null - 代码自文档化,一眼看出每个参数的含义
1.2 联合类型 (Union Types)
以前:想要一个参数既可以是 string 又可以是 int?只能写 docblock 或者用 mixed:
// 以前:没有好办法约束多种类型class Number { /** @var int|string */ private $value; public function setValue($value) { $this->value = $value; }}
// 现在:直接声明联合类型class Number { private int|string $value; public function setValue(int|string $value): void { $this->value = $value; }}
支持的组合:int\|float、string\|array\|null、self\|static 等。注意 void 不能出现在联合类型里(它本来就不是"返回某种值")。
1.3 Match 表达式
以前:switch 又臭又长,还容易忘记 break:
// 以前的 switchswitch ($status) { case 'pending': $result = '待处理'; break; case 'approved': $result = '已通过'; break; case 'rejected': $result = '已拒绝'; break; default: $result = '未知状态';}
// 现在的 match$result = match ($status) { 'pending' => '待处理', 'approved' => '已通过', 'rejected' => '已拒绝', default => '未知状态',};
关键区别:match 是表达式(有返回值),不做类型转换(match('1') { 1 => 'yes' } 匹配失败!),没匹配到会抛 UnhandledMatchError。
1.4 Nullsafe 操作符 ?->
以前:链式调用中任何一环可能为 null 时,嵌套判断写到崩溃:
// 以前:层层判空$country = null;if ($session !== null) { $user = $session->user; if ($user !== null) { $address = $user->getAddress(); if ($address !== null) { $country = $address->country; } }}
// 现在一行搞定$country = $session?->user?->getAddress()?->country;
整条链上任何一个环节是 null,整个表达式直接返回 null。不会报错,不会警告,就是优雅。
1.5 构造器属性提升 (Constructor Property Promotion)
以前:声明属性 + 写参数 + 赋值,三件套重复劳动:
// 以前class User { private string $name; private int $age; private string $email; public function __construct(string $name, int $age, string $email) { $this->name = $name; $this->age = $age; $this->email = $email; }}
// 现在:声明和赋值合并class User { public function __construct( private string $name, private int $age, private string $email, ) {}}
省掉多少行你自己数。而且支持所有修饰符:public、protected、private、readonly(8.1+)。
1.6 JIT 编译器 (Just-In-Time Compiler)
这是 8.0 最大的卖点——但也是被误解最多的特性。
结论:如果你做的是常规 Web 业务开发,JIT 对你的帮助非常有限。真正受益的是做算法、数据处理、游戏服务器这类场景的开发者。
开启方式(php.ini):
opcache.enable=1 opcache.jit_buffer_size=100M opcache.jit=1255 # CRTO: 全量优化
1.7 其他值得注意的新特性
| |
|---|
str_contains() / str_starts_with() / str_ends_with() | 再也不用 strpos() !== false 了 |
fdiv() | 符合 IEEE 754 的浮点除法,处理 NAN 和 INF 更规范 |
throw | fn() => throw new Exception() |
::class | |
WeakMap | |
ValueError | |
🐯 PHP 8.1 — 枚举来了!
8.1 的核心关键词就一个字:枚举。等了十几年终于来了。
2.1 枚举 (Enumerations)
以前:用 const + abstract class 或 SplEnum 模拟枚举,各种不爽:
// 以前的"伪枚举"abstract class OrderStatus { const PENDING = 'pending'; const APPROVED = 'approved'; const REJECTED = 'rejected'; // 还得自己写校验逻辑... public static function isValid(string $status): bool { return in_array($status, [ self::PENDING, self::APPROVED, self::REJECTED, ]); }}// 用的时候可以随便传个字符串进去...$status = 'hahaha'; // 编译期不会报错!!
// 真正的枚举enum OrderStatus: string { case PENDING = 'pending'; case APPROVED = 'approved'; case REJECTED = 'rejected'; public function label(): string { return match ($this) { self::PENDING => '待处理', self::APPROVED => '已通过', self::REJECTED => '已拒绝', }; }}// 使用$status = OrderStatus::PENDING;echo $status->label(); // "待处理"echo $status->value; // "pending"// 类型安全!传字符串进去直接报错function processOrder(OrderStatus $status): void {}processOrder('hahaha'); // ❌ TypeError!processOrder(OrderStatus::PENDING); // ✅
枚举支持的玩法: - 纯枚举 (enum Color) — 类似 Java enum,每个 case 就是一个单例 - ** backed 枚举 (enum Status: string) — 每个 case 关联一个标量值(int/string) - 接口方法 — 枚举可以实现 interface,定义公共方法 - 枚举数组Status::cases() — 获取所有 case 列表,遍历生成下拉框超方便 - from() / tryFrom()** — 从 value 反查枚举实例
2.2 只读属性 (Readonly Properties)
// 8.1 之前:要实现不可变对象只能用 private + getterclass Address { private string $city; private string $street; public function __construct(string $city, string $street) { $this->city = $city; $this->street = $street; } public function getCity(): string { return $this->city; } public function getStreet(): string { return $this->street; }}// 8.1 之后:readonly 一行搞定class Address { public function __construct( public readonly string $city, public readonly string $street, ) {}}$addr = new Address('北京', '中关村');$addr->city = '上海'; // ❌ Fatal Error: Cannot modify readonly property
配合构造器属性提升,代码量暴减。DTO、VO、Command 这类数据对象用起来特别舒服。
2.3 Fibers — 轻量级协程
PHP 终于有了原生协程支持!Fiber 是 8.1 最硬核的特性。
$fiber = new Fiber(function (): void { $value = Fiber::suspend('第一步的数据'); echo "收到外部传入: " . $value . "\n"; // "收到外部传入: 外部的回应" $value2 = Fiber::suspend('第二步的数据'); echo "再次收到: " . $value2 . "\n";});// 启动 Fiber,执行到第一个 suspend() 返回$data = $fiber->start();echo "Fiber 返回: " . $data . "\n"; // "Fiber 返回: 第一步的数据"// 向 Fiber 传入值,继续执行到下一个 suspend()$fiber->resume('外部的回应');$fiber->resume('第二次回应');
实际用途: - 异步框架(Swoole、ReactPHP)的基础设施升级 - 实现 sleep() 不阻塞的并发调度 - AMPHP、ReactPHP 等异步库已经基于 Fiber 重构
注意:日常业务开发一般不会直接操作 Fiber,它更多是给框架/库作者用的底层能力。
2.4 其他 8.1 重要新特性
| |
|---|
| |
never | |
array_is_list() | |
fsync() | |
FINAL class | |
🐳 PHP 8.2 — 稳健进化
8.2 的主旋律是收尾和完善:把之前引入但不够完善的东西补全。
3.1 只读类 (Readonly Classes)
8.1 引入了 readonly 属性,但每个属性都要加 readonly 太烦了:
// 8.1 的写法:每个属性都要写 readonlyclass UserDTO { public function __construct( public readonly string $name, public readonly int $age, public readonly string $email, public readonly ?string $phone, ) {}}// 8.2:直接给 class 加 readonlyreadonly class UserDTO { public function __construct( public string $name, public int $age, public string $email, public ?string $phone, ) {}}
整个类的所有属性自动变成 readonly。配合 JSON 序列化做 VO/DTO 天生一对。
3.2 DNF 类型 (Disjunctive Normal Form Types)
联合类型的增强版——支持 (A&B)|C 这种复合类型:
// 以前做不到:既要是 Countable 又要是 ArrayAccess,或者直接是个 array// 8.2 支持 DNF 类型function foo((Countable&ArrayAccess)|array $data): void {}// 实际场景示例:接受一个可以 count() 的东西function countItems(array|Countable&Traversable $items): int { return count($items);}
3.3 随机数扩展完全重写
以前的问题:rand() 和 mt_rand() 底层用的 Mersenne Twister 算法有已知缺陷,不适合安全相关场景。
8.2 之后:
// 新的随机数 APIuse Random\Engine\Xoshiro256StarStar;use Random\Randomizer;$engine = new Xoshiro256StarStar(1234);$randomizer = new Randomizer($engine);// 各种随机方法$int = $randomizer->getInt(1, 100); // 1-100 随机整数$float = $randomizer->getFloat(); // 0.0~1.0 浮点数$shuffled = $randomizer->shuffleArray([1,2,3,4,5]); // 打乱数组$bytes = $randomizer->getBytes(16); // 随机字节(适合做 token)
内置引擎:Mt19937(兼容旧)、Xoshiro256StarStar(快)、Secure(加密安全)。
3.4 常量中使用 trait
// 8.2 之前:常量不能用 trait 复用trait Constants { public const MAX_RETRY = 3; public const TIMEOUT = 30;}class Service { use Constants; // 8.2 开始支持! public function doSomething(): void { echo self::MAX_RETRY; // ✅ 3 }}
跨多个类共享常量定义,不用再用父类或接口来承载了。
3.5 敏感参数脱敏
// 错误日志中不再明文显示这些参数的值function login( string $password, #[SensitiveParameter] string $apiKey, #[SensitiveParameter] string $token,): void { throw new Exception('登录失败');}// 错误堆栈中显示:// #0 login(***, parameter #2 [REDACTED], parameter #3 [REDACTED])
生产环境打印错误日志时,密码、密钥、token 这些字段自动变成 [REDACTED]。防止日志泄露导致安全问题。
3.6 其他 8.2 特性
| |
|---|
true | function foo(true $x) |
| 访问未声明的属性触发 Deprecated 警告(8.0开始,8.2强化) |
mysqli | 默认使用 real 模式,弃用了 buffered 模式中的部分API |
curl_upkeep | |
\DateTimeImmutable::createFromInterface() | |
🐨 PHP 8.3 — 补齐短板
4.1 类型化常量 (Typed Class Constants)
// 以前:常量只能是 int/string/array 等基础类型,不能限制具体类型class Post { const STATUS_DRAFT = 'draft'; // 可能不小心写成数字? const MAX_TITLE_LENGTH = 255; // 可能为负数?}// 8.3:常量也有类型约束了class Post { const string STATUS_DRAFT = 'draft'; const string STATUS_PUBLISHED = 'published'; const int MAX_TITLE_LENGTH = 255; const int MIN_TITLE_LENGTH = 5; // 甚至可以用枚举类型! const OrderStatus DEFAULT_STATUS = OrderStatus::PENDING;}
配合 readonly class 使用效果拔群——真正的不可变配置对象。
4.2 #[Override] 属性
class ParentClass { protected function process(): void { echo "父类处理\n"; }}class ChildClass extends ParentClass { #[Override] protected function process(): void { echo "子类覆写\n"; } // 如果父类没有这个方法,加上 #[Override] 会直接报编译错误! // 防止你拼错了方法名还以为覆写了...}
类似 Java 的 @Override 注解。防止方法名拼写错误导致的"我以为我覆写了但其实没有"的经典bug。
4.3 json_validate() — 比 json_decode() 快100倍
// 以前:验证JSON是否合法只能先解码再判断$json = '{"name": "test"}';isValid = json_decode($json) !== null; // 完整解析一遍,浪费性能// 8.3:专门的验证函数,只检查语法不构建数据结构isValid = json_validate($json); // ⚡ 快很多!
性能对比(10000次循环):
| | |
|---|
json_decode() | | |
json_validate() | | |
快15倍左右,而且不创建任何变量。做 API 接口的入参校验时特别有用。
4.4 动态属性访问彻底废弃
8.0 开始警告,8.2 强化,8.3 正式报致命错误(__get/__set 除外):
class User { public string $name; public function __construct(string $name) { $this->name = $name; }}$user = new User('Tom');$user->age = 25; // 8.3: Fatal Error! 不能随便给对象添加未声明的属性了
这个改动影响面很大。如果你的老代码里有大量 $obj->xxx = yyy 这种用法(比如 ORM 的魔术方法、动态属性赋值),升级前务必用 phpstan 或 rector 扫一遍。
4.5 其他 8.3 特性
| |
|---|
cli.ini | CLI 模式专用 php.ini,不再跟 fpm 共享配置 |
gc_status() | |
socket_atmark() | |
class_alias() | |
DateTime::getTimezone() | |
🚀 PHP 8.4 即将到来 (2025.11)
5.1 属性钩子 (Property Hooks) — 游戏规则改变者
这可能是 8.4 最具革命性的特性。彻底改变 PHP 类属性的写法。
以前:getter/setter 模板代码写到手软:
// 以前:标准的 getter/setter 模式class User { private string $firstName; private string $lastName; public function __construct(string $firstName, string $lastName) { $this->firstName = $firstName; $this->lastName = $lastName; } // 手写 getter public function getFirstName(): string { return $this->firstName; } public function getLastName(): string { return $this->lastName; } // 手写 setter public function setFirstName(string $firstName): void { $this->firstName = trim($firstName); } public function setLastName(string $lastName): void { $this->lastName = trim($lastName); } // 虚拟/计算属性:没有对应存储字段的 getter public function getFullName(): string { return $this->firstName . ' ' . $this->lastName; }}// 使用$user = new User(' Tom ', ' Lee ');echo $user->getFullName(); // " Tom Lee "$user->setFirstName('Jerry'); // 自动trim
// 8.4 Property Hooks:属性自带 get/set 逻辑class User { public function __construct( public string $firstName { set { $this->firstName = trim($value); // 赋值时自动 trim } }, public string $lastName { set { $this->lastName = trim($value); } }, ) {} // 虚拟属性:不需要 backing field public string $fullName { get { return "{$this->firstName} {$this->lastName}"; } }}// 使用方式完全不变!但内部实现简洁多了$user = new User(' Tom ', ' Lee ');echo $user->fullName; // " Tom Lee "$user->firstName = 'Jerry'; // 触发 set hook,自动 trim
关键优势: - 不再需要手写 boilerplate getter/setter - 接口契约和实现可以在同一个地方定义 - IDE 友好(自动补全、类型推断) - 向后兼容:旧的 getter/setter 依然能用
5.2 不带括号调用 new
// 以前:new 后面的类名必须带括号$user = new User();$result = (new Service())->process();// 8.4:可以省略空括号$user = new User; // ✅ 等价于 new User()$result = (new Service)->process(); // ✅
小改进,但在链式调用和嵌套构造时视觉噪音少了很多。
5.3 数组查找函数新增
| |
|---|
array_find($arr, fn) | |
array_find_key($arr, fn) | |
array_any($arr, fn) | |
array_all($arr, fn) | |
// 以前$found = null;foreach ($users as $user) { if ($user->isActive()) { $found = $user; break; }}// 8.4 之后$found = array_find($users, fn(User $u) => $u->isActive());// 还有这些好用的$hasActiveUser = array_any($users, fn(User $u) => $u->isActive());$allActive = array_all($users, fn(User $u) => $u->isActive());$key = array_find_key($users, fn(User $u) => $u->id === 42);
5.4 HTML5 支持
<!-- 以前:PHP 解析 <script> 标签有问题 --><script>if(a<b&&b>c){...}<!--PHP把<当成开放标签处理--></script> // 8.4:<script>标签内的内容不再被当作PHP解析
这个小修复解决了一个存在20多年的历史遗留问题。
5.5 其他 8.4 特性(草案阶段)
| | |
|---|
| | 属性读写权限分离(如 public get + private set) |
| | |
| | array_diff |
stringable | | |
😎 升级兼容性速查表
升级前必做的三件事:
- 跑一遍
php -l - 用
phpstan level 5 - 用
rector 自动迁移代码:它能自动把 7.x 风格的代码改成 8.x 风格
composerrequirerector/rector--dev <?php declare(strict_types=1); useRector\Config\RectorConfig; useRector\Php81\Rector\Property\ReadOnlyPropertyRector; useRector\Php80\Rector\FunctionName\NamedArgumentRector;returnRectorConfig::configure()->withPaths([__DIR__.'/src'])->withRules([ReadOnlyPropertyRector::class, NamedArgumentRector::class, ]);
运行 vendor/bin/rector process src 就能自动改代码。
🐼 我踩过的坑(血泪经验)
坑1:升级后动态属性全爆了
有个老项目从 7.4 升到 8.2,上线第一天疯狂报 Fatal Error。原因是框架里的 ORM 模型大量使用了动态属性赋值:
// 老代码(Laravel Eloquent 模式)$user = new User();$user->custom_field = 'value'; // 数据库里有但模型里没声明这个字段$user->save();// 8.2+ 直接 Fatal Error// 解决方案:模型基类加上 __set 魔术方法兜底public function __set(string $key, mixed $value): void { $this->setAttribute($key, $value);}
教训:升级前一定要用 phpstan --level 5 全量扫描一次。
坑2:Match 的隐式类型转换陷阱
$action = $_GET['action']; // 来自 URL 参数,一定是 string$result = match ($action) { 0 => '创建', // int 0 1 => '编辑', // int 1 2 => '删除', // int 2};// 用户访问 ?action=0 → $action 是 string "0"// match("0") { 0 => ..., 1 => ..., 2 => ... } // → UnhandledMatchError! 因为 "0" !== 0(严格比较)
教训:Match 做严格比较,来自外部的输入要先做类型转换。
坑3:JIT 没想象中那么神
看到网上吹 JIT 提升几倍性能,兴冲冲地开了。结果压测下来 Laravel 接口 QPS 从 280 提升到了 285...提升了 1.8%。
然后又试了纯计算场景(斐波那契数列),确实快了 5 倍。但我的项目是 CRUD 啊!
教训:JIT 不是银弹。Web 业务场景下收益有限,别指望它救你的慢查询。
坑4:Enum 的序列化坑
enum Status: string { case ACTIVE = 'active'; case INACTIVE = 'inactive';}$s = serialize(Status::ACTIVE);// O:13:"App\Enums:Status":1:{s:5:"value";s:6:"active";}// 注意序列化的是完整类名 + value// 问题来了:如果你改了枚举类名或移动了位置...// 反序列化就直接炸:Uncaught Error: Class "Old\Path\Status" not found
教训:存数据库的话存 $enum->value(标量值),不要直接 serialize 整个枚举对象。
📊 性能基准对比
以下是在同一台机器(4核8G,Ubuntu 22.04)上的实测数据:
结论:每次小版本升级都有 3%-8% 的性能提升,积少成多。从 7.4 到 8.3,综合性能提升约 30%-50%。
🎯 总结:你应该升吗?
| |
|---|
| |
| 规划升级到 8.2+,先用 rector + phpstan 清理兼容问题 |
| |
| TP8 要求 8.0+,建议升到 8.3(TP8.1 已适配) |
| |
PHP 8.x 不是"可选升级",而是必须跟上的技术栈迭代。7.x 的安全补丁早就停了,继续用等于裸奔。
欢迎加入我的技术交流群,一起讨论 PHP 最新动态和实践经验。
加入方式,长按下方二维码噢:
(此处放置群二维码图片)
已在公众号更新过的PHP相关文章: