翻譯進度
51.93% 已翻譯
更新時間:
2024年6月30日 上午8:26:00 [世界標準時間]
翻譯人員:
幫我們翻譯此頁

Eloquent:API 資源

簡介

在製作 API 的時候,我們可能會需要一個轉換層來將 Eloquent Model 轉換為實際要回傳給使用者的 JSON 回應。舉例來說,有一些屬性我們只希望特定使用者能看到,其他使用者不能看;或者,我們可能會希望在 Model 的 JSON 呈現上總是包含特定的關聯。Eloquent 的 Resource 類別能讓我們輕鬆自如地將 Model 與 Model Collection 轉換為 JSON。

當然,我們還是可以用 Eloquent Model 上的 toJson 方法來將其轉為 JSON。不過,Eloquent Resource 能讓我們對於 Model 與其關聯要怎麼被 JSON 序列化有更大的控制。

產生 Resource

若要產生 Resource 類別,可以使用 make:resource Artisan 指令。預設情況下,Resource 會被放在專案中的 app/Http/Resources 目錄。Resource 繼承自 Illuminate\Http\Resources\Json\JsonResource 類別:

1php artisan make:resource UserResource
1php artisan make:resource UserResource

Resource Collection

除了產生能轉換個別 Model 的 Resource 外,也可以產生一個 Resource 來轉換一組包含 Model 的 Collection。這樣以來,就可以在我們的 JSON 回應中包含連結或其他詮釋資訊等與該資源中整個 Collection 相關的資訊。

要建立 Resource Collection,應在建立資源時使用 --collection 旗標。或者,也可以在資源名稱後方加上 Collection,以讓 Laravel 知道我們要建立的是 Resource Collection。

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

概念概覽

lightbulb

這裡提供的是對於 Resource 與 Resource Collection 的高階概覽。我們強烈建議你閱讀本文中的其他段落以深入瞭解 Resource 提供的客製化功能。

在深入瞭解撰寫 Resource 時可用的所有方法前,我們先來用一種高階的方式看看 Laravel 中可以怎麼使用 Resource。Resource 類別代表的是需要被轉換為 JSON 結構的單一 Model。舉例來說,下列是一個簡單的 UserResource Resource 類別:

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}
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}

每個 Resource 類別都有一個 toArray 方法,toArray 方法回傳一組包含屬性的陣列,當資源從路由或 Controller 方法中作為回應回傳時,這些屬性會被轉為 JSON。

可以注意到,我們直接使用 $this 變數來存取 Model 的屬性。這是因為,在存取屬性與方法時,Resource 類別會自動幫我們將這些存取代理 (Proxy) 到底層的 Model,以讓我們能方便地存取。定義好 Resource 之後,就可以從路由或 Controller 中回傳這個 Resource。該 Resource 的建構函式中接受底層的 Model 實體:

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});
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});

Resource Collection

在路由或 Controller 中回傳一組包含 Resource 的 Collection、或是有分頁的回應時,建立 Resource 的時候應使用 Resource 類別提供的 collection 方法:

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

請注意,在回傳 Collection 的同時,這麼做將無法附上額外的詮釋資料。若想自訂 Resource Collection 的回應,可以建立一個專門的 Resource 來代表該 Collection:

1php artisan make:resource UserCollection
1php artisan make:resource UserCollection

產生好 Resource Collection 後,就可以輕鬆地定義要被包含在回應中的詮釋資料:

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}
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}

定義好 Resource Collection 後,就可以在路由或 Controller 內回傳這個 Resource Collection:

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

保留 Collection 的索引鍵

從路由內回傳 Resource Collection 的時候,Laravel 會重設該 Collection 的索引鍵,讓索引鍵按找數字順序排列。不過,可以在 Resource 類別中加上 preserveKeys 屬性來讓 Collection 保留其原始的索引鍵:

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\JsonResource;
6 
7class UserResource extends JsonResource
8{
9 /**
10 * Indicates if the resource's collection keys should be preserved.
11 *
12 * @var bool
13 */
14 public $preserveKeys = true;
15}
1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\JsonResource;
6 
7class UserResource extends JsonResource
8{
9 /**
10 * Indicates if the resource's collection keys should be preserved.
11 *
12 * @var bool
13 */
14 public $preserveKeys = true;
15}

preservedKeys 屬性設為 true 的時,當我們從路由或 Controller 內回傳這個 Collection 的時候,就會保留其中的索引鍵:

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

Customizing the Underlying Resource Class

一般來說,Laravel 會將 Collection 內的各個結果映射到其單數 (Singular) 的 Resource 類別上,然後再用來填充 Resource Collection 的 $this->collection。Laravel 會使用 Collection 的類別名稱去掉 Collection 來推測單數 Resource 的名稱。此外,根據使用者的個人偏好,單數 Resource 有可能會以 Resource 結尾,也有可能不會。

