PHP 新特性

PHP 8.x 带来了大量现代化特性,让代码更简洁、更安全、性能更强。从 PHP 8.0 的 JIT 编译器到 8.1 的枚举和 Fibers,再到 8.2 的 readonly 类,每个版本都在持续改进语言的表达能力和类型安全性。了解这些新特性,可以让你写出更现代、更优雅的 PHP 代码。

命名参数(PHP 8.0)

命名参数允许在调用函数时通过参数名而不是位置来传递参数,让代码意图更清晰,也可以跳过有默认值的中间参数。

PHP 实例
// 传统方式:必须按顺序
array_slice($array, 1, 3, true);

// 命名参数:顺序无关,语义更清晰 array_slice(array: $array, offset: 1, length: 3, preserve_keys: true);

function createUser(string $name, int $age = 18, string $role = 'user'): string { return "$name / $age / $role"; }

echo createUser(name: '张三', role: 'admin'); // 跳过 age ``

命名参数在调用内置函数时特别有用,例如 array_slice() 的第四个参数 preserve_keys 在传统写法中很难一眼看出含义,命名参数让代码自文档化。命名参数也可以与位置参数混用,但位置参数必须在命名参数之前。

联合类型(PHP 8.0)

联合类型允许一个参数或返回值接受多种类型,用 | 分隔,比之前用注释声明类型更加严格和可靠。

`php function formatId(int|string $id): string { return (string)$id; }

function divide(int $a, int $b): int|false { if ($b === 0) return false; return intdiv($a, $b); } `

PHP 8.0 还引入了 mixed 类型(表示任意类型)和 never 返回类型(表示函数永远不会正常返回,如总是抛出异常或调用 exit())。联合类型配合严格类型声明(declare(strict_types=1))可以在编译时捕获更多类型错误。

match 表达式(PHP 8.0)

matchswitch 的现代替代品,使用严格比较(===),每个分支是表达式而不是语句,不需要 break,也不会有 fall-through 问题。

`php // 比 switch 更严格(全等比较)、更简洁 $status = 2;

$label = match($status) { 1 => '待支付', 2, 3 => '处理中', // 多条件 4 => '已完成', default => '未知', };

echo $label; // 处理中 `

match 没有匹配到任何分支且没有 default 时会抛出 UnhandledMatchError,而 switch 在没有匹配时会静默跳过。这种"快速失败"的行为有助于发现代码中的遗漏情况。match 也可以用于复杂条件:match(true) { $x > 100 => 'large', $x > 50 => 'medium', default => 'small' }

Nullsafe 运算符(PHP 8.0)

Nullsafe 运算符 ?-> 让链式调用更加安全,当链中任何一个环节返回 null 时,整个表达式短路返回 null,而不是抛出"Call to a member function on null"错误。

`php // 传统写法:层层判断 $city = null; if ($user !== null) { if ($user->getAddress() !== null) { $city = $user->getAddress()->getCity(); } }

// Nullsafe:链式调用,任一环节为 null 则整体返回 null $city = $user?->getAddress()?->getCity(); `

Nullsafe 运算符极大地简化了处理可能为 null 的对象链的代码,在 ORM 查询结果处理、API 响应解析等场景中非常实用。注意 ?-> 只能用于方法调用和属性访问,不能用于静态方法(?:: 不存在)。

构造器属性提升(PHP 8.0)

构造器属性提升(Constructor Property Promotion)允许在构造函数参数中直接声明和初始化属性,大幅减少样板代码。

`php // 传统写法 class User { public string $name; public int $age; public function __construct(string $name, int $age) { $this->name = $name; $this->age = $age; } }

// 属性提升:一行搞定声明+赋值 class User { public function __construct( public readonly string $name, public int $age = 18, private string $password = '' ) {} } `

属性提升支持所有访问修饰符(publicprotectedprivate)和 readonly,也支持默认值。提升的属性可以与普通参数混用,但提升的属性必须有访问修饰符。这个特性在数据传输对象(DTO)和值对象(Value Object)的定义中特别有用。

readonly 属性(PHP 8.1)

readonly 属性只能被赋值一次(通常在构造函数中),之后任何修改都会抛出错误。这对于不可变值对象非常有用,可以在类型系统层面保证数据不被意外修改。

`php class Config { public readonly string $dsn;

public function __construct(string $dsn) { $this->dsn = $dsn; // 只能赋值一次 } }

$config = new Config('mysql:host=localhost'); echo $config->dsn; // $config->dsn = 'other'; // 报错! `

PHP 8.2 进一步引入了 readonly class,让整个类的所有属性都变为 readonly,无需逐个标注。readonly 属性不能有默认值(除非通过构造器属性提升),也不能在子类中被覆盖。

枚举 Enum(PHP 8.1)

枚举是 PHP 8.1 最受期待的特性之一,提供了类型安全的常量集合,替代了之前用类常量或 define() 模拟枚举的做法。

