Laravel5.5 API Resourcesを利用する その1

先日、Laravel5.5がリリースされました。
このバージョンは新しいLTSとなりますので、5.1からのアップグレードなどを検討してみましょう!

5.5で追加された仕組みの一つに、Eloquent: API Resourcesがあります。

laravel.com

以前からEloquentで取得したオブジェクトを直接レスポンスに与えると、
jsonで返却する機能がありました。

追加されたこの仕組みは、レスポンスとEloquentとの橋渡しをする機能となります。

便利な機能ではありますが、
データベースとの接続、データ返却を行うEloquentと、Httpのレスポンスが結び付き合うのは、
利便性という以外では責務が多すぎるため、
議論の的になることもあります。

と、マニュアルだけでは確かにそう見える機能ですが、
RESTでおなじみのLevel 3 - Hypermedia Controlsに対応することができます。

martinfowler.com

Eloquentを利用すれば、Resourceクラスがjsonに変換する際に、
meta情報などを付け加えることができます。
このレスポンスはjsonapiと呼ばれるフォーマットに近しい形で出力されます。

JSON API — A specification for building APIs in JSON

実際のアプリケーションではEloquent以外のものを利用することも多くあると思います。

本エントリでは、Eloquent以外のものでこのResourceを使って、
HALを適用します。

Resourceクラスは、 \Illuminate\Http\Resources\Json\Resource を利用することで、
さまざまなフォーマットに沿ったjsonを定義することができます。

このResourceクラスには、型宣言がありませんが、toArrayメソッドを持つものであれば、
変換に利用することができます。

シンプルな配列を利用する場合は、次のようになるでしょう

<?php

    $embedded = new \Illuminate\Http\Resources\Json\Resource(
        new class implements \Illuminate\Contracts\Support\Arrayable
        {
            public function toArray()
            {
                return [
                    [
                        'message' => 'hello',
                        'type'    => 'example',
                        '_links'  => [
                            'self' => 'http://example.com/1',
                            'hoge' => 'http://example.com/hoge/1',
                        ],
                    ],
                    [
                        'message' => 'hello',
                        'type'    => 'example',
                        '_links'  => [
                            'self' => 'http://example.com/2',
                            'hoge' => 'http://example.com/hoge/2',
                        ],
                    ],
                ];
            }
        }
    );

このリソース一つ一つにナビゲート可能なリンクがあるものとして、記述しています。

ResourceCollectionクラスを利用することもありますが、
Collectionクラスに依存するため、今回は通常の配列のみを利用します。

このままレスポンスとして利用することも可能ですが、
このリソースを他のリソースで利用し、埋め込み情報として利用します。

<?php

    $resource = new \Illuminate\Http\Resources\Json\Resource(
        new class($embedded) implements \Illuminate\Contracts\Support\Arrayable
        {
            /** @var \Illuminate\Http\Resources\Json\Resource */
            protected $resource;

            /**
             *  constructor.
             *
             * @param \Illuminate\Http\Resources\Json\Resource $resource
             */
            public function __construct(\Illuminate\Http\Resources\Json\Resource $resource)
            {
                $this->resource = $resource;
            }

            public function toArray()
            {
                return [
                    'title'     => 'illuminate/http resource',
                    '_links'    => [
                        'self' => 'http://google.com',
                    ],
                    '_embedded' => $this->resource->jsonSerialize(),
                ];
            }
        }
    );
    $resource::$wrap = null;

最初に記述したリソースを埋め込み情報として利用するリソースクラスを作成します。
デフォルトでdataの配列となりますが、静的プロパティの$wrapに任意の名称を与えることができますので、
ここでは利用しないためnullとします。

埋め込み情報は _embedded として利用します

最後にcontent-typeを 'application/hal+json' としてレスポンスを返却するようにします。

<?php

$response = $resource->response()->header('content-type', 'application/hal+json');
return $response;

レスポンスとしては次のように返却されます。

f:id:ytakezawa:20170908035400p:plain

親子関係がはっきりしたデータ構造や実装であれば、
比較的ライトに実装することができると思います。

LaravelでEntityなどを表現して、データマッパーライクな実装をしている場合は、
以前のエントリを参考にアノテーションを使う実装の方が、
フィットするかと思います。

qiita.com

ytake.hateblo.jp1