へっぽこ社会人4年生がプログラミングを頑張る

へっぽこ社会人4年目がプログラミング系統を中心に書きたいことをつらつらと書きます

LaravelでREST APIのJSONを柔軟に変形させたい

この記事は Web × PHP TechCafe Advent Calendar 2019 の12/15の記事です。

LaravelでモデルをREST APIのレスポンスとして返す際には、コントローラからモデルのCollectionやインスタンスを返すことで、レスポンスはJSON形式で返されます。しかし、モデルのプロパティを変形してからJSONのレスポンスとして返したいこともあります。

Laravelの API Resource を利用することで、REST APIのレスポンスとして返すJSONの値を柔軟に設定できます。時刻のフォーマットを変換してJSONに埋め込むような場合に便利です。

テーブル構造の例

例として、決まった日付の範囲で、日付ごとのスケジュールを編集するシステムを考えます。日付の範囲を格納するテーブルを dates, スケジュールのデータを格納するテーブルを schedules とします。テーブル構造は以下に示します。

f:id:sierra-kilo:20191214103014p:plain

テーブル定義をマイグレーションファイルに記述します。dates のテーブル構造を CreateDatesTable クラス、 schedules のテーブル構造を CreateSchedulesTable クラスとしてそれぞれ定義します。(use 文やコメントは省略しています)

class CreateDatesTable extends Migration
{
    public function up()
    {
        Schema::create('dates', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->date('date');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('dates');
    }
}
class CreateSchedulesTable extends Migration
{
    public function up()
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('description')->nullable();
            $table->unsignedBigInteger('date_id');
            $table->time('start');
            $table->time('end');
            $table->text('remarks')->nullable();
            $table->timestamps();
            $table->softDeletes();

            $table->foreign('date_id')->references('id')->on('dates');
        });
    }

    public function down()
    {
        Schema::dropIfExists('schedules');
    }
}

モデルとAPI Resourceの実装

モデルの定義

テーブルをPHPのオブジェクトとして扱うために、 datesschedules テーブルに対応するモデルクラス DateSchedule クラスを定義します。外部キー制約を schedules テーブルの date_id カラムに設定したので、モデルにもリレーションを設定します。

class Date extends Model
{
    protected $fillable = ['date'];
}
class Schedule extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'title',
        'description',
        'date_id',
        'start',
        'end',
        'remarks'
    ];

    public function date()
    {
        return $this->belongsTo(Date::class);
    }
}

JSONの構造とAPI Resource

スケジュールを以下のようなJSON形式のレスポンスで返すことを考えます。

{
  "data": [
    {
      "id": 1,
      "title": "PHP Conference Japan 2019",
      "description": "国内最大級のPHPのカンファレンス",
      "date": "2019-12-01",
      "start": "10:00",
      "end": "18:00",
      "remarks": "東京都大田区南蒲田1丁目20-20 大田区産業プラザ PiO"
    }
  ]
}

JSONに含まれる date には Date クラスの date プロパティの値を、 startend はそれぞれ H:i 形式の文字列を設定します。

JSONレスポンスをAPI Resourceとして返すために、 Resource クラスを作成します。雛形を作成するコマンドは以下の通りです。

php artisan make:resource ScheduleResource

雛形を作成したら、 toArray() の内容を以下に示すコード例のように修正します。

class ScheduleResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'description' => $this->description,
            'date' => $this->date->date,
            'start' => Carbon::parse($this->start)->format('H:i'),
            'end' => Carbon::parse($this->end)->format('H:i'),
            'remarks' => $this->remarks,
        ];
    }
}

変数 $this には Schedule クラスのインスタンスが格納されているので、リレーション先の Date クラスのオブジェクトも date プロパティから取得できます。日付の文字列を取得するためには $this->date->date を参照します。予定の開始時刻と終了時刻を示すプロパティ startendH:i 形式の文字列に変換するために、Laravelに組み込まれているライブラリ Carbon を利用します。

コントローラからAPI Resourceを用いてレスポンスを生成

API Responseをコントローラのメソッドから返すことで、JSON形式のレスポンスが生成できます。単純にAPI Resourceを返す場合は、ルーティングで直接 Resource クラスのインスタンスを返却することもできます。

モデルのインスタンス単体を Resource として返す場合は、 Resource クラスのインスタンスを返却することで実現できます。複数のモデルのインスタンスCollection として返したい場合は、 collection()を利用します。 Resource クラスのインスタンスnew する際の引数はモデルクラスのインスタンスを、 collection()の引数にはモデルのインスタンスCollection を指定します。

APIコントローラの index() から、スケジュールを開始時刻順にソートしてREST APIとしてJSON形式のレスポンスを返すコード例は以下の通りです。

public function index()
{
    return ScheduleResource::collection(
        Schedule::orderBy('date_id')
            ->orderBy('start')
            ->get()
    );
}

まとめ

LaravelのAPI Resourceを利用すれば、REST APIのレスポンスとして返すJSONを柔軟に変形できます。デフォルトでは JSONdata フィールドに Resource クラスの toArray() で返されるデータが格納されますが、サービスプロバイダで Resource::withoutWrapping()data キーを削除できます。

モデルのフィールドを変形する程度なら、モデルのアクセサを利用するという手段もあります。レスポンスで返すJSONのフィールドをどのように変形したいかによって、モデルのアクセサとAPI Resourceを使い分けると良さそうですね。

参考文献