4.1 PHP 8 新特性:站台设备的更新换代 本节摘要:PHP 8 系列的更新可以概括为"更少的样板、更严的边界、更高的下限"。本节挑对服务端日常影响最大的六项:构造器属性提升、match 表达式、命名参数、空安全运算符、枚举与只读属性,每项给"旧写法对照新写法",并交代它们在本册前文出现过的位置——你会发现第二章的代码已经悄悄用上了大半。文末附版本时间线与"该不该立刻用"的判断法。 换代换的是什么 一个常被误解的点:语言新版本的价值不在"能写以前写不了的程序",而在"用更少的代码表达同样的约束"。约束写得越省,人越愿意写;写得越严,运行期炸点越少。带着这个视角看六项特性,每一条都是"把过去的口头约定升级为机器强制"。 构造器属性提升(8.0):声明属性、接收参数、赋初值三件事合一。
本节摘要:PHP 8 系列的更新可以概括为"更少的样板、更严的边界、更高的下限"。本节挑对服务端日常影响最大的六项:构造器属性提升、match 表达式、命名参数、空安全运算符、枚举与只读属性,每项给"旧写法对照新写法",并交代它们在本册前文出现过的位置——你会发现第二章的代码已经悄悄用上了大半。文末附版本时间线与"该不该立刻用"的判断法。
一个常被误解的点:语言新版本的价值不在"能写以前写不了的程序",而在"用更少的代码表达同样的约束"。约束写得越省,人越愿意写;写得越严,运行期炸点越少。带着这个视角看六项特性,每一条都是"把过去的口头约定升级为机器强制"。
构造器属性提升(8.0):声明属性、接收参数、赋初值三件事合一。本册 2.1 节的 Ticket 类已用过:
// 旧写法:三段式样板 class Ticket { private string $from; private string $to; public function __construct(string $from, string $to) { $this->from = $from; $this->to = $to; } } // 新写法:一行顶七行 class Ticket2 { public function __construct( private string $from, private string $to, ) {} }
match 表达式(8.0):严格比较、带返回值、无穿透。1.2 节的状态分发与 2.10 节的工厂分支都用过它,对照的旧写法是加 break、易穿透的 switch。
命名参数(8.0):调用时按名传值,跳过中间的默认项。1.3 节的 ticket(1001, 2002, fast: false) 即是——可读性收益在"多默认参数函数"上尤其明显。
空安全运算符(8.0):链式取值中途遇 null 直接短路返回 null,不再逐步判空:
// 旧写法:层层 isset $city = null; if ($order !== null) { if ($order->user !== null) { $city = $order->user->city; } } // 新写法:一条链 $city = $order?->user?->city; // 任一环节为 null,整体即 null

这两项(8.1)是本册最推荐优先上车的,因为它们直接消灭整类错误。枚举把"一组固定取值"定义成类型,替代散落各处的字符串魔法值:
<?php enum SeatClass: string { case Economy = 'economy'; case Business = 'business'; } function fare(SeatClass $seat): float { return match ($seat) { SeatClass::Economy => 240.0, SeatClass::Business => 580.0, }; } echo fare(SeatClass::from('business')), "\n"; // 580 // fare('商务舱'); // TypeError:字符串进不来
对照 1.7 节的手工校验 in_array($seat, ['economy','business'], true):枚举让"合法值只有这些"从校验规则升级为类型签名,函数签名本身即文档,传错在调用瞬间就报错。只读属性(8.1,8.2 支持只读类)则锁定"创建后不可改":
<?php class Ticket { public function __construct( public readonly string $from, public readonly string $to, public readonly float $price, ) {} } $t = new Ticket('南苑', '虹桥', 240.0); // $t->price = 0; // Error:只读属性创建后不可改
车票出了票就不能改价——领域对象的天然需求,如今是语言能力。JIT(即时编译)在这轮更新里名气最大,但对典型 Web 请求的体感收益有限(2.5 节说过瓶颈多在数据链路),反而在数学密集与图像处理场景明显——排序自然放在最后。
给老代码"换代"的务实路径。第一步永远是升运行环境并跑通 2.8 节的测试集;第二步把最疼的点换成新语法,优先级建议:字符串魔法值密集的模块换枚举、构造器样板多的类用属性提升、深层判空链换空安全运算符;第三步把 2.9 节的静态分析等级随版本拧紧。每一小步都有测试与工具兜底,换代就不再是"大爆炸重构"。
特性要上手才算学会。拿本册再熟悉不过的"车票"业务,把一段 7.x 口径的代码完整改写成 8.x 风格,六项特性一次落位:
<?php // 改写前:7.x 口径(无类型、魔法值、层层判空) class FareService { private $rules; public function __construct() { $this->rules = ['economy' => 240.0, 'business' => 580.0]; } public function calc($order) { if ($order != null) { if (isset($order['seat'])) { $seat = $order['seat']; if (array_key_exists($seat, $this->rules)) { return $this->rules[$seat]; } } } return 0; } }
<?php enum SeatClass: string { case Economy = 'economy'; case Business = 'business'; } // 改写后:8.x 口径(枚举、构造器提升、只读、match、空安全) final class FareService { /** @var array<SeatClass, float> */ public function __construct(private readonly array $rules = [ SeatClass::Economy => 240.0, SeatClass::Business => 580.0, ]) {} public function calc(?array $order): float { $seat = SeatClass::tryFrom((string) ($order['seat'] ?? '')); return $seat === null ? 0.0 : $this->rules[$seat]; } }
逐条盘账:魔法值字符串换成 SeatClass 枚举,非法取值进不了类型;三段式属性样板换成构造器提升;规则表声明为 readonly,运行期改不了;多层判空虽在本例收敛成了 ?? 与 tryFrom,若对象链更深则轮到空安全运算符;分支返回用 match 严丝合缝。改写前后行为完全一致,但非法输入的暴露时机从"业务深处"提前到"调用瞬间"——这正是 4.1 开头说的"约束前移"落到一段真实代码上的样子。建议你从自己项目里挑一段类似的老代码做同款改写,改完跑一遍 2.8 的测试集,这条练习胜过再读十篇特性盘点。
⚠️ 教程与版本错位:网上大量资料停留在 7.x 口径(松散比较、旧版字符函数),照抄前先核对特性版本起点。
⚠️ 枚举滥用:取值会随业务增长的集合(比如车次)不该枚举,枚举适合"集合稳定且封闭"的语义。
下一节换一副眼镜:不写代码时,去哪里找答案、找库、找同伴。