はじめに
APIを構築する際、Eloquentモデルと実際にアプリケーションのユーザーに返されるJSONレスポンスの間に変換レイヤーが必要になることがあります。たとえば、特定のユーザーのサブセットに対して特定の属性を表示したり、常に特定のリレーションシップをモデルのJSON表現に含めたりすることができます。Eloquentのリソースクラスを使用すると、モデルやモデルコレクションをJSONに表現豊かに簡単に変換できます。
もちろん、EloquentモデルやコレクションをtoJson
メソッドを使用してJSONに変換することもできますが、EloquentリソースはモデルとそのリレーションシップのJSONシリアル化に対してより詳細で堅牢な制御を提供します。
リソースの生成
リソースクラスを生成するには、make:resource
Artisanコマンドを使用できます。デフォルトでは、リソースはアプリケーションのapp/Http/Resources
ディレクトリに配置されます。リソースはIlluminate\Http\Resources\Json\JsonResource
クラスを拡張します:
php artisan make:resource UserResource
リソースコレクション
個々のモデルを変換するリソースを生成するだけでなく、モデルのコレクションを変換する責任を持つリソースを生成することもできます。これにより、JSONレスポンスに特定のリソースの全体のコレクションに関連するリンクやその他のメタ情報を含めることができます。
リソースコレクションを作成するには、リソースを作成する際に--collection
フラグを使用する必要があります。また、リソース名にCollection
という単語を含めることで、Laravelにコレクションリソースを作成するよう指示します。コレクションリソースはIlluminate\Http\Resources\Json\ResourceCollection
クラスを拡張します:
php artisan make:resource User --collection
php artisan make:resource UserCollection
概念の概要
これはリソースとリソースコレクションの高レベルの概要です。リソースによって提供されるカスタマイズと力を深く理解するために、このドキュメントの他のセクションを読むことを強くお勧めします。
リソースを書く際に利用可能なすべてのオプションに飛び込む前に、まずLaravel内でリソースがどのように使用されるかを高レベルで見てみましょう。リソースクラスは、JSON構造に変換する必要がある単一のモデルを表します。たとえば、ここにシンプルなUserResource
リソースクラスがあります:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
すべてのリソースクラスは、リソースがルートまたはコントローラーメソッドからレスポンスとして返されるときにJSONに変換される属性の配列を返すtoArray
メソッドを定義します。
モデルのプロパティには$this
変数から直接アクセスできます。これは、リソースクラスが便利なアクセスのために、基盤となるモデルへのプロパティとメソッドのアクセスを自動的にプロキシするためです。リソースが定義されると、それはルートまたはコントローラーから返されることができます。リソースは、そのコンストラクタを介して基盤となるモデルインスタンスを受け取ります:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
リソースコレクション
リソースのコレクションを返す場合やページネートされたレスポンスを返す場合、ルートまたはコントローラーでリソースインスタンスを作成する際に、リソースクラスが提供するcollection
メソッドを使用する必要があります:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
これは、コレクションと共に返される必要があるカスタムメタデータの追加を許可しません。リソースコレクションレスポンスをカスタマイズしたい場合は、コレクションを表す専用のリソースを作成できます:
php artisan make:resource UserCollection
リソースコレクションクラスが生成されたら、レスポンスに含めるべきメタデータを簡単に定義できます:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
リソースコレクションを定義した後、それはルートまたはコントローラーから返されることができます:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
コレクションキーの保持
ルートからリソースコレクションを返すと、Laravelはコレクションのキーをリセットして数値順にします。ただし、コレクションの元のキーを保持するかどうかを示すpreserveKeys
プロパティをリソースクラスに追加できます:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Indicates if the resource's collection keys should be preserved.
*
* @var bool
*/
public $preserveKeys = true;
}
``````php
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all()->keyBy->id);
});
`
基盤となるリソースクラスのカスタマイズ
通常、リソースコレクションの$this->collection
プロパティは、コレクションの各アイテムをその単数リソースクラスにマッピングした結果で自動的に埋められます。単数リソースクラスは、クラス名の末尾のCollection
部分を除いたコレクションのクラス名と見なされます。さらに、個人の好みに応じて、単数リソースクラスはResource
でサフィックスされる場合とされない場合があります。
たとえば、UserCollection
は、指定されたユーザーインスタンスをUserResource
リソースにマッピングしようとします。この動作をカスタマイズするには、リソースコレクションの$collects
プロパティをオーバーライドできます:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* @var string
*/
public $collects = Member::class;
}
リソースの作成
もしまだ概念の概要を読んでいない場合は、このドキュメントを進める前に読むことを強くお勧めします。
リソースは、特定のモデルを配列に変換する必要があるだけです。したがって、各リソースには、モデルの属性をAPIフレンドリーな配列に変換するtoArray
メソッドが含まれています。この配列は、アプリケーションのルートやコントローラーから返されることができます:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
リソースが定義されると、それはルートまたはコントローラーから直接返されることができます:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
リレーションシップ
関連リソースをレスポンスに含めたい場合は、リソースのtoArray
メソッドによって返される配列に追加できます。この例では、PostResource
リソースのcollection
メソッドを使用して、ユーザーのブログ投稿をリソースレスポンスに追加します:
use App\Http\Resources\PostResource;
use Illuminate\Http\Request;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
リレーションシップがすでにロードされている場合にのみリレーションシップを含めたい場合は、条件付きリレーションシップに関するドキュメントを確認してください。
リソースコレクション
リソースは単一のモデルを配列に変換しますが、リソースコレクションはモデルのコレクションを配列に変換します。ただし、すべてのリソースがcollection
メソッドを提供しているため、各モデルに対してリソースコレクションクラスを定義する必要はありません。このメソッドは、即席のリソースコレクションをその場で生成します:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
ただし、コレクションと共に返されるメタデータをカスタマイズする必要がある場合は、自分のリソースコレクションを定義する必要があります:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
単数リソースと同様に、リソースコレクションはルートやコントローラーから直接返されることができます:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
データラッピング
デフォルトでは、リソースレスポンスがJSONに変換されると、最外層のリソースはdata
キーでラップされます。たとえば、典型的なリソースコレクションレスポンスは次のようになります:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": ""
},
{
"id": 2,
"name": "Liliana Mayert",
"email": ""
}
]
}
最外層のリソースのラッピングを無効にしたい場合は、基盤となるIlluminate\Http\Resources\Json\JsonResource
クラスのwithoutWrapping
メソッドを呼び出す必要があります。通常、このメソッドは、アプリケーションへのすべてのリクエストで読み込まれるAppServiceProvider
または他のサービスプロバイダーから呼び出す必要があります:
<?php
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
JsonResource::withoutWrapping();
}
}
<a name="wrapping-nested-resources"></a>
#### ネストされたリソースのラッピング
リソースのリレーションシップがどのようにラップされるかを決定する自由があります。すべてのリソースコレクションをネストに関係なく`````data`````キーでラップしたい場合は、各リソースのリソースコレクションクラスを定義し、`````data`````キー内でコレクションを返す必要があります。
最外層のリソースが2つの`````data`````キーでラップされるかどうか疑問に思うかもしれません。心配しないでください、Laravelはリソースが誤って二重にラップされることを決して許可しないので、変換しているリソースコレクションのネストレベルについて心配する必要はありません:
``````php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CommentsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return ['data' => $this->collection];
}
}
`
データラッピングとページネーション
ページネートされたコレクションをリソースレスポンスで返す場合、Laravelはリソースデータをdata
キーでラップします。withoutWrapping
メソッドが呼び出されていてもです。これは、ページネートされたレスポンスには常にmeta
およびlinks
キーが含まれており、ページネーターの状態に関する情報が含まれているためです:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": ""
},
{
"id": 2,
"name": "Liliana Mayert",
"email": ""
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}
ページネーション
Laravelのページネーターインスタンスをリソースのcollection
メソッドまたはカスタムリソースコレクションに渡すことができます:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
ページネートされたレスポンスには常にmeta
およびlinks
キーが含まれており、ページネーターの状態に関する情報が含まれています:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": ""
},
{
"id": 2,
"name": "Liliana Mayert",
"email": ""
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}
ページネーション情報のカスタマイズ
ページネーションレスポンスのlinks
またはmeta
キーに含まれる情報をカスタマイズしたい場合は、リソースにpaginationInformation
メソッドを定義できます。このメソッドは、$paginated
データと、links
およびmeta
キーを含む配列である$default
情報の配列を受け取ります:
/**
* Customize the pagination information for the resource.
*
* @param \Illuminate\Http\Request $request
* @param array $paginated
* @param array $default
* @return array
*/
public function paginationInformation($request, $paginated, $default)
{
$default['links']['custom'] = 'https://example.com';
return $default;
}
条件付き属性
時には、特定の条件が満たされた場合にのみリソースレスポンスに属性を含めたい場合があります。たとえば、現在のユーザーが「管理者」である場合にのみ値を含めたい場合があります。Laravelは、この状況を支援するためにさまざまなヘルパーメソッドを提供しています。when
メソッドを使用して、リソースレスポンスに条件付きで属性を追加できます:
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
この例では、secret
キーは、認証されたユーザーのisAdmin
メソッドがtrue
を返す場合にのみ最終的なリソースレスポンスに返されます。メソッドがfalse
を返す場合、secret
キーはクライアントに送信される前にリソースレスポンスから削除されます。when
メソッドを使用すると、配列を構築する際に条件文に頼ることなく、リソースを表現豊かに定義できます。
``````php
'secret' => $this->when($request->user()->isAdmin(), function () {
return 'secret-value';
}),
`
``````php
'name' => $this->whenHas('name'),
`
さらに、whenNotNull
メソッドは、属性がnullでない場合にリソースレスポンスに属性を含めるために使用できます:
'name' => $this->whenNotNull($this->name),
条件付き属性のマージ
時には、同じ条件に基づいてリソースレスポンスに含めるべきいくつかの属性がある場合があります。この場合、mergeWhen
メソッドを使用して、与えられた条件がtrue
である場合にのみレスポンスに属性を含めることができます:
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen($request->user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
再度、与えられた条件がfalse
である場合、これらの属性はクライアントに送信される前にリソースレスポンスから削除されます。
<a name="conditional-relationships"></a>
### 条件付きリレーションシップ
属性を条件付きでロードすることに加えて、モデルにリレーションシップがすでにロードされているかどうかに基づいてリソースレスポンスにリレーションシップを条件付きで含めることができます。これにより、コントローラーがモデルにどのリレーションシップをロードするかを決定し、リソースは実際にロードされた場合にのみそれらを簡単に含めることができます。最終的に、これによりリソース内で「N+1」クエリの問題を回避しやすくなります。
`````whenLoaded`````メソッドを使用して、リレーションシップを条件付きでロードできます。不要にリレーションシップをロードしないように、このメソッドはリレーションシップ自体ではなく、リレーションシップの名前を受け取ります:
``````php
use App\Http\Resources\PostResource;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
`
この例では、リレーションシップがロードされていない場合、posts
キーはクライアントに送信される前にリソースレスポンスから削除されます。
条件付きリレーションシップカウント
リレーションシップを条件付きで含めることに加えて、リレーションシップのカウントがモデルにロードされているかどうかに基づいて、リソースレスポンスにリレーションシップの「カウント」を条件付きで含めることができます:
new UserResource($user->loadCount('posts'));
``````php
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts_count' => $this->whenCounted('posts'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
`
この例では、posts
リレーションシップのカウントがロードされていない場合、posts_count
キーはクライアントに送信される前にリソースレスポンスから削除されます。
``````php
'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),
`
条件付きピボット情報
リソースレスポンスにリレーションシップ情報を条件付きで含めることに加えて、多対多リレーションシップの中間テーブルからのデータをwhenPivotLoaded
メソッドを使用して条件付きで含めることができます。whenPivotLoaded
メソッドは、最初の引数としてピボットテーブルの名前を受け取ります。第二引数は、ピボット情報がモデルに存在する場合に返される値を返すクロージャである必要があります:
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}
リレーションシップがカスタム中間テーブルモデルを使用している場合、whenPivotLoaded
メソッドの最初の引数として中間テーブルモデルのインスタンスを渡すことができます:
'expires_at' => $this->whenPivotLoaded(new Membership, function () {
return $this->pivot->expires_at;
}),
中間テーブルがpivot
以外のアクセサを使用している場合、whenPivotLoadedAs
メソッドを使用できます:
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
return $this->subscription->expires_at;
}),
];
}
メタデータの追加
一部のJSON API標準では、リソースおよびリソースコレクションレスポンスにメタデータを追加することが要求されます。これには、リソースまたは関連リソースへのlinks
のようなものや、リソース自体に関するメタデータが含まれることがよくあります。リソースに関する追加のメタデータを返す必要がある場合は、toArray
メソッドに含めてください。たとえば、リソースコレクションを変換する際にlinks
情報を含めることができます:
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
リソースから追加のメタデータを返す際、ページネートされたレスポンスを返す際にLaravelによって自動的に追加されるlinks
またはmeta
キーを誤って上書きすることを心配する必要はありません。定義した追加のlinks
は、ページネーターによって提供されるリンクとマージされます。
トップレベルメタデータ
時には、リソースが返される最外層のリソースである場合にのみ、リソースレスポンスに特定のメタデータを含めたい場合があります。通常、これはレスポンス全体に関するメタ情報を含みます。このメタデータを定義するには、リソースクラスにwith
メソッドを追加します。このメソッドは、リソースが変換される最外層のリソースである場合にのみ、リソースレスポンスに含めるメタデータの配列を返す必要があります:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
/**
* Get additional data that should be returned with the resource array.
*
* @return array<string, mixed>
*/
public function with(Request $request): array
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
リソース構築時のメタデータの追加
ルートやコントローラーでリソースインスタンスを構築する際に、トップレベルのデータを追加することもできます。すべてのリソースで利用可能なadditional
メソッドは、リソースレスポンスに追加されるべきデータの配列を受け取ります:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
リソースレスポンス
すでに読んだように、リソースはルートやコントローラーから直接返されることがあります:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
ただし、時にはクライアントに送信される前に送信されるHTTPレスポンスをカスタマイズする必要がある場合があります。これを達成する方法は2つあります。まず、リソースにresponse
メソッドをチェーンすることができます。このメソッドはIlluminate\Http\JsonResponse
インスタンスを返し、レスポンスのヘッダーを完全に制御できます:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user', function () {
return (new UserResource(User::find(1)))
->response()
->header('X-Value', 'True');
});
または、リソース自体内にwithResponse
メソッドを定義することができます。このメソッドは、リソースがレスポンスの最外層のリソースとして返されるときに呼び出されます:
<?php
namespace App\Http\Resources;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
];
}
/**
* Customize the outgoing response for the resource.
*/
public function withResponse(Request $request, JsonResponse $response): void
{
$response->header('X-Value', 'True');
}
}