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

自訂底層的 Resource 類別

一般來說,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}

若使用 data 以外的其他自訂索引鍵,可以在 Resource 類別內定義一個 $wrap屬性:

1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\JsonResource;
6 
7class UserResource extends JsonResource
8{
9 /**
10 * The "data" wrapper that should be applied.
11 *
12 * @var string|null
13 */
14 public static $wrap = 'user';
15}
1<?php
2 
3namespace App\Http\Resources;
4 
5use Illuminate\Http\Resources\Json\JsonResource;
6 
7class UserResource extends JsonResource
8{
9 /**
10 * The "data" wrapper that should be applied.
11 *
12 * @var string|null
13 */
14 public static $wrap = 'user';
15}

若不想要包裝最外層的資源,請叫用基礎 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}
exclamation

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}

資料包裝與分頁

當使用 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}

自訂分頁的資訊

若想自定分頁 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 回應中移除。

exclamation

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}

新增詮釋資料

有的 JSON API 標準中要求要有 Resource 與 Resource Collection 回應的詮釋資料 (Meta Data)。通常包含如 Resource 或關聯 Resource 的連結 (links)、或是有關 Resource 本身的詮釋資料等。若想回傳關於 Resource 的額外詮釋資料,請將這些資料包含在 toArray 方法內。舉例來說,我們可能會想在轉換 Resource Collection 時包含 link 資訊:

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

留言

尚無留言

“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.