在构建 API 时,我们经常需要将 Eloquent 模型或数据转换为特定的 JSON 结构,例如隐藏某些字段、添加计算属性、嵌套关联数据等。Laravel 的 API 资源(Resource) 提供了一种优雅且可复用的方式来完成数据转换。本章将全面讲解如何创建资源类、处理集合、条件加载以及高级用法。
API 资源是一个中间层,它接收原始数据(通常是 Eloquent 模型或集合),并将其转换为开发者指定的 JSON 结构。资源类通常存放在 app/Http/Resources 目录下,分为两种:
使用 Artisan 命令快速生成资源:
php artisan make:resource UserResource
php artisan make:resource UserCollection --collection
也可以生成一个同时支持单个和集合的资源(不推荐,建议分开):
php artisan make:resource UserResource --collection
在资源类的 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 包裹层。
对于多个模型,可以使用资源集合类。如果未单独生成集合类,可以使用 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());
你可以在资源中包含模型的关联,只需在 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);
你可以根据条件决定是否包含某个字段,例如只有管理员才能看到邮箱字段。
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。
你可以在资源中使用 additional 方法添加顶层元数据,或者通过 with 方法静态定义。
// 在控制器中动态添加
return (new UserResource($user))->additional([
'meta' => [
'version' => '1.0.0',
'timestamp' => now()->toIso8601String(),
],
]);
// 或者在资源类中定义默认元数据
public function with($request)
{
return [
'status' => 'success',
];
}
当使用分页时,Laravel 会自动生成包含 links 和 meta 信息的结构。你可以将分页结果直接传递给 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
}
}
默认情况下,资源会被包裹在 data 键中。你可以在 App\Providers\AppServiceProvider 中修改全局包裹键名,或者在资源类中覆盖 $wrap 属性。
// 全局修改(在 AppServiceProvider 的 boot 方法中)
use Illuminate\Http\Resources\Json\JsonResource;
JsonResource::withoutWrapping(); // 完全移除包裹
// 或者更改包裹键名为 'result'
JsonResource::wrap('result');
whenLoaded,避免 N+1 查询问题。assertResource 等测试方法,可以单独测试资源输出。V1\UserResource、V2\UserResource)。laravel-json-api 包,但原生资源已能满足大多数需求。
Laravel API 资源系统提供了一套优雅且可维护的数据转换方案。通过定义资源类,你可以轻松控制 API 输出结构,隐藏敏感字段,嵌套关联,并统一处理分页和元数据。合理使用条件属性和预加载,可以避免性能问题。掌握 API 资源,将使你的 API 开发更加高效和专业。