(再翻訳中)Laravel 8.x Eloquent:APIリソース

イントロダクション

APIを構築する場合、Eloquentモデルと実際にアプリケーションのユーザーへ返すJSONレスポンスの間に変換レイヤーが必要になるでしょう。たとえば、ユーザーのサブセットに対して特定の属性を表示し、他のユーザーには表示したくない場合や、モデルのJSON表現に常に特定のリレーションを含めたい場合などです。Eloquentのリソースクラスを使用すると、モデルとモデルコレクションを表現力豊かかつ簡単にJSONに変換できます。

もちろん、toJsonメソッドを使用してEloquentモデルまたはコレクションをJSONへいつでも変換できます。ただし、Eloquentリソースは、モデルのJSONシリアル化とそれらの関係をよりきめ細かく堅牢に制御します。

リソースの生成

リソースクラスを生成するには、make:resource Artisanコマンドを使用します。リソースはアプリケーションのapp/Http/Resourcesディレクトリにデフォルトで配置されます。リソースはIlluminate\Http\Resources\Json\JsonResourceクラスを拡張します。

php artisan make:resource User

リソースコレクション

個々のモデルを変換するリソースを生成することに加えて、モデルのコレクションの変換を担当するリソースを生成することもできます。これにより、JSONレスポンスへ指定するリソースのコレクション全体へ関連するリンクやその他のメタ情報を含められます。

リソースコレクションを生成するには、リソースを生成するときに--collectionフラグを使用する必要があります。または、リソース名にCollectionという単語を含めると、コレクションリソースを作成する必要があるとLaravelに指示できます。コレクションリソースは、Illuminate\Http\Resources\JSON\ResourceCollectionクラスを拡張します。

php artisan make:resource User --collection

php artisan make:resource UserCollection

概論

Tip!! これは、リソースとリソースコレクションの概要です。リソースによって提供されるカスタマイズとパワーをより深く理解するために、このドキュメントの他のセクションも読むことを強く推奨します。

リソースを作成するときに利用できるすべてのオプションに飛び込む前に、まずLaravel内でリソースがどのように使用されているかを大まかに見てみましょう。リソースクラスは、JSON構造に変換する必要がある単一のモデルを表します。たとえば、以下は単純なUserリソースクラスです。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * リソースを配列に変換
     *
     * @param  \Illuminate\Http\Request  $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\Http\Resources\User as UserResource;
use App\Models\User;

Route::get('/user/{id}', function ($id) {
    return new UserResource(User::findOrFail($id));
});

リソースコレクション

リソースのコレクションまたはページ分割されたレスポンスを返す場合は、ルートまたはコントローラーでリソースインスタンスを作成するときに、リソースクラスによって提供されるcollectionメソッドを使用する必要があります。

use App\Http\Resources\User as 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\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変換
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        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 User extends JsonResource
{
    /**
     * リソースのコレクションキーを保持する必要がある事を示す
     *
     * @var bool
     */
    public $preserveKeys = true;
}

preserveKeysプロパティがtrueに設定されている場合、コレクションをルートまたはコントローラーから返すとき、コレクションのキーが保持されます。

use App\Http\Resources\User as UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all()->keyBy->id);
});

基礎となるリソースクラスのカスタマイズ

通常、リソースコレクションの$this->collectionプロパティへは、コレクションの各アイテムをその単一のリソースクラスにマッピングした結果を自動的に代入します。単一のリソースクラスは、クラス名の末尾からCollection部分除いたコレクションのクラス名であると想定します。

たとえば、UserCollectionは指定ユーザーインスタンスをUserリソースにマップしようとします。この動作をカスタマイズするには、リソースコレクションの$collectsプロパティをオーバーライドします。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * このリソースが収集するリソース
     *
     * @var string
     */
    public $collects = Member::class;
}

リソースの記述

Tip!! 概論を読んでいない場合は、このドキュメント読み進める前に一読することを強く推奨します。

本質的に、リソースは単純です。特定のモデルを配列に変換するだけで済みます。したがって、各リソースには、モデルの属性をアプリケーションのルートまたはコントローラーから返すことができるAPIフレンドリーな配列に変換するtoArrayメソッドが含まれています。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * リソースを配列に変換
     *
     * @param  \Illuminate\Http\Request  $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\Http\Resources\User as UserResource;
use App\Models\User;

Route::get('/user/{id}', function ($id) {
    return new UserResource(User::findOrFail($id));
});

リレーション

リレーションをレスポンスへ含めたい場合は、リソースのtoArrayメソッドから返す配列にそれらを追加できます。この例では、Postリソースのcollectionメソッドを使用して、ユーザーのブログ投稿をリソースレスポンスへ追加しています。

use App\Http\Resources\Post;

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $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\Http\Resources\User as UserResource;
use App\Models\User;

Route::get('/users', 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  $request
     * @return array
     */
    public function toArray($request)
    {
        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": "therese28@example.com",
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com",
        }
    ]
}

dataの代わりにカスタムキーを使用したい場合は、リソースクラスに$wrap属性を定義します。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * 適用する「データ」ラッパー
     *
     * @var string
     */
    public static $wrap = 'user';
}

