このリポジトリはPHP Webアプリフレームワークである、LaravelのLTSバージョンである5.5の公式英文ドキュメントを日本語へ翻訳しています。
This project is maintained by okinaka
API構築時、Eloquentモデルと、アプリケーションユーザーに対して実際に返信するJSONリスポンスとの間に、トランスレーション層を設置することが必要となります。Laravelのリソースクラスは、モデルやモデルコレクションを記述しやすく簡単に、JSONへと変換してくれます。
リソースクラスを生成するには、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
{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,
];
}
}
レスポンスを送り返す時に、JSONへ変換する必要のある属性の配列を返す、toArray
メソッドを全リソースクラスで定義します。$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());
});
{tip} 概略をまだ読んでいないのなら、ドキュメントを読み進める前に目を通しておくことを強くおすすめします。
リソースの本質はシンプルです。特定のモデルを配列に変換する必要があるだけです。そのため、APIフレンドリーな配列としてユーザーへ送り返せるように、モデルの属性を変換するためのtoArray
メソッドをリソースは持っています。
<?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} 既にロードされている場合のみ、リレーションを含めたい場合は、条件付きリレーションのドキュメントを参照してください。
リソースは一つのモデルを配列へ変換するのに対し、コレクションリソースはモデルのコレクションを配列へ変換します。モデルタイプそれぞれに対し、コレクションリソースを絶対に定義する必要があるわけではありません。すべてのリソースは、簡単に「アドホック」なコレクションリソースを生成するために、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',
],
];
}
}
1モデルを扱うリソースと同様にコレクションリソースも、ルートやコントローラから直接返してください。
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
デフォルトではリソースレスポンスがJSONに変換されるとき、一番外側のリソースをdata
キー下にラップします。たとえば、典型的なコレクションリソースのレスポンスは、次のようになるでしょう。
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
]
}
一番外部のリソースでラップしないようにしたい場合は、ベースのリソースクラスに対し、withoutWrapping
メソッドを使用してください。通常、このメソッドはアプリケーションに対するリクエストごとにロードされる、AppServiceProvider
か、もしくは他のサービスプロバイダから呼び出します。
<?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
}
}
常にペジネータインスタンスをリソースの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
}
}
条件が一致する場合のみ、リソースレスポンスへ属性を含めたいこともあります。たとえば、現在のユーザーが”administrator”の場合のみ、ある値を含めたいときです。こうした状況で役に立つ様々なヘルパメソッドを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
メソッドは、第2引数にクロージャを引き受け、指定した条件がtrue
の場合のみ、結果の値を算出することもできます。
'secret' => $this->when($this->isAdmin(), function () {
return 'secret-value';
}),
{tip} リソースで呼び出されるメソッドは、元のモデルインスタンスへの呼び出しを仲介することを思い出してください。ですからこの場合、
isAdmin
メソッドはリソースを提供するオリジナルのEloquentモデルへと仲介されます。
リソースレスポンスへ同じ条件にもとづいて、多くの属性を含めたい場合もあります。この場合、指定した条件がtrue
の場合のみ、レスポンスへ属性を組み入れるmergeWhen
メソッドを使用します。
/**
* リソースを配列へ変換
*
* @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
メソッドは、文字列と数値のキーが混ざっている配列の中では、使用しないでください。さらに、順番に並んでいない数値キーの配列でも、使用しないでください。
条件によりロードする属性に付け加え、リレーションがモデルにロードされているかに基づいて、リソースレスポンスへリレーションを条件付きで含めることもできます。これにより、どのリレーションをモデルにロードさせるかをコントローラで決め、リソースでは実際にロード済みの場合のみ、レスポンスへ含めることが簡単に実現できます。
この機能により最終的に、リソースでの「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
メソッドは、第1引数に中間テーブルの名前を引き受けます。第2引数には、ピボット情報がそのモデルに対し利用可能な場合の返却値を定義するクロージャを指定します。
/**
* リソースを配列へ変換
*
* @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;
}),
];
}
いくつかのJSON API規約では、リソースとコレクションリソースレスポンスで、追加のメタデータを要求しています。これらには、リソースへのlink
のような情報や、関連するリソース、リソース自体のメタデータなどがよく含まれます。リソースに関する追加のメタデータを返す必要がある場合は、toArray
メソッドに含めるだけです。たとえば、コレクションリソースを変換する時に、link
情報を含めるには次のようにします。
/**
* リソースを配列へ変換
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
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
{
/**
* コレクションリソースを配列へ変換
*
* @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',
]]);
既に説明したように、リソースはルートかコントローラから直接返されます。
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');
}
}