跳轉至內容

Eloquent: API 資源

簡介

在構建 API 時,你可能需要一個轉換層,位於 Eloquent 模型與實際返回給使用者應用的 JSON 響應之間。例如,你可能希望向部分使用者顯示特定屬性而不向其他使用者顯示,或者希望在模型的 JSON 表示中始終包含某些關聯。Eloquent 的資源類允許你以富有表現力且簡便的方式將模型和模型集合轉換為 JSON。

當然,你總是可以使用 Eloquent 模型或集合的 toJson 方法將其轉換為 JSON;然而,Eloquent 資源對模型的 JSON 序列化及其關聯提供了更細粒度且健壯的控制。

生成資源

要生成資源類,可以使用 make:resource Artisan 命令。預設情況下,資源會被放置在應用程式的 app/Http/Resources 目錄中。資源繼承自 Illuminate\Http\Resources\Json\JsonResource 類。

1php artisan make:resource UserResource

資源集合

除了生成轉換單個模型的資源外,你還可以生成負責轉換模型集合的資源。這允許你的 JSON 響應包含與給定資源的整個集合相關的連結和其他元資訊。

要建立資源集合,應在建立資源時使用 --collection 標誌。或者,在資源名稱中包含 Collection 一詞,Laravel 就會知道應該建立一個集合資源。集合資源繼承自 Illuminate\Http\Resources\Json\ResourceCollection 類。

1php artisan make:resource User --collection
2 
3php artisan make:resource UserCollection

概念概述

這是關於資源和資源集合的高階概述。強烈建議閱讀本文件的其他章節,以深入瞭解資源為你提供的定製化能力和強大功能。