舉例來說,UserCollection 會將給定的 User 實體映射到 UserResource 資源上。若要自訂此行為,可以複寫 Resource Collection 的 $collects 屬性:

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\ResourceCollection;
6 
7class UserCollection extends ResourceCollection
8{
9 /**
10 * The resource that this resource collects.
11 *
12 * @var string
13 */
14 public $collects = Member::class;
15}
1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\ResourceCollection;
6 
7class UserCollection extends ResourceCollection
8{
9 /**
10 * The resource that this resource collects.
11 *
12 * @var string
13 */
14 public $collects = Member::class;
15}

撰寫 Resource

lightbulb

若你還未閱讀《概念概覽》,我們強烈建議你在繼續之前先閱讀該段落。

Resource 只負責把給定的 Model 轉換為陣列。因此,每個 Resource 都包含了一個 toArray 方法,可用來將 Model 的屬性轉換為對適合用在 API 的陣列,並讓你能在路由或 Controller 內回傳這個陣列:

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}
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}

定義好 Resource 後,我們就可以直接在路由或 Controller 內將其回傳:

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});
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});

關聯

若要定義想被包含在回應內的關聯資源,可直接將這些關聯加在 Resource 的 toArray 方法內。在這個例子中,我們會使用 PostResource Resource 的 collection 方法來講使用者的部落格貼文加到 Resource 回應內:

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}
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}
lightbulb

若只想在關聯已載入的情況下才將這些關聯包含在回應內,請參考條件式關聯

Resource Collection

Resource 會將單一 Model 轉換為陣列,Resource Collection 則將一組包含 Model 的 Collection 轉換為陣列。不過,並不需要為每個 Model 都應以一個對應的 Resource Collection,因為所有的 Resource 都有提供一個 collection 方法,可以讓你即時產生一個特別的 Resource Collection:

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

不過,若有需要定義與 Collection 一起回傳的詮釋資料 (Meta Data),就需要定義你自己的 Resource Collection:

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}
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}

與單數 Resource 類似,Resource Collection 也可以直接在路由或 Controller 內回傳:

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

資料包裝

預設情況下,當 Resource 回應被轉成 JSON 時,最外層的資源會被包裝在 data 索引鍵地下。因此,舉例來說,正常的 Resource Collection 回應會長這樣:

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}
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 或其他每個請求都會載入的 Service Provider 內呼叫這個方法:

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}
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}
lightbulb

withoutWrapping 方法只會影響最外層的回應。withoutWrapping 方法不會移除手動新增到 Resource Collection 內的 data 索引鍵。

包裝巢狀 Resource

對於要如何包裝 Resource 的關聯,開發人員擁有絕對的自由。若想讓所有無論是不是巢狀的 Resource Collection 都被包裝在 data 索引鍵內,則可以為每個 Resource 都定義一個 Resource Collection 類別,並以 data 索引鍵回傳 Collection。

讀者可能會疑惑:這麼做會不會讓最外層的 Resource 被包裝在 data 索引鍵裡兩次?別擔心,Laravel 不會讓你不小心把 Resource 重複包裝的。因此,在轉換 Resource Collection 時,完全不需擔心 Resource Collection 的巢狀層級:

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}
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}

Data Wrapping and Pagination

當使用 Resource 回應來回船分頁過的 Collection 時,就算有呼叫過 withoutWrapper 方法,Laravel 也會將這些 Resource 資料放在 data 索引鍵裡。這是因為,所有經過分頁的回應都會包含如 metalinks 等有關 Paginator 狀態的資訊:

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}
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 的 Paginator 實體傳入 Resource 的 collection 方法或自訂 Resource Collection 中:

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

所有經過分頁的回應都會包含 metalinks 等關於 Paginator 狀態的資訊:

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}
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}

Customizing the Pagination Information

若想自定分頁 Response 中 linksmeta 索引鍵內所包含的資訊,可在 Resource 上定義 paginationInformation 方法。該方法會收到一個 $paginated 資料,以及一個陣列的 $default 資訊。$default 是一個包含 linksmeta 索引鍵的陣列:

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}
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}

有條件的屬性

有時候,我們只想在滿足特定條件的時候才在 Resource 回應內包含某個屬性。舉例來說,我們或許會只在目前使用者是「管理員 (Administrator)」時才將某個值包含在回應內。Laravel 為這種情況提供了一個輔助函式。可以使用 when 來有條件地在 Resource 回應內新增屬性:

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}
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}