`php // 纯枚举 enum Status { case Active; case Inactive; case Deleted; }

// 带值的枚举 enum Color: string { case Red = 'red'; case Green = 'green'; case Blue = 'blue'; }

$status = Status::Active; $color = Color::Red; echo $color->value; // red

// 在函数中使用 function setStatus(Status $status): void { echo $status->name; // Active } `

枚举可以实现接口,可以有方法,带值的枚举还可以用 from()tryFrom() 从值创建枚举实例(tryFrom() 在值不存在时返回 null 而不是抛出异常)。枚举在数据库存储时通常存储其 value,读取时用 from() 还原。

交集类型(PHP 8.1)

交集类型用 & 表示,要求值必须同时满足多个类型约束,通常用于要求实现多个接口的场景。

`php interface Stringable {} interface Countable {}

// 必须同时实现两个接口 function process(Stringable&Countable $value): void { echo count($value); } `

PHP 8.2 引入了 DNF(析取范式)类型,可以将联合类型和交集类型组合使用:(Stringable&Countable)|null,进一步增强了类型系统的表达能力。

Fibers(PHP 8.1)

Fibers 是 PHP 8.1 引入的轻量级协程机制,允许在函数执行过程中暂停和恢复,是实现异步编程的基础。

`php // 轻量级协程,用于异步编程 $fiber = new Fiber(function(): void { $value = Fiber::suspend('第一次暂停'); echo '恢复,收到:' . $value; });

$result = $fiber->start(); echo $result; // 第一次暂停 $fiber->resume('hello'); // 恢复,收到:hello `

Fibers 本身是底层机制,普通开发者通常不直接使用,而是通过基于 Fibers 构建的异步框架(如 ReactPHP、Amp)来使用。Fibers 与 JavaScript 的 Generator 类似,但更强大,可以在任意调用深度暂停,而不仅限于直接调用 yield 的函数。

PHP 8.2 新特性

PHP 8.2 在 8.1 的基础上继续完善类型系统和语言特性。

`php // DNF 类型(析取范式) function process((Stringable&Countable)|null $value): void { // ... }

// readonly 类 readonly class Point { public function __construct( public float $x, public float $y, ) {} } `

PHP 8.2 还废弃了动态属性(在类定义外直接给对象赋值属性),这是一个重要的破坏性变更,需要在升级前检查代码。同时引入了 truefalsenull 作为独立类型,可以用于函数返回类型声明。

箭头函数与数组解包增强

PHP 8.1 增强了数组操作能力,支持字符串键的数组解包,以及将函数作为一等公民传递的语法。

`php // 数组解包支持字符串键(PHP 8.1) $arr1 = ['a' => 1, 'b' => 2]; $arr2 = ['b' => 3, 'c' => 4]; $merged = [...$arr1, ...$arr2]; // ['a' => 1, 'b' => 3, 'c' => 4]

// First-class callable(PHP 8.1) $fn = strlen(...); echo $fn('hello'); // 5

$arr = ['banana', 'apple', 'cherry']; usort($arr, strcmp(...));

First-class callable 语法(strlen(...))让将内置函数和方法作为回调传递变得更加简洁,不再需要写 ['strlen']Closure::fromCallable('strlen')

常见问题

Q1:PHP 8 的 JIT 编译器能显著提升性能吗?

A:JIT(Just-In-Time)编译器在 PHP 8.0 中引入,对计算密集型任务(如数学运算、图像处理)有显著提升(2-3 倍),但对典型的 Web 应用(主要瓶颈在 I/O 和数据库)提升有限(约 10-20%)。JIT 默认未开启,需要在 php.ini 中配置 opcache.enable=1opcache.jit_buffer_size=100M。对于 CPU 密集型的 CLI 脚本,JIT 的收益更为明显。

Q2:match 表达式和 switch 语句有哪些关键区别?

A:主要区别:① match 使用严格比较(===),switch 使用松散比较(==);② match 的每个分支是表达式,有返回值,switch 的分支是语句;③ match 没有 fall-through,不需要 break;④ match 没有匹配时抛出 UnhandledMatchErrorswitch 静默跳过;⑤ match 是表达式,可以直接赋值给变量。

Q3:枚举和类常量有什么区别,什么时候用枚举?

A:类常量只是普通的值,没有类型约束,可以是任意类型;枚举是独立的类型,函数参数声明为枚举类型后,只能传入该枚举的实例,类型安全性更强。枚举还可以有方法、实现接口,带值枚举可以方便地与数据库值互转。当你有一组固定的、有限的选项(如状态、颜色、方向)时,应优先使用枚举而不是类常量或字符串。

Q4:如何平滑地将旧项目升级到 PHP 8?

A:升级步骤:① 用 php-compatibility 工具扫描代码,找出不兼容的写法;② 重点关注:废弃的函数(如 each())、类型强制转换行为变化、matchswitch 的差异;③ 更新 Composer 依赖,确保所有包支持 PHP 8;④ 在测试环境运行完整的测试套件;⑤ 逐步迁移,先升级到 PHP 7.4,再升级到 8.0,最后升级到 8.1/8.2。PHP 8 的大多数破坏性变更都有充分的废弃警告期,提前关注 E_DEPRECATED` 警告可以减少升级阻力。