👌

Laravelの標準機能でシンプルにRESTful APIを実装するときのテクニック

2022/06/01に公開

LaravelでREST APIを組むときに標準の機能だけで、かなりシンプルに実装することができるのでまとめ。

Modelの定義

Eloquent\Model を継承してモデルを定義します。

artisanでオプションでファクトリ、コントローラー、マイグレーションファイルなどを一緒に作成することもできます。

$ php artisan make:model Foo

https://readouble.com/laravel/8.x/ja/eloquent.html

モデルを定義しておくことで、DBへのアクセスが容易になり、後述するRoute Model Bindingでモデルをコントローラーに渡すのが楽になったり、レスポンスで返却するデータのシリアライズが楽になります。

FormRequestの定義

標準のRequestの代わりに、FormRequestを継承したクラスを使うことで、バリデーションやでコードなど、ペイロードに対する処理をコントローラーから分離して実装できるようになります。

Modelと同様、artisanでも作成できます。

$ php artisan make:request FooRequest

https://readouble.com/laravel/8.x/ja/validation.html

コントローラーのメソッドで引数の型に定義したFormRequestを指定することで、FormRequestとして処理してくれるようになります。

バリデーションルールを定義しておくことで、データの内容を検証してエラーにする処理を省略して実装することができます。

Route Model Binding

https://laravel.com/docs/9.x/routing#route-model-binding

URLに主キーを含むような設計の場合、Route Model Bindingを使うことで簡潔に実装することができます。

routeの定義で、以下のようにURLの中に {foo} とモデル名を入れておくことで、Laravelが内部で {foo} の主キーを持つ Foo のレコードを取得してメソッドに渡すようになります。

Route::get('/foo/{foo}', 'FooController@getFoo');

コントローラー側では、引数に型の指定をするだけでモデルを扱うことができます。

class FooController extends Controller {
    public function getFoo(Foo $foo) {
        var_dump($foo->toArray());
    }
}

※実装はいろいろ省略してます

モデルの検索処理が省略できるので単純なRead, Update, Deleteがかなり簡潔に実装できるようになります。

class FooController extends Controller {
    public function getFoo(Foo $foo) {
        return response()->json($foo, 200);
    }
    
    public function patchFoo(Foo $foo, FooRequest $req) {
        $foo->fill($foo->validated())->saveOrFail();
	return response()->json($foo, 200);
    }
    
    public function deleteFoo(Foo $foo) {
        $foo->delete();
	return response()->json([], 200);
    }
}

また、対応するモデルが見つからないときは404が返却されるようになります。

モデルのリレーション

モデル同士を関連付けることで、関連するモデルの一覧取得などが楽に実現できます。

例えば、Fooに紐づくBarのモデルが複数存在するときに、

class Foo extends Model {

    protected $hidden = [
        "bars"
    ];

    public function bars() {
        return $this->hasMany(Bar::class);
    }
}

と、定義しておくと、 $foo->bars で Foo.id の値を Bar.foo_idにもつすべてのレコードを取得することができます。

逆に、BarからFooにアクセスするときにつかう、belongsTo や、複数のモデルを経由する hasManyThrough など、様々な関連付けができるので、設計に合わせて定義しておくことで、簡単なクエリやモデルの結合処理を省略して実装することができます。

また、 $hidden に設定しておくことで、ここで追加したアトリビュートをシリアライズの対象から外すことができます。
レコードが多くなる場合は $hidden プロパティを使って秘匿性のある情報や、内部でしか使用しないカラムを隠すことができます。

ページネーション

一覧APIなどで、多くのデータを扱う場合、クライアント側が指定してしたサイズでデータを返却したいケースがあります。
そういう場合は、モデル標準のページネーションを使うことで実現できます。

https://readouble.com/laravel/8.x/ja/pagination.html

標準のページネーションでは、あらかじめ内部で定義されているクエリパラメータを使って自動でページネーションを実現してくれているため、Laravel標準のパラメータ名であれば、リクエストからパラメータを取得する処理を省略することができます。

class FooController extends Controller {
    public function getFoos() {
        return response()->json(Foo::paginate($perPage=10)->items(), 200);
    }    
}

paginateインスタンスはitems以外にも様々なメソッドを持っているので、設計に合わせたデータをレスポンスに含めることができます。

Discussion