Laravel 5.5 Eloquent: APIリソース

イントロダクション

API構築時、Eloquentモデルと、アプリケーションユーザーに対して実際に返信するJSONリスポンスとの間に、トランスレーション層を設置することが必要となります。Laravelのリソースクラスは、モデルやモデルコレクションを記述しやすく簡単に、JSONへと変換してくれます。

リソース生成

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

php artisan make:resource UserResource

コレクションのリソース

個別のモデルのリソースに加え、モデルのコレクションを変換し、返信するリソースを生成することも可能です。これにより、レスポンスにリンクと、指定したコレクションリソース全体を表す他のメタ情報を含めることができるようになります。

コレクションリソースを生成するには、リソース生成時に--collectionフラグを指定してください。もしくは、シンプルにリソース名へCollectionを含め、Laravelへコレクションリソースを生成するように指示できます。コレクションリソースは、Illuminate\Http\Resources\Json\ResourceCollectionクラスを拡張します。

php artisan make:resource Users --collection

php artisan make:resource UserCollection

概略

Tip!! このセクションは、リソースとコレクションリソースについて、大雑把に概略を説明します。リソースで実現可能な機能とカスタマイズについて深く理解するため、このドキュメントの他の部分もお読みください。

リソースを書く場合に指定可能な全オプションを説明する前に、最初はLaravelでリソースがどのように使われるかという点を俯瞰し、確認しておきましょう。リソースクラスは、JSON構造へ変換する必要のある一つのモデルを表します。例として、シンプルなUserResourceクラスを見てみましょう。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class UserResource 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\UserResource;

Route::get('/user', function () {
    return new UserResource(User::find(1));
});

コレクションリソース

ページ付けしたリソースやコレクションを返す場合は、ルートかコントローラの中で、リソースインスタンスを生成する時に、collectionメソッドを使用します。

use App\User;
use App\Http\Resources\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 UserResource 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\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\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キーでラップします。なぜなら、ページ付けしたレスポンスは、ペジネータの状態を含めたmetalinksキーを常に含めるからです。

{
    "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());
});

ページ付けしたレスポンスは常に、ペジネータの状態を含むmetalinksキーを持っています。

{
    "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が自動的に付け加える、linksmetaキーを意図せずオーバーライドしてしまう心配はありません。追加の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\UserResource;

Route::get('/user', function () {
    return new UserResource(User::find(1));
});

しかし、利用者へ送信する前に、HTTPレスポンスをカスタマイズする必要が時々あります。リソースに対してresponseメソッドをチェーンしてください。このメソッドは、Illuminate\Http\Responseインスタンスを返しますので、レスポンスヘッダを完全にコントロールできます。

use App\User;
use App\Http\Resources\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 UserResource 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');
    }
}

ドキュメント章別ページ

公式パッケージ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュを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)へ移動

その他

?

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