0
0

More than 1 year has passed since last update.

Laravelの個人的プラクティス

Posted at

最初に

数ヶ月Laravelを使った開発を行ってきました。
その中で、今後実装する上で気をつけたい点が溜まってきたので、備忘録として残しておきます。
※ 用途はReactのバックエンドのAPIサーバーで、Bladeテンプレートは使っておりません。

ここに記載されてあるポイントはこちらのボイラープレートに反映されています。

サービスコンテナを使用する

サービスコンテナとは、以下の事を指します。

コントローラーにロジックが乗ったり、モデルにそぐわないメソッドが実装されるのを防ぐ為に
最初からこういったレイヤーを導入すべきでした。

このサービスコンテナを導入する事で、他のメリットも享受できます。
サービスコンテナはDI(Depedency Injection)によってインスタンスが生成されるので、テストの際に簡単にモックに入れ替える事ができます。

また、サービスコンテナはコントローラーに限らず他のクラス、例えばジョブクラスからもDI出来るので、再利用しやすいです。

レスポンスはAPIリソースを使用する

APIリソースとは、以下の事を指します。

以下のドキュメントにある様に、Eloquentモデルやそのコレクションを直接返す事ができますが、
この方法が避けるべきだと考えます。

少し極端ですが、以下の様な実装がされているとレスポンスにuserが含まれたり含まれなかったりします。

class Phone extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
Route::get('/phones/{phone}', function (Phone $phone) {
    if (rand(0, 1)) {
        $phone->user;
    }
    return $phone;
});

$phone->userにアクセスしていない場合は以下の様になりますが、

{
  "id": 1,
  "user_id": 1,
  "created_at": "2022-07-08T02:45:56.000000Z",
  "updated_at": "2022-07-08T02:45:56.000000Z"
}

$phone->userにアクセスした場合はuserの中身もレスポンスに含まれてしまいます。

{
  "id": 1,
  "user_id": 1,
  "created_at": "2022-07-08T02:45:56.000000Z",
  "updated_at": "2022-07-08T02:45:56.000000Z",
  "user": {
    "id": 1,
    "name": "Mr. Kameron Barrows",
    "email": "boehm.lia@example.com",
    "email_verified_at": "2022-07-08T02:45:55.000000Z",
    "created_at": "2022-07-08T02:45:55.000000Z",
    "updated_at": "2022-07-08T02:45:55.000000Z"
  }
}

$hiddenに設定すれば、勝手にレスポンスに含まれるという問題は解決できますが、
追加で開発を進める内に設定が漏れる可能性があり、また漏れても気がつかない恐れがあります。

class Phone extends Model
{
    protected $hidden = [
        'user',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

N+1対応を入れる

Model::preventLazyLoading()を使って、
最初から開発環境でN+1が発生した際にはエラーが発生する様にすべきでした。

また、対応方針としてwithもしくはloadMissingを使うことを推奨します。
loadMissingは、未ロードの場合のみロードするので、不要なロードを防ぐ事ができます。

そこで、loadMissingを「コントローラー」「サービスコンテナ」「APIリソース」のどこで実装するかという問題がありますが、基本的に必要なタイミングの直前で実装するのが良いと考えます。

例えば、APIのレスポンスとして関連するモデルが必要な場合は、APIリソースで実装します。
APIのレスポンスとしてのみ必要にも関わらず、サービスコンテナで実装してしまうと
バッチからそのサービスコンテナを呼び出すと不要なロードになります。

では、APIリソースでの実装箇所ですが、JsonResource::toResponseResourceCollection::toResponseメソッドをオーバーライドして実装します。
JsonResource::toArrayで実装してしまうと、JsonResource::collectionを使用した際に何度もロードが呼び出される事になります。

権限チェックのポリシーはコントローラーと紐づける

ポリシーとは、以下の事を指します。

上記ドキュメントにもある様に、ポリシーはモデルと1対1の関係が前提になっている様です。
ただ、同じモデルでもAPIのエンドポイントによって別のポリシーを適用したいケースが発生しました。

例えば、Phoneモデルを扱う用途の異なるAPIエンドポイント(コントローラー)が2つあり
1つは管理者用のポリシーを、1つは一般ユーザー用のポリシーを適用したケースです。

エンドポイント コントローラー ポリシー
/api/phones/ App\Http\Controllers\Admin\PhoneController.php App\Policies\Admin\PhonePolicy.php
/aip/user/phones/ App\Http\Controllers\User\PhoneController.php App\Policies\User\PhonePolicy.php

Gate::guessPolicyNamesUsingを実装することで、プロジェクトに合ったロジックを適用する事ができます。

最後に

まだまだ日々発見の多いLaravelですが、RailsやDjangoなどと比較しても魅力的なフレームワークです。
Qiitaを含め、皆様の知見もキャッチアップしながら、より良い実装を目指していきたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0