在深入瞭解編寫資源時可用的所有選項之前,讓我們先從宏觀上看看 Laravel 中是如何使用資源的。資源類代表了需要被轉換為 JSON 結構的單個模型。例如,這是一個簡單的 UserResource 資源類。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\JsonResource;
7 
8class UserResource extends JsonResource
9{
10 /**
11 * Transform the resource into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'id' => $this->id,
19 'name' => $this->name,
20 'email' => $this->email,
21 'created_at' => $this->created_at,
22 'updated_at' => $this->updated_at,
23 ];
24 }
25}

每個資源類都定義了一個 toArray 方法,該方法返回一個屬性陣列,當資源作為路由或控制器方法的響應返回時,這些屬性將被轉換為 JSON。

注意,我們可以直接透過 $this 變數訪問模型屬性。這是因為資源類會自動將屬性和方法訪問代理到底層模型,以方便訪問。一旦定義了資源,它就可以從路由或控制器中返回。資源透過其建構函式接收底層模型例項。

1use App\Http\Resources\UserResource;
2use App\Models\User;
3 
4Route::get('/user/{id}', function (string $id) {
5 return new UserResource(User::findOrFail($id));
6});

為了方便起見,你可以使用模型的 toResource 方法,該方法將使用框架約定自動發現模型底層的資源。

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

呼叫 toResource 方法時,Laravel 將嘗試在最接近模型名稱空間的 Http\Resources 名稱空間中定位與模型名稱匹配且可選後綴為 Resource 的資源。

如果你的資源類不遵循此命名約定,或者位於不同的名稱空間中,你可以使用 UseResource 屬性為模型指定預設資源。

1<?php
2 
3namespace App\Models;
4 
5use App\Http\Resources\CustomUserResource;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Attributes\UseResource;
8 
9#[UseResource(CustomUserResource::class)]
10class User extends Model
11{
12 // ...
13}

或者,你可以透過將資源類傳遞給 toResource 方法來指定資源類。

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

資源集合

如果你返回的是資源集合或分頁響應,則應在路由或控制器中建立資源例項時,使用資源類提供的 collection 方法。

1use App\Http\Resources\UserResource;
2use App\Models\User;
3 
4Route::get('/users', function () {
5 return UserResource::collection(User::all());
6});

或者,為了方便起見,你可以使用 Eloquent 集合的 toResourceCollection 方法,該方法將使用框架約定自動發現模型底層的資源集合。

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

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型名稱空間的 Http\Resources 名稱空間中定位與模型名稱匹配且字尾為 Collection 的資源集合。

如果你的資源集合類不遵循此命名約定,或者位於不同的名稱空間中,你可以使用 UseResourceCollection 屬性為模型指定預設資源集合。

1<?php
2 
3namespace App\Models;
4 
5use App\Http\Resources\CustomUserCollection;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
8 
9#[UseResourceCollection(CustomUserCollection::class)]
10class User extends Model
11{
12 // ...
13}

或者,你可以透過將資源集合類傳遞給 toResourceCollection 方法來指定資源集合類。

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

自定義資源集合

預設情況下,資源集合不允許新增可能需要與集合一起返回的任何自定義元資料。如果你想自定義資源集合的響應,可以建立一個專門的資源來表示該集合。

1php artisan make:resource UserCollection

生成資源集合類後,你可以輕鬆定義應包含在響應中的任何元資料。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\ResourceCollection;
7 
8class UserCollection extends ResourceCollection
9{
10 /**
11 * Transform the resource collection into an array.
12 *
13 * @return array<int|string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'data' => $this->collection,
19 'links' => [
20 'self' => 'link-value',
21 ],
22 ];
23 }
24}

定義資源集合後,即可從路由或控制器中返回它。

1use App\Http\Resources\UserCollection;
2use App\Models\User;
3 
4Route::get('/users', function () {
5 return new UserCollection(User::all());
6});

或者,為了方便起見,你可以使用 Eloquent 集合的 toResourceCollection 方法,該方法將使用框架約定自動發現模型底層的資源集合。

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

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型名稱空間的 Http\Resources 名稱空間中定位與模型名稱匹配且字尾為 Collection 的資源集合。

保留集合鍵

當從路由返回資源集合時,Laravel 會重置集合的鍵,使其按數字順序排列。但是,你可以在資源類上使用 PreserveKeys 屬性,以指明是否應保留集合的原始鍵。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Attributes\PreserveKeys;
6use Illuminate\Http\Resources\Json\JsonResource;
7 
8#[PreserveKeys]
9class UserResource extends JsonResource
10{
11 // ...
12}

preserveKeys 屬性設定為 true 時,從路由或控制器返回集合時將保留集合鍵。

1use App\Http\Resources\UserResource;
2use App\Models\User;
3 
4Route::get('/users', function () {
5 return UserResource::collection(User::all()->keyBy->id);
6});

自定義底層資源類

通常,資源集合的 $this->collection 屬性會自動填充,其結果是將集合中的每個專案對映到其對應的單數資源類。單數資源類被假定為集合類名去掉末尾的 Collection 部分。此外,根據個人喜好,單數資源類名可以帶有 Resource 字尾,也可以不帶。

例如,UserCollection 將嘗試將給定的使用者例項對映到 UserResource 資源。要自定義此行為,你可以在資源集合上使用 Collects 屬性。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Attributes\Collects;
6use Illuminate\Http\Resources\Json\ResourceCollection;
7 
8#[Collects(Member::class)]
9class UserCollection extends ResourceCollection
10{
11 // ...
12}

編寫資源

如果你還沒有閱讀概念概述,強烈建議在繼續閱讀本文件之前先閱讀它。

資源只需要將給定的模型轉換為陣列。因此,每個資源都包含一個 toArray 方法,該方法將模型的屬性轉換為 API 友好的陣列,並可從應用程式的路由或控制器中返回。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\JsonResource;
7 
8class UserResource extends JsonResource
9{
10 /**
11 * Transform the resource into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'id' => $this->id,
19 'name' => $this->name,
20 'email' => $this->email,
21 'created_at' => $this->created_at,
22 'updated_at' => $this->updated_at,
23 ];
24 }
25}

定義資源後,它可以直接從路由或控制器中返回。

1use App\Models\User;
2 
3Route::get('/user/{id}', function (string $id) {
4 return User::findOrFail($id)->toUserResource();
5});

關聯關係

如果你希望在響應中包含相關資源,可以將它們新增到資源 toArray 方法返回的陣列中。在此示例中,我們將使用 PostResource 資源的 collection 方法將使用者的部落格文章新增到資源響應中。

1use App\Http\Resources\PostResource;
2use Illuminate\Http\Request;
3 
4/**
5 * Transform the resource into an array.
6 *
7 * @return array<string, mixed>
8 */
9public function toArray(Request $request): array
10{
11 return [
12 'id' => $this->id,
13 'name' => $this->name,
14 'email' => $this->email,
15 'posts' => PostResource::collection($this->posts),
16 'created_at' => $this->created_at,
17 'updated_at' => $this->updated_at,
18 ];
19}

如果你只想在關聯已載入的情況下才包含它們,請檢視關於條件關聯的文件。

資源集合

雖然資源將單個模型轉換為陣列,但資源集合將模型集合轉換為陣列。然而,並不是必須為每個模型定義資源集合類,因為所有 Eloquent 模型集合都提供了一個 toResourceCollection 方法,以便即時生成“臨時”資源集合。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 return User::all()->toResourceCollection();
5});

