Laravel:在资源类中正确获取枚举字段的值
问题:Laravel资源类中OrderStatus实例返回空数组的优雅解决办法
环境与代码背景
使用PHP 8.1 + Laravel 9开发项目,已实现以下代码结构:
1. OrderStatuses枚举类
enum OrderStatuses : string { case New = 'new'; case Pending = 'pending'; case Canceled = 'canceled'; case Paid = 'paid'; case PaymentFailed = 'payment-failed'; public function createOrderStatus(Order $order) : OrderStatus { return match($this) { OrderStatuses::Pending => new PendingOrderStatus($order), OrderStatuses::Canceled => new CanceledOrderStatus($order), OrderStatuses::Paid => new PaidOrderStatus($order), OrderStatuses::PaymentFailed => new PaymentFailedOrderStatus($order), default => new NewOrderStatus($order) }; } }
2. OrderStatus抽象类及子类
abstract class OrderStatus { public function __construct(protected Order $order) { } /** * 判断订单状态是否可变更 * * @return bool */ abstract public function canBeChanged() : bool; }
class PaidOrderStatus extends OrderStatus { public function canBeChanged(): bool { return false; } }
其他子类仅
canBeChanged方法实现不同
3. OrdersResource资源类
class OrdersResource extends JsonResource { public function toArray($request): array { return [ 'id' => (string)$this->id, 'type' => 'orders', 'attributes' => [ 'status' => $this->status, 'payment_type' => $this->payment_type, 'payment_transaction_no' => $this->payment_transaction_no, 'subtotal' => $this->subtotal, 'taxes' => $this->taxes, 'total' => $this->total, 'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ] ]; } }
4. 控制器调用方式
return (new OrdersResource($order)) ->response()->setStatusCode(ResponseAlias::HTTP_OK);
5. Order模型配置
protected $casts = [ 'status' => OrderStatuses::class, ];
protected function status(): Attribute { return new Attribute( get: fn(string $value) => OrderStatuses::from($value)->createOrderStatus($this), ); }
问题现象
实现枚举后,资源类返回的status字段变为空数组[],示例返回内容:
"id" => "86b4e2da-76d4-4e66-8016-88a251513050" "type" => "orders" "attributes" => array:8 [ "status" => [] "payment_type" => "card" "payment_transaction_no" => "3kaL92f5UwOG" "subtotal" => 3005.76 "taxes" => 0 "total" => 3005.76 "created_at" => "2022-08-31T12:47:55.000000Z" "updated_at" => "2022-08-31T12:47:55.000000Z" ] ]
在OrdersResource的toArray方法中用dd($this->status)确认,$this->status是正确的Domain\Order\Enums\PaidOrderStatus实例,但JSON序列化时被转为空数组。尝试给子类添加__toString()方法无效,临时给子类加test()方法返回枚举值可解决,但不够优雅,需要更合理的方案。
优雅解决方案
方案1:给OrderStatus抽象类实现JsonSerializable接口
让状态实例遵循PHP序列化规范,在抽象类中统一实现序列化逻辑:
abstract class OrderStatus implements \JsonSerializable { public function __construct(protected Order $order) { } abstract public function canBeChanged() : bool; public function jsonSerialize(): mixed { // 方式1:直接读取订单原始状态值 return $this->order->getOriginal('status'); // 方式2:通过类名映射到对应枚举值(更严谨,避免原始值被篡改) // return match(static::class) { // NewOrderStatus::class => OrderStatuses::New->value, // PendingOrderStatus::class => OrderStatuses::Pending->value, // CanceledOrderStatus::class => OrderStatuses::Canceled->value, // PaidOrderStatus::class => OrderStatuses::Paid->value, // PaymentFailedOrderStatus::class => OrderStatuses::PaymentFailed->value, // }; } }
Laravel在序列化资源时会自动调用jsonSerialize()方法,返回正确的状态字符串,同时不影响状态实例的业务方法调用。
方案2:在资源类中直接获取枚举值
如果不想修改OrderStatus类,可在资源类中直接读取订单的原始状态值或枚举实例的value:
class OrdersResource extends JsonResource { public function toArray($request): array { // 方式1:读取数据库原始值 $statusValue = $this->getOriginal('status'); // 方式2:通过枚举转换获取标准值 // $statusValue = OrderStatuses::from($this->getOriginal('status'))->value; return [ 'id' => (string)$this->id, 'type' => 'orders', 'attributes' => [ 'status' => $statusValue, 'payment_type' => $this->payment_type, 'payment_transaction_no' => $this->payment_transaction_no, 'subtotal' => $this->subtotal, 'taxes' => $this->taxes, 'total' => $this->total, 'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ] ]; } }
方案3:模型Attribute区分场景返回值
通过判断请求场景,在非序列化场景返回状态实例,序列化场景返回枚举值:
protected function status(): Attribute { return new Attribute( get: function(string $value) { $statusInstance = OrderStatuses::from($value)->createOrderStatus($this); // 仅当请求期望JSON返回时,返回枚举值 if (request()->expectsJson()) { return OrderStatuses::from($value)->value; } return $statusInstance; }, ); }
方案选择建议
优先选择方案1,将序列化逻辑封装在OrderStatus类内部,符合单一职责原则,后续新增状态子类时只需确保映射逻辑覆盖即可,维护性更强。
内容的提问来源于stack exchange,提问作者MrCujo
相关产品推荐
相关产品推荐

