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 --collection23php artisan make:resource UserCollection
1php artisan make:resource User --collection23php artisan make:resource UserCollection
概念概覽
這裡提供的是對於 Resource 與 Resource Collection 的高階概覽。我們強烈建議你閱讀本文中的其他段落以深入瞭解 Resource 提供的客製化功能。
在深入瞭解撰寫 Resource 時可用的所有方法前,我們先來用一種高階的方式看看 Laravel 中可以怎麼使用 Resource。Resource 類別代表的是需要被轉換為 JSON 結構的單一 Model。舉例來說,下列是一個簡單的 UserResource
Resource 類別:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\JsonResource;78class UserResource extends JsonResource9{10 /**11 * Transform the resource into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {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<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\JsonResource;78class UserResource extends JsonResource9{10 /**11 * Transform the resource into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {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;34Route::get('/user/{id}', function (string $id) {5 return new UserResource(User::findOrFail($id));6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::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;34Route::get('/users', function () {5 return UserResource::collection(User::all());6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::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<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\ResourceCollection;78class UserCollection extends ResourceCollection9{10 /**11 * Transform the resource collection into an array.12 *13 * @return array<int|string, mixed>14 */15 public function toArray(Request $request): array16 {17 return [18 'data' => $this->collection,19 'links' => [20 'self' => 'link-value',21 ],22 ];23 }24}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\ResourceCollection;78class UserCollection extends ResourceCollection9{10 /**11 * Transform the resource collection into an array.12 *13 * @return array<int|string, mixed>14 */15 public function toArray(Request $request): array16 {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;34Route::get('/users', function () {5 return new UserCollection(User::all());6});
1use App\Http\Resources\UserCollection;2use App\Models\User;34Route::get('/users', function () {5 return new UserCollection(User::all());6});
保留 Collection 的索引鍵
從路由內回傳 Resource Collection 的時候,Laravel 會重設該 Collection 的索引鍵,讓索引鍵按找數字順序排列。不過,可以在 Resource 類別中加上 preserveKeys
屬性來讓 Collection 保留其原始的索引鍵:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\JsonResource;67class UserResource extends JsonResource8{9 /**10 * Indicates if the resource's collection keys should be preserved.11 *12 * @var bool13 */14 public $preserveKeys = true;15}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\JsonResource;67class UserResource extends JsonResource8{9 /**10 * Indicates if the resource's collection keys should be preserved.11 *12 * @var bool13 */14 public $preserveKeys = true;15}
preservedKeys
屬性設為 true
的時,當我們從路由或 Controller 內回傳這個 Collection 的時候,就會保留其中的索引鍵:
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::get('/users', function () {5 return UserResource::collection(User::all()->keyBy->id);6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::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<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class UserCollection extends ResourceCollection8{9 /**10 * The resource that this resource collects.11 *12 * @var string13 */14 public $collects = Member::class;15}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class UserCollection extends ResourceCollection8{9 /**10 * The resource that this resource collects.11 *12 * @var string13 */14 public $collects = Member::class;15}
撰寫 Resource
若你還未閱讀《概念概覽》,我們強烈建議你在繼續之前先閱讀該段落。
Resource 只負責把給定的 Model 轉換為陣列。因此,每個 Resource 都包含了一個 toArray
方法,可用來將 Model 的屬性轉換為對適合用在 API 的陣列,並讓你能在路由或 Controller 內回傳這個陣列:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\JsonResource;78class UserResource extends JsonResource9{10 /**11 * Transform the resource into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {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<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\JsonResource;78class UserResource extends JsonResource9{10 /**11 * Transform the resource into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {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;34Route::get('/user/{id}', function (string $id) {5 return new UserResource(User::findOrFail($id));6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::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;34/**5 * Transform the resource into an array.6 *7 * @return array<string, mixed>8 */9public function toArray(Request $request): array10{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;34/**5 * Transform the resource into an array.6 *7 * @return array<string, mixed>8 */9public function toArray(Request $request): array10{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}
若只想在關聯已載入的情況下才將這些關聯包含在回應內,請參考條件式關聯。
Resource Collection
Resource 會將單一 Model 轉換為陣列,Resource Collection 則將一組包含 Model 的 Collection 轉換為陣列。不過,並不需要為每個 Model 都應以一個對應的 Resource Collection,因為所有的 Resource 都有提供一個 collection
方法,可以讓你即時產生一個特別的 Resource Collection:
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::get('/users', function () {5 return UserResource::collection(User::all());6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::get('/users', function () {5 return UserResource::collection(User::all());6});
不過,若有需要定義與 Collection 一起回傳的詮釋資料 (Meta Data),就需要定義你自己的 Resource Collection:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\ResourceCollection;78class UserCollection extends ResourceCollection9{10 /**11 * Transform the resource collection into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {17 return [18 'data' => $this->collection,19 'links' => [20 'self' => 'link-value',21 ],22 ];23 }24}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Request;6use Illuminate\Http\Resources\Json\ResourceCollection;78class UserCollection extends ResourceCollection9{10 /**11 * Transform the resource collection into an array.12 *13 * @return array<string, mixed>14 */15 public function toArray(Request $request): array16 {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;34Route::get('/users', function () {5 return new UserCollection(User::all());6});
1use App\Http\Resources\UserCollection;2use App\Models\User;34Route::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.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",12 }13 ]14}
1{2 "data": [3 {4 "id": 1,5 "name": "Eladio Schroeder Sr.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",12 }13 ]14}
若不想要包裝最外層的資源,請叫用基礎 Illuminate\Http\Resources\Json\JsonResource
類別底下的 withoutWrapping
方法。一般來說,應在 AppServiceProvider
或其他每個請求都會載入的 Service Provider 內呼叫這個方法:
1<?php23namespace App\Providers;45use Illuminate\Http\Resources\Json\JsonResource;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Register any application services.12 */13 public function register(): void14 {15 // ...16 }1718 /**19 * Bootstrap any application services.20 */21 public function boot(): void22 {23 JsonResource::withoutWrapping();24 }25}
1<?php23namespace App\Providers;45use Illuminate\Http\Resources\Json\JsonResource;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Register any application services.12 */13 public function register(): void14 {15 // ...16 }1718 /**19 * Bootstrap any application services.20 */21 public function boot(): void22 {23 JsonResource::withoutWrapping();24 }25}
withoutWrapping
方法只會影響最外層的回應。withoutWrapping
方法不會移除手動新增到 Resource Collection 內的 data
索引鍵。
包裝巢狀 Resource
對於要如何包裝 Resource 的關聯,開發人員擁有絕對的自由。若想讓所有無論是不是巢狀的 Resource Collection 都被包裝在 data
索引鍵內,則可以為每個 Resource 都定義一個 Resource Collection 類別,並以 data
索引鍵回傳 Collection。
讀者可能會疑惑:這麼做會不會讓最外層的 Resource 被包裝在 data
索引鍵裡兩次?別擔心,Laravel 不會讓你不小心把 Resource 重複包裝的。因此,在轉換 Resource Collection 時,完全不需擔心 Resource Collection 的巢狀層級:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class CommentsCollection extends ResourceCollection8{9 /**10 * Transform the resource collection into an array.11 *12 * @return array<string, mixed>13 */14 public function toArray(Request $request): array15 {16 return ['data' => $this->collection];17 }18}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class CommentsCollection extends ResourceCollection8{9 /**10 * Transform the resource collection into an array.11 *12 * @return array<string, mixed>13 */14 public function toArray(Request $request): array15 {16 return ['data' => $this->collection];17 }18}
Data Wrapping and Pagination
當使用 Resource 回應來回船分頁過的 Collection 時,就算有呼叫過 withoutWrapper
方法,Laravel 也會將這些 Resource 資料放在 data
索引鍵裡。這是因為,所有經過分頁的回應都會包含如 meta
與 links
等有關 Paginator 狀態的資訊:
1{2 "data": [3 {4 "id": 1,5 "name": "Eladio Schroeder Sr.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",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": null19 },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": 1028 }29}
1{2 "data": [3 {4 "id": 1,5 "name": "Eladio Schroeder Sr.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",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": null19 },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": 1028 }29}
分頁
可以將 Laravel 的 Paginator 實體傳入 Resource 的 collection
方法或自訂 Resource Collection 中:
1use App\Http\Resources\UserCollection;2use App\Models\User;34Route::get('/users', function () {5 return new UserCollection(User::paginate());6});
1use App\Http\Resources\UserCollection;2use App\Models\User;34Route::get('/users', function () {5 return new UserCollection(User::paginate());6});
所有經過分頁的回應都會包含 meta
與 links
等關於 Paginator 狀態的資訊:
1{2 "data": [3 {4 "id": 1,5 "name": "Eladio Schroeder Sr.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",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": null19 },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": 1028 }29}
1{2 "data": [3 {4 "id": 1,5 "name": "Eladio Schroeder Sr.",7 },8 {9 "id": 2,10 "name": "Liliana Mayert",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": null19 },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": 1028 }29}
Customizing the Pagination Information
若想自定分頁 Response 中 links
或 meta
索引鍵內所包含的資訊,可在 Resource 上定義 paginationInformation
方法。該方法會收到一個 $paginated
資料,以及一個陣列的 $default
資訊。$default
是一個包含 links
與 meta
索引鍵的陣列:
1/**2 * Customize the pagination information for the resource.3 *4 * @param \Illuminate\Http\Request $request5 * @param array $paginated6 * @param array $default7 * @return array8 */9public function paginationInformation($request, $paginated, $default)10{11 $default['links']['custom'] = 'https://example.com';1213 return $default;14}
1/**2 * Customize the pagination information for the resource.3 *4 * @param \Illuminate\Http\Request $request5 * @param array $paginated6 * @param array $default7 * @return array8 */9public function paginationInformation($request, $paginated, $default)10{11 $default['links']['custom'] = 'https://example.com';1213 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): array7{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): array7{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): array7{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): array7{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 回應中移除。
mergeWhen
方法不可使用在組合使用數字與字串索引鍵的陣列上。此外,mergeWhen
也不能使用在數字索引鍵沒有連續的陣列上。
有條件的關聯
除了有條件地載入屬性外,我們還能依據關聯是否已載入到 Model 上來有條件地將關聯包含在 Resource 回應中。這樣一來,我們的 Controller 就能決定要載入哪些關聯,而 Resource 就可以輕鬆地在有載入這些關聯的時候才將這些關聯包含在回應中。最後,這麼做就能輕鬆地在 Resource 內避免「N+1」查詢。
可以使用 whenLoaded
方法來有條件地載入關聯。為了避免不必要地載入關聯,這個方法接受關聯的名稱,而非關聯物件本身:
1use App\Http\Resources\PostResource;23/**4 * Transform the resource into an array.5 *6 * @return array<string, mixed>7 */8public function toArray(Request $request): array9{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;23/**4 * Transform the resource into an array.5 *6 * @return array<string, mixed>7 */8public function toArray(Request $request): array9{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): array7{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): array7{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
, min與
max也可以使用
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): array7{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): array7{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): array7{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): array7{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): array7{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): array7{8 return [9 'data' => $this->collection,10 'links' => [11 'self' => 'link-value',12 ],13 ];14}
當從 Resource 內回傳額外的詮釋資料時,不需擔心是否會不小心複寫在回傳經過分頁的資料時 Laravel 自動新增的 links
或 meta
等資料。若有定義額外的 links
,這些 links
會跟 Paginator 提供的連結合併在一起。
最上層的詮釋資料
有時候,我們可能會想只在當目前 Resource 是回傳的最外層 Resource 時才包含某些詮釋資料。一般來說,這種情況的詮釋資料就是對於回應的詮釋資料。若要定義這種詮釋資料,可在 Resource 類別內加上一個 with
方法。這個方法應回傳一組包含詮釋資料的陣列,用以在目前 Resource 是最外層 Resource 時包含在 Resource 回應內:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class UserCollection extends ResourceCollection8{9 /**10 * Transform the resource collection into an array.11 *12 * @return array<string, mixed>13 */14 public function toArray(Request $request): array15 {16 return parent::toArray($request);17 }1819 /**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): array25 {26 return [27 'meta' => [28 'key' => 'value',29 ],30 ];31 }32}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\Resources\Json\ResourceCollection;67class UserCollection extends ResourceCollection8{9 /**10 * Transform the resource collection into an array.11 *12 * @return array<string, mixed>13 */14 public function toArray(Request $request): array15 {16 return parent::toArray($request);17 }1819 /**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): array25 {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;34Route::get('/user/{id}', function (string $id) {5 return new UserResource(User::findOrFail($id));6});
1use App\Http\Resources\UserResource;2use App\Models\User;34Route::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;34Route::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;34Route::get('/user', function () {5 return (new UserResource(User::find(1)))6 ->response()7 ->header('X-Value', 'True');8});
或者,也可以在 Resource 裡面定義一個 withResponse
。這個方法會在該 Resource 是回應中最外層 Resource 時被呼叫:
1<?php23namespace App\Http\Resources;45use Illuminate\Http\JsonResponse;6use Illuminate\Http\Request;7use Illuminate\Http\Resources\Json\JsonResource;89class UserResource extends JsonResource10{11 /**12 * Transform the resource into an array.13 *14 * @return array<string, mixed>15 */16 public function toArray(Request $request): array17 {18 return [19 'id' => $this->id,20 ];21 }2223 /**24 * Customize the outgoing response for the resource.25 */26 public function withResponse(Request $request, JsonResponse $response): void27 {28 $response->header('X-Value', 'True');29 }30}
1<?php23namespace App\Http\Resources;45use Illuminate\Http\JsonResponse;6use Illuminate\Http\Request;7use Illuminate\Http\Resources\Json\JsonResource;89class UserResource extends JsonResource10{11 /**12 * Transform the resource into an array.13 *14 * @return array<string, mixed>15 */16 public function toArray(Request $request): array17 {18 return [19 'id' => $this->id,20 ];21 }2223 /**24 * Customize the outgoing response for the resource.25 */26 public function withResponse(Request $request, JsonResponse $response): void27 {28 $response->header('X-Value', 'True');29 }30}