但是,如果你需要自定義隨集合返回的元資料,則必須定義自己的資源集合。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\ResourceCollection;
7 
8class UserCollection extends ResourceCollection
9{
10 /**
11 * Transform the resource collection into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'data' => $this->collection,
19 'links' => [
20 'self' => 'link-value',
21 ],
22 ];
23 }
24}

與單數資源一樣,資源集合也可以直接從路由或控制器中返回。

1use App\Http\Resources\UserCollection;
2use App\Models\User;
3 
4Route::get('/users', function () {
5 return new UserCollection(User::all());
6});

或者,為了方便起見,你可以使用 Eloquent 集合的 toResourceCollection 方法,該方法將使用框架約定自動發現模型底層的資源集合。

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

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型名稱空間的 Http\Resources 名稱空間中定位與模型名稱匹配且字尾為 Collection 的資源集合。

資料包裝

預設情況下,當資源響應轉換為 JSON 時,最外層的資源會被包裝在一個 data 鍵中。因此,典型的資源集合響應看起來如下所示。

1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[email protected]"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[email protected]"
12 }
13 ]
14}

如果你想停用最外層資源的包裝,則應在基礎的 Illuminate\Http\Resources\Json\JsonResource 類上呼叫 withoutWrapping 方法。通常,你應該在 AppServiceProvider 或其他在每個請求中載入的服務提供者中呼叫此方法。

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Http\Resources\Json\JsonResource;
6use Illuminate\Support\ServiceProvider;
7 
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Register any application services.
12 */
13 public function register(): void
14 {
15 // ...
16 }
17 
18 /**
19 * Bootstrap any application services.
20 */
21 public function boot(): void
22 {
23 JsonResource::withoutWrapping();
24 }
25}

withoutWrapping 方法只會影響最外層的響應,不會移除你手動新增到資源集合中的 data 鍵。

包裝巢狀資源

你可以完全自由地決定如何包裝資源的關聯。如果你希望所有資源集合都包裝在 data 鍵中(無論其巢狀程度如何),你應該為每個資源定義一個資源集合類,並在 data 鍵中返回該集合。

你可能想知道這是否會導致最外層資源被包裝在兩個 data 鍵中。別擔心,Laravel 永遠不會讓你的資源被意外地雙重包裝,因此你不必擔心正在轉換的資源集合的巢狀級別。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\ResourceCollection;
6 
7class CommentsCollection extends ResourceCollection
8{
9 /**
10 * Transform the resource collection into an array.
11 *
12 * @return array<string, mixed>
13 */
14 public function toArray(Request $request): array
15 {
16 return ['data' => $this->collection];
17 }
18}

資料包裝與分頁

當透過資源響應返回分頁集合時,即使呼叫了 withoutWrapping 方法,Laravel 也會將你的資源資料包裝在 data 鍵中。這是因為分頁響應始終包含 metalinks 鍵,其中包含有關分頁器狀態的資訊。

1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[email protected]"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[email protected]"
12 }
13 ],
14 "links":{
15 "first": "http://example.com/users?page=1",
16 "last": "http://example.com/users?page=1",
17 "prev": null,
18 "next": null
19 },
20 "meta":{
21 "current_page": 1,
22 "from": 1,
23 "last_page": 1,
24 "path": "http://example.com/users",
25 "per_page": 15,
26 "to": 10,
27 "total": 10
28 }
29}

分頁

你可以將 Laravel 分頁器例項傳遞給資源的 collection 方法或自定義資源集合。

1use App\Http\Resources\UserCollection;
2use App\Models\User;
3 
4Route::get('/users', function () {
5 return new UserCollection(User::paginate());
6});