最も外側のリソースのラッピングを無効にする場合は、ベースの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
{
    /**
     * 全アプリケーションサービスの登録
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * アプリケーションサービスの全初期起動処理
     *
     * @return void
     */
    public function boot()
    {
        JsonResource::withoutWrapping();
    }
}

Note: withoutWrappingメソッドは最も外側のレスポンスにのみ影響し、独自のリソースコレクションに手動で追加したdataキーは削除しません。

ネストされたリソースのラップ

リソースのリレーションをどのようにラップするか、決定する完全な自由が皆さんにあります。ネストに関係なくすべてのリソースコレクションをdataキーでラップする場合は、リソースごとにリソースコレクションクラスを定義し、dataキー内でコレクションを返す必要があります。

これにより、もっとも外側のリソースが2つのdataキーにラップされてしまうのか疑問に思われるかもしれません。心配ありません。Laravelが誤ってリソースを二重にラップすることは決してないので、変換するリソースコレクションのネストレベルについて心配する必要はありません。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変換
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return ['data' => $this->collection];
    }
}

データのラップとペジネーション

リソースレスポンスを介してページ付けされたコレクションを返す場合、LaravelはwithoutWrappingメソッドが呼び出された場合でも、リソースデータを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
    }
}

ペジネーション

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": "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メソッドを使用して、リソースレスポンスに属性を条件付きで追加できます。

use Illuminate\Support\Facades\Auth;

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

この例では、認証済みユーザーのisAdminメソッドがtrueを返した場合にのみ、最終的なリソースレスポンスでsecretキーが返されます。メソッドがfalseを返す場合、secretキーは、クライアントに送信される前にリソースレスポンスから削除されます。whenメソッドを使用すると、配列を作成するときに条件文に頼ることなく、リソースを表現的に定義できます。

whenメソッドは2番目の引数としてクロージャも受け入れ、指定する条件がtrueの場合にのみ結果の値を計算できるようにします。

'secret' => $this->when(Auth::user()->isAdmin(), function () {
    return 'secret-value';
}),

条件付き属性のマージ

同じ条件に基づいてリソースレスポンスにのみ含める必要のある属性が複数存在する場合があります。この場合、指定する条件がtrueの場合にのみ、mergeWhenメソッドを使用して属性をレスポンスへ含められます。

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        $this->mergeWhen(Auth::user()->isAdmin(), [
            'first-secret' => 'value',
            'second-secret' => 'value',
        ]),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

この場合も、指定する条件がfalseの場合、これらの属性は、クライアントに送信される前にリソースレスポンスから削除されます。

Note: mergeWhenメソッドは、文字列キーと数値キーが混在する配列内では使用しないでください。さらに、順番に並べられていない数値キーを持つ配列内では使用しないでください。

条件付きリレーション

条件付きで属性をロードすることに加え、リレーションがすでにモデルにロードされているかどうかの条件付きで、リソースレスポンスへリレーションを含めることもできます。これにより、コントローラーはモデルにロードする必要のあるリレーションを決定でき、リソースは実際にロードされた場合にのみ簡単に含めることができます。最終的に、これにより、リソース内の「N+1」クエリの問題を簡単に回避できます。

whenLoadedメソッドを使用して、リレーションを条件付きでロードできます。リレーションを不必要にロードすることを避けるために、このメソッドはリレーション自体ではなくリレーション名を引数に取ります。

use App\Http\Resources\Post;

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $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メソッドは、最初の引数にピボットテーブル名を取ります。2番目の引数は、モデルでピボット情報が利用可能な場合に返す値を返すクロージャである必要があります。

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
public function toArray($request)
{
    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メソッドを使用します。

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
            return $this->subscription->expires_at;
        }),
    ];
}

メタデータの追加

一部のJSON API基準では、リソースおよびリソースコレクションのレスポンスへメタデータを追加する必要があります。これには多くの場合、リソースまたは関連リソースへの「リンク(links)」や、リソース自体に関するメタデータなどが含まれます。リソースに関する追加のメタデータを返す必要がある場合は、それをtoArrayメソッドに含めます。たとえば、リソースコレクションを変換するときにlink情報を含めることができます。

/**
 * リソースを配列に変換
 *
 * @param  \Illuminate\Http\Request  $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  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }

    /**
     * Getリソース配列とともに返す必要のある追加データ
     *
     * @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\Http\Resources\User as UserResource;
use App\Models\User;

Route::get('/user/{id}', function ($id) {
    return new UserResource(User::findOrFail($id));
});

しかし、クライアントに送信する前に、送信HTTPレスポンスをカスタマイズする必要が起きる場合があります。これを実現するには2つの方法があります。最初の方法は、responseメソッドをリソースにチェーンすることです。このメソッドはIlluminate\Http\JsonResponseインスタンスを返し、皆さんがレスポンスのヘッダーを完全にコントロールできるようにします。

use App\Http\Resources\User as 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\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * リソースを配列に変換
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
        ];
    }

    /**
     * リソースの送信レスポンスのカスタマイズ
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function withResponse($request, $response)
    {
        $response->header('X-Value', 'True');
    }
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる