Laravel API 资源与转换

在构建 API 时,我们经常需要将 Eloquent 模型或数据转换为特定的 JSON 结构,例如隐藏某些字段、添加计算属性、嵌套关联数据等。Laravel 的 API 资源(Resource) 提供了一种优雅且可复用的方式来完成数据转换。本章将全面讲解如何创建资源类、处理集合、条件加载以及高级用法。

🎯 核心价值: API 资源将数据转换逻辑从控制器中抽离,让代码更清晰、易于测试,并且可以统一处理模型及其关联的响应格式。

1. 什么是 API 资源

API 资源是一个中间层,它接收原始数据(通常是 Eloquent 模型或集合),并将其转换为开发者指定的 JSON 结构。资源类通常存放在 app/Http/Resources 目录下,分为两种:

  • 资源类(Resource): 用于转换单个模型实例。
  • 资源集合类(ResourceCollection): 用于转换模型集合,也支持分页数据。

2. 生成资源类

使用 Artisan 命令快速生成资源:


php artisan make:resource UserResource
php artisan make:resource UserCollection --collection
                    

也可以生成一个同时支持单个和集合的资源(不推荐,建议分开):


php artisan make:resource UserResource --collection
                    

3. 定义数据转换

在资源类的 toArray 方法中,定义需要返回的数组结构。你可以在控制器中直接使用资源。

示例:用户资源


<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toISOString(),
            'is_admin' => $this->isAdmin(),
        ];
    }
}
                    

在控制器中使用:


use App\Http\Resources\UserResource;
use App\Models\User;

public function show($id)
{
    $user = User::findOrFail($id);
    return new UserResource($user);
}
                    

返回的 JSON 将自动包含 data 包裹层。

4. 处理集合与分页

对于多个模型,可以使用资源集合类。如果未单独生成集合类,可以使用 Resource::collection() 方法。


// 在控制器中返回集合
public function index()
{
    $users = User::paginate(15);
    return UserResource::collection($users);
}
                    

如果你生成了独立的集合类(如 UserCollection),可以在其中自定义 toArray 方法添加额外的元数据(如统计信息)。


<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'meta' => ['total_users' => $this->collection->count()],
        ];
    }
}
                    

使用时:


return new UserCollection(User::all());
                    

5. 嵌套资源与关联关系

你可以在资源中包含模型的关联,只需在 toArray 中返回对应的资源类即可。


// PostResource 中包含用户信息和评论集合
public function toArray($request)
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'user' => new UserResource($this->whenLoaded('user')),
        'comments' => CommentResource::collection($this->whenLoaded('comments')),
    ];
}
                    

使用 whenLoaded 方法,可以避免在没有预加载关联时进行不必要的查询。控制器中需要手动预加载:


$post = Post::with('user', 'comments')->find(1);
return new PostResource($post);
                    

6. 条件属性

你可以根据条件决定是否包含某个字段,例如只有管理员才能看到邮箱字段。


return [
    'id' => $this->id,
    'name' => $this->name,
    'email' => $this->when($request->user()->isAdmin(), $this->email),
    'secret' => $this->when(true, '默认值'), // 始终包含
    'is_verified' => $this->when($this->isVerified(), true, false),
];
                    

还可以使用 whenHas 在属性存在时包含,或使用 whenNotNull

7. 添加元数据

你可以在资源中使用 additional 方法添加顶层元数据,或者通过 with 方法静态定义。


// 在控制器中动态添加
return (new UserResource($user))->additional([
    'meta' => [
        'version' => '1.0.0',
        'timestamp' => now()->toIso8601String(),
    ],
]);

// 或者在资源类中定义默认元数据
public function with($request)
{
    return [
        'status' => 'success',
    ];
}
                    

8. 资源与分页

当使用分页时,Laravel 会自动生成包含 linksmeta 信息的结构。你可以将分页结果直接传递给 collection 方法:


$users = User::paginate(20);
return UserResource::collection($users);
                    

返回的 JSON 示例:


{
    "data": [ /* 用户资源数组 */ ],
    "links": {
        "first": "http://example.com/users?page=1",
        "last": "...",
        "prev": null,
        "next": "..."
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 5,
        "per_page": 20,
        "to": 20,
        "total": 100
    }
}
                    

9. 自定义数据包裹键名

默认情况下,资源会被包裹在 data 键中。你可以在 App\Providers\AppServiceProvider 中修改全局包裹键名,或者在资源类中覆盖 $wrap 属性。


// 全局修改(在 AppServiceProvider 的 boot 方法中)
use Illuminate\Http\Resources\Json\JsonResource;

JsonResource::withoutWrapping(); // 完全移除包裹

// 或者更改包裹键名为 'result'
JsonResource::wrap('result');
                    

10. 最佳实践与注意事项

  • 延迟加载关联: 始终使用 whenLoaded,避免 N+1 查询问题。
  • 保持资源轻量: 不要在资源中进行复杂的业务逻辑,只负责数据转换。
  • 复用资源类: 对于模型的不同输出场景(如列表视图和详情视图),可以创建不同的资源类或使用条件属性。
  • 测试资源: Laravel 提供了 assertResource 等测试方法,可以单独测试资源输出。
  • API 版本控制: 可以通过命名空间区分不同版本的资源(如 V1\UserResourceV2\UserResource)。
  • 避免在资源中直接调用数据库: 所有需要的数据应提前通过控制器预加载传入。
💡 提示: 如果你需要将资源输出为 JSON API 规范格式,可以安装 laravel-json-api 包,但原生资源已能满足大多数需求。

11. 总结

Laravel API 资源系统提供了一套优雅且可维护的数据转换方案。通过定义资源类,你可以轻松控制 API 输出结构,隐藏敏感字段,嵌套关联,并统一处理分页和元数据。合理使用条件属性和预加载,可以避免性能问题。掌握 API 资源,将使你的 API 开发更加高效和专业。

📖 官方文档: Eloquent API Resources 提供了更全面的参考。