或者,為了方便起見,你可以使用分頁器的 toResourceCollection 方法,該方法將使用框架約定自動發現分頁模型的底層資源集合。

1return User::paginate()->toResourceCollection();

分頁響應始終包含 metalinks 鍵,其中包含有關分頁器狀態的資訊。

1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[email protected]"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[email protected]"
12 }
13 ],
14 "links":{
15 "first": "http://example.com/users?page=1",
16 "last": "http://example.com/users?page=1",
17 "prev": null,
18 "next": null
19 },
20 "meta":{
21 "current_page": 1,
22 "from": 1,
23 "last_page": 1,
24 "path": "http://example.com/users",
25 "per_page": 15,
26 "to": 10,
27 "total": 10
28 }
29}

自定義分頁資訊

如果你想自定義分頁響應的 linksmeta 鍵中包含的資訊,可以在資源上定義一個 paginationInformation 方法。該方法將接收 $paginated 資料和包含 linksmeta 鍵的 $default 資訊陣列。

1/**
2 * Customize the pagination information for the resource.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @param array $paginated
6 * @param array $default
7 * @return array
8 */
9public function paginationInformation($request, $paginated, $default)
10{
11 $default['links']['custom'] = 'https://example.com';
12 
13 return $default;
14}

條件屬性

有時你可能希望僅在滿足特定條件時才在資源響應中包含某個屬性。例如,你可能希望僅在當前使用者是“管理員”時才包含某個值。Laravel 提供了多種輔助方法來幫助你處理這種情況。when 方法可用於有條件地向資源響應新增屬性。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
13 'created_at' => $this->created_at,
14 'updated_at' => $this->updated_at,
15 ];
16}

在此示例中,只有當已驗證使用者的 isAdmin 方法返回 true 時,secret 鍵才會在最終的資源響應中返回。如果該方法返回 false,則 secret 鍵將在傳送到客戶端之前從資源響應中移除。when 方法允許你富有表現力地定義資源,而無需在構建陣列時使用條件語句。

when 方法的第二個引數也可以接受閉包,允許你僅在給定條件為 true 時才計算結果值。

1'secret' => $this->when($request->user()->isAdmin(), function () {
2 return 'secret-value';
3}),

whenHas 方法可用於在屬性實際存在於底層模型上時將其包含進來。

1'name' => $this->whenHas('name'),

此外,whenNotNull 方法可用於在屬性不為 null 時將其包含在資源響應中。

1'name' => $this->whenNotNull($this->name),

合併條件屬性

有時你可能希望根據相同的條件包含多個屬性。在這種情況下,你可以使用 mergeWhen 方法,僅在給定條件為 true 時將屬性包含在響應中。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 $this->mergeWhen($request->user()->isAdmin(), [
13 'first-secret' => 'value',
14 'second-secret' => 'value',
15 ]),
16 'created_at' => $this->created_at,
17 'updated_at' => $this->updated_at,
18 ];
19}

同樣,如果給定條件為 false,這些屬性將在傳送到客戶端之前從資源響應中移除。

mergeWhen 方法不應在混合字串和數字鍵的陣列中使用。此外,它不應在包含非順序排序的數字鍵的陣列中使用。

條件關聯

除了有條件地載入屬性外,你還可以根據關聯是否已在模型上載入,有條件地在資源響應中包含關聯。這允許你的控制器決定模型上應該載入哪些關聯,而你的資源可以輕鬆地僅在它們實際載入時包含它們。最終,這使得在資源中避免“N+1”查詢問題變得更加容易。

whenLoaded 方法可用於有條件地載入關聯。為了避免不必要地載入關聯,此方法接受關聯的名稱而不是關聯本身。

1use App\Http\Resources\PostResource;
2 
3/**
4 * Transform the resource into an array.
5 *
6 * @return array<string, mixed>
7 */
8public function toArray(Request $request): array
9{
10 return [
11 'id' => $this->id,
12 'name' => $this->name,
13 'email' => $this->email,
14 'posts' => PostResource::collection($this->whenLoaded('posts')),
15 'created_at' => $this->created_at,
16 'updated_at' => $this->updated_at,
17 ];
18}

在此示例中,如果關聯尚未載入,posts 鍵將在傳送到客戶端之前從資源響應中移除。

條件關聯計數

