# Eloquent: API 資源
- [簡介](#introduction)
- [生成資源](#generating-resources)
- [概念綜述](#concept-overview)
- [編寫資源](#writing-resources)
- [數據包裹](#data-wrapping)
- [分頁](#pagination)
- [條件屬性](#conditional-attributes)
- [條件關聯](#conditional-relationships)
- [添加元數據](#adding-meta-data)
- [資源響應](#resource-responses)
<a name="introduction"></a>
## 簡介
當構建 API 時,你往往需要一個轉換層來聯結你的 Eloquent 模型和實際返回給用戶的 JSON 響應。 Laravel 的資源類能夠讓你以更直觀簡便的方式將模型和模型集合轉化成 JSON 。
<a name="generating-resources"></a>
## 生成資源
你可以使用 `make:resource` Artisan 命令來生成資源類。默認情況下生成的資源都會被放置在框架 `app/Http/Resources` 文件夾下。資源繼承 `Illuminate\Http\Resources\Json\Resource` 類:
php artisan make:resource User
#### 資源集合
除了生成資源轉換單個模型外,你還可以生成資源集合用來轉換模型的集合。這允許你在響應中包含與給定資源相關的鏈接與其他元信息。
你需要在生成資源時添加 `--collection` 標志以生成一個資源集合。你也可以直接在資源的名稱中包含 `Collection` 向 Laravel 表示應該生成一個資源集合。資源集合繼承 `Illuminate\Http\Resources\Json\ResourceCollection` 類:
php artisan make:resource Users --collection
php artisan make:resource UserCollection
<a name="concept-overview"></a>
## 概念綜述
> {tip} 這是對資源和資源集合的概述。強烈建議你閱讀本文檔的其他部分,以深入了解如何更好地自定義和使用資源。
在深入了解如何定制化編寫你的資源之前,讓我們先來看看在 Laravel 中如何使用資源。一個資源類表示一個單一模型需要被轉換成 JSON 格式。例如,現在我們有一個簡單的 `User` 資源類:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class User extends Resource
{
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
每一個資源類都定義了一個 `toArray` 方法,在發送響應時它會返回應該被轉化成 JSON 的屬性數組。注意在這里我們可以直接使用 `$this` 變量來訪問模型屬性。這是因為資源類將自動代理屬性和方法到底層模型以方便訪問。你可以在路由或控制器中返回已定義的資源:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
### 資源集合
你可以在路由或控制器中使用 `collection` 方法來創建資源實例,以返回多個資源的集合或分頁響應:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
使用如上方法,你將不能添加任何附加的元數據和集合一起返回。如果你需要自定義資源集合響應,你需要創建一個資源集合來返回多個模型的集合:
php artisan make:resource UserCollection
你可以輕松的在已生成的資源集合類中定義任何你想在響應中返回的元數據:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* 將資源集合轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
你可以在路由或控制器中返回已定義的資源集合:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
<a name="writing-resources"></a>
## 編寫資源
> {tip} 如果你還沒有閱讀過 [概念綜述](#concept-overview) , 那么強烈建議你在繼續使用本文檔前閱讀這個部分。
從本質上來說,資源的作用很簡單。它們只需要將一個給定的模型轉換成一個數組。所以每一個資源都包含一個 `toArray` 方法用來將你的模型屬性轉換成可以返回給用戶的 API 友好的數組:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class User extends Resource
{
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
你可以在路由或控制器中返回已定義的資源:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
#### 關聯
如果你希望在響應中包含關聯資源,你只需要簡單的將它們添加到 `toArray` 方法返回的數組中。在下面這個例子里,我們將使用 `Post` 資源的 `collection` 方法將用戶的文章添加到資源響應中:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => Post::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
> {tip} 如果你只想在關聯已經加載時添加關聯資源,請查看文檔 [條件關聯](#conditional-relationships) 。
#### 資源集合
資源是將單個模型轉換成數組,而資源集合是將多個模型的集合轉換成數組。所有的資源都提供了一個 `collection` 方法來生成一個 「臨時」 資源集合,所以你沒有必要為每一個模型類型都編寫一個資源集合類:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
如果你需要自定義返回集合的元數據,則需要定義一個資源集合:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* 將資源集合轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
和單個資源一樣,你可以在路由或控制器中直接返回資源集合:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
<a name="data-wrapping"></a>
### 數據包裹
默認情況下,當資源響應被轉換成 JSON 時,頂層資源將會被包裹在 `data` 鍵中。因此一個典型的資源集合響應如下所示:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
]
}
你可以使用資源基類的 `withoutWrapping` 方法來禁用頂層資源的包裹。你應該在 `AppServiceProvider` 或其他在程序每一個請求中都會被加載的 [服務提供者](/docs/{{version}}/providers) 中調用此方法:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\Resource;
class AppServiceProvider extends ServiceProvider
{
/**
* 在注冊后進行服務的啟動。
*
* @return void
*/
public function boot()
{
Resource::withoutWrapping();
}
/**
* 在容器中注冊綁定。
*
* @return void
*/
public function register()
{
//
}
}
> {note} `withoutWrapping` 方法只會禁用頂層資源的包裹,不會刪除你手動添加到資源集合中的 `data` 鍵。
### 包裹嵌套資源
你有絕對的自由決定你的資源關聯如何被包裹。如果你希望無論怎樣嵌套,都將所有資源集合包裹在 `data` 鍵中,那么你需要為每個資源都定義一個資源集合類,并將返回的集合包裹在 `data` 鍵中。
當然,你可能會擔心這樣頂層資源將會被包裹在兩個 `data` 鍵中。請放心, Laravel 將永遠不會讓你的資源被雙層包裹,因此你不必擔心被轉換的資源集合會被多重嵌套:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CommentsCollection extends ResourceCollection
{
/**
* 將資源集合轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return ['data' => $this->collection];
}
}
### 數據包裹和分頁
當在資源響應中返回分頁集合時,即使你調用了 `withoutWrapping` 方法, Laravel 也會將你的資源數據包裹在 `data` 鍵中。這是因為分頁響應中總會有 `meta` 和 `links` 鍵包含著分頁狀態信息:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
<a name="pagination"></a>
### 分頁
你可以將分頁實例傳遞給資源的 `collection` 方法或者自定義的資源集合:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
分頁響應中總有 `meta` 和 `links` 鍵包含著分頁狀態信息:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
<a name="conditional-attributes"></a>
### 條件屬性
有些時候,你可能希望在給定條件滿足時添加屬性到資源響應里。例如,你可能希望如果當前用戶是 「管理員」 時添加某個值到資源響應中。在這種情況下 Laravel 提供了一些輔助方法來幫助你解決問題。 `when` 方法可以被用來有條件地向資源響應添加屬性:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($this->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
在上面這個例子中,只有當 `$this->isAdmin()` 方法返回 `true` 時, `secret` 鍵才會最終在資源響應中被返回。如果該方法返回 `false` ,則 `secret` 鍵將會在資源響應被發送給客戶端之前被刪除。 `when` 方法可以使你避免使用條件語句拼接數組,轉而用更優雅的方式來編寫你的資源。
`when` 方法也接受閉包作為其第二個參數,只有在給定條件為 `true` 時,才從閉包中計算返回的值:
'secret' => $this->when($this->isAdmin(), function () {
return 'secret-value';
}),
> {tip} 記住,你在資源上調用的方法將被代理到底層模型實例。所以在這種情況下,你調用的 `isAdmin` 方法實際上是調用最初傳遞給資源的 Eloquent 模型上的方法。
#### 有條件地合并數據
有些時候,你可能希望在給定條件滿足時添加多個屬性到資源響應里。在這種情況下,你可以使用 `mergeWhen` 方法在給定的條件為 `true` 時將多個屬性添加到響應中:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen($this->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
同理,如果給定的條件為 `false` 時,則這些屬性將會在資源響應被發送給客戶端之前被刪除。
> {note} 在混合字符串和數字鍵的數組中你不應該使用 `mergeWhen` 方法。此外,它不應該在不按順序排列的數字鍵的數組中被使用。
<a name="conditional-relationships"></a>
### 條件關聯
除了有條件地添加屬性之外,你還可以根據模型關聯是否已加載來有條件的在你的資源響應中包含關聯。這允許你在控制器中決定加載哪些模型關聯,這樣你的資源可以在模型關聯被加載后才添加它們。
這樣做可以避免在你的資源中出現 「N+1」 查詢問題。你應該使用 `whenLoaded` 方法來有條件的加載關聯。為了避免加載不必要的關聯,此方法接受關聯的名稱而不是關聯本身作為其參數:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => Post::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
在上面這個例子中,如果關聯沒有被加載,則 `posts` 鍵將會在資源響應被發送給客戶端之前被刪除。
#### 條件中間表信息
除了在你的資源響應中有條件地包含關聯外,你還可以使用 `whenPivotLoaded` 方法有條件地從多對多關聯的中間表中添加數據。 `whenPivotLoaded` 方法第一個參數為中間表的名稱。第二個參數應為當模型中間表信息可用時返回要添加數據的閉包:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_users', function () {
return $this->pivot->expires_at;
}),
];
}
<a name="adding-meta-data"></a>
### 添加元數據
一些 JSON API 標準需要你在資源和資源集合響應中添加元數據。這通常包括資源或相關資源的 `links` ,或一些關于資源本身的元數據。如果你需要返回有關資源的其他元數據,只需要將它們包含在 `toArray` 方法中即可。例如在轉換資源集合時你可能需要添加 `links` 信息:
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
當添加額外元數據到你的資源中時,你不必擔心會覆蓋 Laravel 在返回分頁響應時自動添加的 `links` 或 `meta` 鍵。你添加的任何其他 `links` 會與分頁響應添加的 `links` 相合并。
#### 頂層元數據
有時候你可能希望當資源被作為頂層資源返回時添加某些元數據到資源響應中。這通常包括整個響應的元信息。你可以在資源類中添加 `with` 方法來定義元數據。此方法應返回一個元數據數組,當資源被作為頂層資源渲染時,這個數組將會被包含在資源響應中:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* 將資源集合轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
/**
* 返回應該和資源一起返回的其他數據數組。
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
#### 構造資源時添加元數據
你還可以在路由或控制器中構造資源實例時添加頂層數據。所有資源都可以使用 `additional` 方法來接受應該被添加到資源響應中的數據數組:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
<a name="resource-responses"></a>
## 資源響應
就像你已經知道那樣,資源可以直接在路由和控制器中被返回:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
但是有些時候你可能需要自定義發送給客戶端的 HTTP 響應。你有兩種選擇。第一,你可以在資源上鏈式調用 `response` 方法。此方法將返回 `Illuminate\Http\Response` 實例,允許你自定義響應頭信息:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return (new UserResource(User::find(1)))
->response()
->header('X-Value', 'True');
});
或者你也可以在資源中定義一個 `withResponse` 方法。此方法將會在資源被作為頂層資源在響應中返回時被調用:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class User extends Resource
{
/**
* 將資源轉換成數組。
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
];
}
/**
* 自定義資源響應。
*
* @param \Illuminate\Http\Request
* @param \Illuminate\Http\Response
* @return void
*/
public function withResponse($request, $response)
{
$response->header('X-Value', 'True');
}
}
## 譯者署名
| 用戶名 | 頭像 | 職能 | 簽名 |
|---|---|---|---|
| [@young](https://laravel-china.org/users/4236) | <img class="avatar-66 rm-style" src="https://dn-phphub.qbox.me/uploads/avatars/4236_1461228940.png?imageView2/1/w/200/h/200"> | 翻譯 | 18屆應屆生 [求工作](https://xiayang.me/hire-me),Laravel、PHP、web后端開發方向 |
---
> {note} 歡迎任何形式的轉載,但請務必注明出處,尊重他人勞動共創開源社區。
>
> 轉載請注明:本文檔由 Laravel China 社區 [laravel-china.org](https://laravel-china.org) 組織翻譯,詳見 [翻譯召集帖](https://laravel-china.org/topics/5756/laravel-55-document-translation-call-come-and-join-the-translation)。
>
> 文檔永久地址: https://d.laravel-china.org
- 說明
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- HomeStead
- Valet
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- 門面(Facades)
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 重定向
- Session
- 表單驗證
- 錯誤與日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- 用戶認證
- API認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Passport OAuth 認證
- Scout 全文搜索
- Socialite 社交化登錄
- 交流說明