You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 17:19:04