除了有條件地包含關聯外,你還可以根據模型上是否已載入關聯的計數,有條件地在資源響應中包含關聯計數。

1new UserResource($user->loadCount('posts'));

whenCounted 方法可用於有條件地在資源響應中包含關聯的計數。此方法避免在關聯計數不存在時不必要地包含該屬性。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 'posts_count' => $this->whenCounted('posts'),
13 'created_at' => $this->created_at,
14 'updated_at' => $this->updated_at,
15 ];
16}

在此示例中,如果 posts 關聯的計數尚未載入,posts_count 鍵將在傳送到客戶端之前從資源響應中移除。

其他型別的聚合,例如 avgsumminmax,也可以使用 whenAggregated 方法有條件地載入。

1'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
2'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
3'words_min' => $this->whenAggregated('posts', 'words', 'min'),
4'words_max' => $this->whenAggregated('posts', 'words', 'max'),

條件中間表(Pivot)資訊

除了在資源響應中有條件地包含關聯資訊外,你還可以使用 whenPivotLoaded 方法有條件地包含多對多關聯中間表中的資料。whenPivotLoaded 方法的第一個引數接受中間表的名稱。第二個引數應為一個閉包,返回模型上可用中間表資訊時要返回的值。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'expires_at' => $this->whenPivotLoaded('role_user', function () {
12 return $this->pivot->expires_at;
13 }),
14 ];
15}

如果你的關聯使用的是自定義中間表模型,則可以將中間表模型的例項作為第一個引數傳遞給 whenPivotLoaded 方法。

1'expires_at' => $this->whenPivotLoaded(new Membership, function () {
2 return $this->pivot->expires_at;
3}),

如果你的中間表使用的訪問器不是 pivot,你可以使用 whenPivotLoadedAs 方法。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
12 return $this->subscription->expires_at;
13 }),
14 ];
15}

新增元資料

某些 JSON API 標準要求向你的資源和資源集合響應中新增元資料。這通常包括指向資源或相關資源的 links,或關於資源本身的元資料。如果你需要返回關於資源的額外元資料,請將其包含在 toArray 方法中。例如,在轉換資源集合時,你可能會包含 links 資訊。

1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'data' => $this->collection,
10 'links' => [
11 'self' => 'link-value',
12 ],
13 ];
14}

當從資源返回額外的元資料時,你無需擔心會意外覆蓋 Laravel 在返回分頁響應時自動新增的 linksmeta 鍵。你定義的任何額外 links 都將與分頁器提供的連結合併。

頂級元資料

有時,如果資源是返回的最外層資源,你可能希望僅在資源響應中包含特定的元資料。通常,這包括關於整個響應的元資訊。要定義此元資料,請向資源類新增 with 方法。此方法應返回一個數組,該陣列中的元資料僅在資源是所轉換的最外層資源時才會包含在資源響應中。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\ResourceCollection;
6 
7class UserCollection extends ResourceCollection
8{
9 /**
10 * Transform the resource collection into an array.
11 *
12 * @return array<string, mixed>
13 */
14 public function toArray(Request $request): array
15 {
16 return parent::toArray($request);
17 }
18 
19 /**
20 * Get additional data that should be returned with the resource array.
21 *
22 * @return array<string, mixed>
23 */
24 public function with(Request $request): array
25 {
26 return [
27 'meta' => [
28 'key' => 'value',
29 ],
30 ];
31 }
32}

構建資源時新增元資料

你也可以在路由或控制器中構建資源例項時新增頂級資料。所有資源都可用的 additional 方法接受一個數組,該陣列中的資料將被新增到資源響應中。

1return User::all()
2 ->load('roles')
3 ->toResourceCollection()
4 ->additional(['meta' => [
5 'key' => 'value',
6 ]]);

JSON:API 資源

Laravel 自帶 JsonApiResource,這是一種生成符合 JSON:API 規範的響應的資源類。它繼承自標準的 JsonResource 類,並自動處理資源物件結構、關聯、稀疏欄位集、包含項,並將 Content-Type 頭設定為 application/vnd.api+json

Laravel 的 JSON:API 資源處理響應的序列化。如果你還需要解析傳入的 JSON:API 查詢引數(如過濾和排序),Spatie 的 Laravel Query Builder 是一個極好的輔助擴充套件包。

