![]()
85.9%的Packagist用户已经跑在PHP 8.x上,但直到2025年6月,还有人在用字符串'pending'和'shipped'当状态码。这就像用便利贴管理银行流水——能跑,但随时可能着火。
PHP 8.1在2021年底推出的枚举(Enum),本质是给一堆固定值发了身份证。不是常量那种复印件,是带芯片的、能验真伪的、IDE能认出来的实体证件。这篇从底层语法讲到Laravel的自动转换,代码可以直接复制。
纯枚举:类型安全的第一道门
以前写状态管理,大概是这么个画风:
class OrderStatus { const PENDING = 'pending'; const SHIPPED = 'shipped'; } function updateStatus(string $status): void { // 传个'banana'进来完全不会报错 } updateStatus('banana'); // 安静崩溃
字符串类型提示是个黑洞。拼写错误、大小写混乱、重构时漏改——这些bug不会在编译期暴露,只会在凌晨三点的生产环境给你惊喜。grep是你的唯一保险,但grep不认识语义。
纯枚举(Pure Enum)把这个洞堵上了:
enum OrderStatus { case Pending; case Shipped; case Delivered; } function updateStatus(OrderStatus $status): void { // 只认OrderStatus的实例,其他一律TypeError } updateStatus(OrderStatus::Pending); // 通过 updateStatus('pending'); // 编译期直接拦住
纯枚举是对象,可以用===比较,可以塞进match表达式,可以当类型提示。但它有个硬伤:没有标量值,存不进数据库。就像你有身份证,但银行系统只认银行卡号。
Backed Enum:给数据库一个能认的号码
![]()
Backed Enum给每个case配了一个字符串或整数 backing value。数据库能存,代码里还是类型安全。
enum OrderStatus: string { case Pending = 'pending'; case Shipped = 'shipped'; case Delivered = 'delivered'; }
注意那个冒号和类型声明。这是PHP的语法:Backed Enum必须指定string或int,每个case必须赋值,且类型一致。你不能混着来——Pending配字符串,Shipped配整数,解释器会直接甩脸。
从数据库读出来的原始值,可以用from()或tryFrom()转成枚举实例:
$status = OrderStatus::from('shipped'); // 返回OrderStatus::Shipped $status = OrderStatus::tryFrom('invalid'); // 返回null,不抛异常
from()在值不存在时抛ValueError,tryFrom()返回null。选哪个取决于你想让错误在哪里爆炸——是尽早崩溃,还是交给调用方处理。
Laravel的自动转换:写一次,到处用
手动转换很快变成样板代码。Laravel 10+的Eloquent casting系统把这个过程自动化了。
在模型里声明casts()方法:
class Order extends Model { protected function casts(): array { return [ 'status' => OrderStatus::class, ]; } }
![]()
现在读写status字段时,Laravel自动处理转换。数据库里存的是'pending'字符串,代码里拿到的是OrderStatus::Pending实例。赋值时反过来——传枚举实例,存字符串进去。
这个机制依赖Backed Enum的string/int backing value。纯枚举用不了,因为Laravel不知道往数据库里写什么。
查询时也能直接用枚举:
Order::where('status', OrderStatus::Pending)->get(); // 自动解包成'pending'
Laravel在底层调用枚举的value属性获取backing value,拼进SQL。你不用操心类型转换,专注业务逻辑就行。
边缘情况:当数据库比你先升级
现实不总干净。假设数据库里有个遗留值'cancelled',但你的枚举还没定义这个case。tryFrom()会返回null,from()会抛异常。
处理方案取决于场景。如果是新系统,加数据库约束保证数据干净。如果是遗留系统,可以在模型里加个访问器兜底:
protected function status(): Attribute { return Attribute::make( get: fn (string $value) => OrderStatus::tryFrom($value) ?? OrderStatus::Pending, ); }
或者干脆在枚举里留个Unknown case,专门吃这些脏数据。没有银弹,只有权衡。
PHP 8.1已经在2025年底结束生命周期,但枚举语法原封不动地进了8.2、8.3、8.4。你现在写的代码,未来五年不会过时。
W3Techs 2026年2月的数据显示,PHP still powers 72.0% of all websites with known server-side language。在这个体量下,一个类型安全的基础特性普及了三年才到85%,是工具链的滞后,还是开发者对"能跑就行"的路径依赖?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.