在這個例子中,只有在已登入使用者的 idAdmin 方法回傳 true 時,最終的 Resource 回應內才會包含 secret 索引鍵。若 isAdmin 方法回傳 false,則在 Resource 回應回傳給用戶端之前,secret 索引鍵就會被移除。使用 when 方法就可以用一種語意化的方法來定義 Resource,而不需要建立陣列時使用條件式陳述式。

when 的第二個引數也可以傳入一個閉包。可以使用這個閉包來只在條件為 true 時計算結果值:

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

whenHas 方法可用來在當底層 Model 內真的有包含某個屬性時將該屬性包含進來:

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

此外,當屬性不為 null 時,也可以使用 whereNotNull 來在 Resource Response 中包含某個屬性:

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

合併有條件的屬性

有時候,我們可能會有數個屬性想依據相同的條件來被包含在 Resource 回應中。在這種情況下,可以使用 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}
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,則這些屬性將在傳回給用戶端前被從 Resource 回應中移除。

lightbulb

mergeWhen 方法不可使用在組合使用數字與字串索引鍵的陣列上。此外,mergeWhen 也不能使用在數字索引鍵沒有連續的陣列上。

有條件的關聯

除了有條件地載入屬性外,我們還能依據關聯是否已載入到 Model 上來有條件地將關聯包含在 Resource 回應中。這樣一來,我們的 Controller 就能決定要載入哪些關聯,而 Resource 就可以輕鬆地在有載入這些關聯的時候才將這些關聯包含在回應中。最後,這麼做就能輕鬆地在 Resource 內避免「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}
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 索引鍵就會被移除。

條件式關聯的計數

除了可有條件地包含關聯外,我們也可以依據關聯的計數是否已載入到 Model 上來將關聯的「計數 (Count)」包含到 Resource Response 上:

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

whenCounted 方法可用來有條件地在 Resource Response 上包含關聯的計數。使用該方法也能避免在關聯沒有計數時將其包含進來:

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}
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 索引鍵就會在傳給用戶端前被移除。

其他的匯總類型,如 avg, sum, minmax也可以使用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'),
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) 資訊

除了有條件地將關聯資訊加到 Resource 回應內之外,我們還能使用 whenPivotLoaded 方法來有條件地包含 Many-to-many 關聯的中介資料表中的資料庫。whenPivotLoaded 方法的第一個引數為樞紐資料表的名稱,第二個引數則為一個閉包,該閉包應回傳當 Model 上有樞紐資訊時要回傳的值:

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}
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}

若關聯使用自訂的中介資料表 Model,則應講樞紐資料表的實體作為第一個引數傳給 whenPivotLoaded 方法:

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

若中介資料表使用 pivot 以外的存取方法,則可以使用 whenPivotLoadesAs 方法:

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}
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}

新增詮釋資料

Some JSON API standards require the addition of meta data to your resource and resource collections responses. This often includes things like links to the resource or related resources, or meta data about the resource itself. If you need to return additional meta data about a resource, include it in your toArray method. For example, you might include links information when transforming a resource collection:

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}
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}

當從 Resource 內回傳額外的詮釋資料時,不需擔心是否會不小心複寫在回傳經過分頁的資料時 Laravel 自動新增的 linksmeta 等資料。若有定義額外的 links,這些 links 會跟 Paginator 提供的連結合併在一起。

最上層的詮釋資料

有時候,我們可能會想只在當目前 Resource 是回傳的最外層 Resource 時才包含某些詮釋資料。一般來說,這種情況的詮釋資料就是對於回應的詮釋資料。若要定義這種詮釋資料,可在 Resource 類別內加上一個 with 方法。這個方法應回傳一組包含詮釋資料的陣列,用以在目前 Resource 是最外層 Resource 時包含在 Resource 回應內:

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}
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}

在建立 Resource 時加上詮釋資料

我們也可以在路由或 Controller 內建構 Resource 時加上最上層的資料。 所有 Resource 內都提供了一個 additional 方法,可將要加到 Resource 回應內的資料放在陣列中傳給該方法:

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

Resource 回應

剛才已經讀過,我們可以直接從路由或 Controller 內回傳 Resource:

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});
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});

不過,有的時候我們會需要在回應被傳回用戶端前自訂外連 HTTP 回應。有兩種方法可以自訂外連 HTTP 回應。第一種方法,我們可以將 response 方法串連到 Resource 後面。該方法會回傳一個 Illuminate\Http\JsonResponse 實體,讓我們能對回應的標頭有完整的控制權:

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

或者,也可以在 Resource 裡面定義一個 withResponse。這個方法會在該 Resource 是回應中最外層 Resource 時被呼叫:

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}
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}
翻譯進度
51.93% 已翻譯
更新時間:
2024年6月30日 上午8:26:00 [世界標準時間]
翻譯人員:
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.