生成 JSON:API 資源

要生成 JSON:API 資源,請使用帶有 --json-api 標誌的 make:resource Artisan 命令。

1php artisan make:resource PostResource --json-api

生成的類將繼承 Illuminate\Http\Resources\JsonApi\JsonApiResource,幷包含 $attributes$relationships 屬性供你定義。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\JsonApi\JsonApiResource;
7 
8class PostResource extends JsonApiResource
9{
10 /**
11 * The resource's attributes.
12 */
13 public $attributes = [
14 // ...
15 ];
16 
17 /**
18 * The resource's relationships.
19 */
20 public $relationships = [
21 // ...
22 ];
23}

JSON:API 資源可以像標準資源一樣從路由和控制器中返回。

1use App\Http\Resources\PostResource;
2use App\Models\Post;
3 
4Route::get('/api/posts/{post}', function (Post $post) {
5 return new PostResource($post);
6});

或者,為了方便起見,你可以使用模型的 toResource 方法。

1Route::get('/api/posts/{post}', function (Post $post) {
2 return $post->toResource();
3});

這將生成一個符合 JSON:API 的響應。

1{
2 "data": {
3 "id": "1",
4 "type": "posts",
5 "attributes": {
6 "title": "Hello World",
7 "body": "This is my first post."
8 }
9 }
10}

要返回 JSON:API 資源集合,請使用 collection 方法或 toResourceCollection 輔助方法。

1return PostResource::collection(Post::all());
2 
3return Post::all()->toResourceCollection();

定義屬性

有兩種方法可以定義 JSON:API 資源中包含哪些屬性。

最簡單的方法是在資源上定義 $attributes 屬性。你可以列出屬性名稱作為值,這些值將直接從底層模型中讀取。

1public $attributes = [
2 'title',
3 'body',
4 'created_at',
5];

或者,為了完全控制資源的屬性,你可以重寫資源上的 toAttributes 方法。

1/**
2 * Get the resource's attributes.
3 *
4 * @return array<string, mixed>
5 */
6public function toAttributes(Request $request): array
7{
8 return [
9 'title' => $this->title,
10 'body' => $this->body,
11 'is_published' => $this->published_at !== null,
12 'created_at' => $this->created_at,
13 'updated_at' => $this->updated_at,
14 ];
15}

定義關聯

JSON:API 資源支援定義遵循 JSON:API 規範的關聯。關聯僅在客戶端透過 include 查詢引數請求時才會被序列化。

$relationships 屬性

你可以透過資源上的 $relationships 屬性定義資源的可包含關聯。

1public $relationships = [
2 'author',
3 'comments',
4];

當將關聯名稱列為值時,Laravel 將解析相應的 Eloquent 關聯並自動發現合適的資源類。如果你需要顯式指定資源類,可以將關聯定義為鍵/類對。

1use App\Http\Resources\UserResource;
2 
3public $relationships = [
4 'author' => UserResource::class,
5 'comments',
6];

或者,你可以重寫資源上的 toRelationships 方法。

1/**
2 * Get the resource's relationships.
3 */
4public function toRelationships(Request $request): array
5{
6 return [
7 'author' => UserResource::class,
8 'comments',
9 ];
10}

包含關聯

客戶端可以使用 include 查詢引數請求相關資源。

1GET /api/posts/1?include=author,comments

這將生成一個響應,其中 relationships 鍵中包含資源識別符號物件,頂級 included 陣列中包含完整的資源物件。

1{
2 "data": {
3 "id": "1",
4 "type": "posts",
5 "attributes": {
6 "title": "Hello World"
7 },
8 "relationships": {
9 "author": {
10 "data": {
11 "id": "1",
12 "type": "users"
13 }
14 },
15 "comments": {
16 "data": [
17 {
18 "id": "1",
19 "type": "comments"
20 }
21 ]
22 }
23 }
24 },
25 "included": [
26 {
27 "id": "1",
28 "type": "users",
29 "attributes": {
30 "name": "Taylor Otwell"
31 }
32 },
33 {
34 "id": "1",
35 "type": "comments",
36 "attributes": {
37 "body": "Great post!"
38 }
39 }
40 ]
41}

巢狀關聯可以使用點符號包含。

1GET /api/posts/1?include=comments.author

關聯深度

