Skip to content

Eloquent: API 资源

简介

在构建 API 时,你可能需要一个转换层,位于 Eloquent 模型和实际返回给应用程序用户的 JSON 响应之间。例如,你可能希望为部分用户显示某些属性而不为其他用户显示,或者你可能希望始终在模型的 JSON 表示中包含某些关联关系。Eloquent 的资源类允许你以表达性和简单的方式将模型和模型集合转换为 JSON。

当然,你始终可以使用 toJson 方法将 Eloquent 模型或集合转换为 JSON;但是,Eloquent 资源为模型及其关联关系的 JSON 序列化提供了更精细和强大的控制。

生成资源

要生成资源类,可以使用 make:resource Artisan 命令。默认情况下,资源将放置在应用程序的 app/Http/Resources 目录中。资源继承 Illuminate\Http\Resources\Json\JsonResource 类:

shell
php artisan make:resource UserResource

资源集合

除了生成转换单个模型的资源外,你还可以生成负责转换模型集合的资源。这允许你的 JSON 响应包含与整个给定资源集合相关的链接和其他元信息。

要创建资源集合,应该在创建资源时使用 --collection 标志。或者,在资源名称中包含 Collection 一词将向 Laravel 表明它应该创建集合资源。集合资源继承 Illuminate\Http\Resources\Json\ResourceCollection 类:

shell
php artisan make:resource User --collection

php artisan make:resource UserCollection

概念概述

NOTE

这是资源和资源集合的高级概述。强烈建议你阅读本文档的其他部分,以更深入地了解资源为你提供的自定义和功能。

在深入了解编写资源时可用的所有选项之前,让我们先从高层次了解资源在 Laravel 中的使用方式。资源类表示需要转换为 JSON 结构的单个模型。例如,这是一个简单的 UserResource 资源类:

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * 将资源转换为数组。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

每个资源类定义一个 toArray 方法,该方法返回在资源作为路由或控制器方法的响应返回时应转换为 JSON 的属性数组。

请注意,我们可以直接从 $this 变量访问模型属性。这是因为资源类会自动将属性和方法访问代理到底层模型以方便访问。一旦定义了资源,它就可以从路由或控制器返回。资源通过其构造函数接受底层模型实例:

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

Route::get('/user/{id}', function (string $id) {
    return new UserResource(User::findOrFail($id));
});

为方便起见,你可以使用模型的 toResource 方法,该方法将使用框架约定自动发现模型的基础资源:

php
return User::findOrFail($id)->toResource();

当调用 toResource 方法时,Laravel 将尝试定位与模型名称匹配并在最接近模型命名空间的 Http\Resources 命名空间中可选地以 Resource 为后缀的资源。

如果你的资源类不遵循此命名约定或位于不同的命名空间中,可以使用 UseResource 属性为模型指定默认资源:

php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResource;

#[UseResource(CustomUserResource::class)]
class User extends Model
{
    // ...
}

或者,你可以通过将资源类传递给 toResource 方法来指定:

php
return User::findOrFail($id)->toResource(CustomUserResource::class);

资源集合

如果你返回资源集合或分页响应,应该在路由或控制器中创建资源实例时使用资源类提供的 collection 方法:

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

Route::get('/users', function () {
    return UserResource::collection(User::all());
});

或者,为方便起见,你可以使用 Eloquent 集合的 toResourceCollection 方法,该方法将使用框架约定自动发现模型的基础资源集合:

php
return User::all()->toResourceCollection();

当调用 toResourceCollection 方法时,Laravel 将尝试定位与模型名称匹配并在最接近模型命名空间的 Http\Resources 命名空间中以 Collection 为后缀的资源集合。

如果你的资源集合类不遵循此命名约定或位于不同的命名空间中,可以使用 UseResourceCollection 属性为模型指定默认资源集合:

php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;

#[UseResourceCollection(CustomUserCollection::class)]
class User extends Model
{
    // ...
}

或者,你可以通过将资源集合类传递给 toResourceCollection 方法来指定:

php
return User::all()->toResourceCollection(CustomUserCollection::class);

自定义资源集合

默认情况下,资源集合不允许添加可能需要随集合返回的任何自定义元数据。如果你想自定义资源集合响应,可以创建专用资源来表示集合:

shell
php artisan make:resource UserCollection

一旦生成了资源集合类,你就可以轻松定义应包含在响应中的任何元数据:

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * 将资源集合转换为数组。
     *
     * @return array<int|string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

定义资源集合后,它可以从路由或控制器返回:

php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

或者,为方便起见,你可以使用 Eloquent 集合的 toResourceCollection 方法,该方法将使用框架约定自动发现模型的基础资源集合:

php
return User::all()->toResourceCollection();

当调用 toResourceCollection 方法时,Laravel 将尝试定位与模型名称匹配并在最接近模型命名空间的 Http\Resources 命名空间中以 Collection 为后缀的资源集合。

保留集合键

当从路由返回资源集合时,Laravel 会重置集合的键,使它们按数字顺序排列。但是,你可以在资源类上使用 PreserveKeys 属性,指示是否应保留集合的原始键:

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\PreserveKeys;
use Illuminate\Http\Resources\Json\JsonResource;

#[PreserveKeys]
class UserResource extends JsonResource
{
    // ...
}

preserveKeys 属性设置为 true 时,从路由或控制器返回集合时将保留集合键:

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

Route::get('/users', function () {
    return UserResource::collection(User::all()->keyBy->id);
});

自定义底层资源类

通常,资源集合的 $this->collection 属性会自动填充将集合的每个项目映射到其单一资源类的结果。单一资源类被假定为集合的类名,不包含类名末尾的 Collection 部分。此外,根据你的个人偏好,单一资源类可以带有或不带有 Resource 后缀。

例如,UserCollection 将尝试将给定的用户实例映射到 UserResource 资源。要自定义此行为,可以在资源集合上使用 Collects 属性:

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\Collects;
use Illuminate\Http\Resources\Json\ResourceCollection;

#[Collects(Member::class)]
class UserCollection extends ResourceCollection
{
    // ...
}

编写资源

NOTE

如果你还没有阅读概念概述,强烈建议在继续阅读本文档之前先阅读它。

资源只需要将给定模型转换为数组。因此,每个资源都包含一个 toArray 方法,该方法将模型的属性转换为可以从应用程序的路由或控制器返回的 API 友好数组:

php
<?php

namespace App\Http\Resources;