最初に
数ヶ月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::toResponse
、ResourceCollection::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を含め、皆様の知見もキャッチアップしながら、より良い実装を目指していきたいと思います。