預設情況下,巢狀關聯包含受限於最大深度。你可以在應用程式的服務提供者之一中使用 maxRelationshipDepth 方法自定義此限制。

1use Illuminate\Http\Resources\JsonApi\JsonApiResource;
2 
3JsonApiResource::maxRelationshipDepth(3);

資源型別與 ID

預設情況下,資源的 type 派生自資源類名。例如,PostResource 生成型別 postsBlogPostResource 生成 blog-posts。資源的 id 從模型的主鍵中解析。

如果你需要自定義這些值,可以重寫資源上的 toTypetoId 方法。

1/**
2 * Get the resource's type.
3 */
4public function toType(Request $request): string
5{
6 return 'articles';
7}
8 
9/**
10 * Get the resource's ID.
11 */
12public function toId(Request $request): string
13{
14 return (string) $this->uuid;
15}

這在資源型別應與其類名不同時特別有用,例如當 AuthorResource 包裝 User 模型且應輸出型別 authors 時。

稀疏欄位集與包含項

JSON:API 資源支援 稀疏欄位集,允許客戶端使用 fields 查詢引數僅請求每種資源型別的特定屬性。

1GET /api/posts?fields[posts]=title,created_at&fields[users]=name

這將僅包含 posts 資源的 titlecreated_at 屬性,以及 users 資源的 name 屬性。

忽略查詢字串

如果你想為給定的資源響應停用稀疏欄位集過濾,可以呼叫 ignoreFieldsAndIncludesInQueryString 方法。

1return $post->toResource()
2 ->ignoreFieldsAndIncludesInQueryString();

包含預先載入的關聯

預設情況下,關聯僅在透過 include 查詢引數請求時才包含在響應中。如果你想包含所有之前已預載入的關聯,而不考慮查詢字串,可以呼叫 includePreviouslyLoadedRelationships 方法。

1return $post->load('author', 'comments')
2 ->toResource()
3 ->includePreviouslyLoadedRelationships();

你可以透過重寫資源上的 toLinkstoMeta 方法,將連結和元資訊新增到你的 JSON:API 資源物件中。

1/**
2 * Get the resource's links.
3 */
4public function toLinks(Request $request): array
5{
6 return [
7 'self' => route('api.posts.show', $this->resource),
8 ];
9}
10 
11/**
12 * Get the resource's meta information.
13 */
14public function toMeta(Request $request): array
15{
16 return [
17 'readable_created_at' => $this->created_at->diffForHumans(),
18 ];
19}

這將在響應的資源物件中新增 linksmeta 鍵。

1{
2 "data": {
3 "id": "1",
4 "type": "posts",
5 "attributes": {
6 "title": "Hello World"
7 },
8 "links": {
9 "self": "https://example.com/api/posts/1"
10 },
11 "meta": {
12 "readable_created_at": "2 hours ago"
13 }
14 }
15}

資源響應

如你所讀,資源可以直接從路由和控制器中返回。

1use App\Models\User;
2 
3Route::get('/user/{id}', function (string $id) {
4 return User::findOrFail($id)->toResource();
5});

但是,有時你可能需要在傳送到客戶端之前自定義傳出的 HTTP 響應。有兩種方法可以實現這一點。首先,你可以將 response 方法連結到資源上。此方法將返回一個 Illuminate\Http\JsonResponse 例項,讓你完全控制響應頭。

1use App\Http\Resources\UserResource;
2use App\Models\User;
3 
4Route::get('/user', function () {
5 return User::find(1)
6 ->toResource()
7 ->response()
8 ->header('X-Value', 'True');
9});

或者,你可以在資源本身內定義 withResponse 方法。當資源作為響應中的最外層資源返回時,將呼叫此方法。

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\JsonResponse;
6use Illuminate\Http\Request;
7use Illuminate\Http\Resources\Json\JsonResource;
8 
9class UserResource extends JsonResource
10{
11 /**
12 * Transform the resource into an array.
13 *
14 * @return array<string, mixed>
15 */
16 public function toArray(Request $request): array
17 {
18 return [
19 'id' => $this->id,
20 ];
21 }
22 
23 /**
24 * Customize the outgoing response for the resource.
25 */
26 public function withResponse(Request $request, JsonResponse $response): void
27 {
28 $response->header('X-Value', 'True');
29 }
30}