2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelの paginate() から考えるレイヤー分離

Last updated at Posted at 2025-05-29

Laravelでありがちな「数ページ分割」処理において、思わぬところで HTTP 依存が激しく迷い込みやすいパターンがあります。それが Laravelの paginate() です。

1. レビューで見つけた paginate のワナ

あるプロジェクトで Repository クラスのコードレビューを行っていたとき、次のような記述に出会いました:

use Illuminate\Http\Request;

class SampleRepository
{
    public function fetch(int $limit)
    {
        $request = app(Request::class);
        $request->query->set('page', $page);
        return SampleModel::paginate($limit);
    }

え?なんでリポジトリ(≒DB操作)でHTTPリクエストが出てきてるの?
HTTPリクエストのパラメータにページ番号入れて意味ないでしょ?
ええっ!ちゃんとページングできてる!!なんで!?

この Request を介してページ番号を無理やり設定する実装に違和感を覚え、調査を進めると、Laravel の クエリビルダの paginate() はデフォルトで 現在の HTTP リクエストから ?page= を自動的に取得する 仕様であることが判明しました。
リファレンスにも書いてあります。
https://readouble.com/laravel/12.x/ja/pagination.html

デフォルトでは、現在のページはHTTPリクエストのpageクエリ文字列引数の値から検出されます

これは一見便利ですが、実は重大な設計リスクを孕んでいます。


2. Laravel の paginate() は Request に依存している

paginate() の第三引数にはページキーを明示的に指定できますが、指定しなかった場合は現在の HTTP リクエストを参照してページ番号を決定します。

// Laravelの内部動作
$page = request()->input('page', 1); // デフォルトでRequestを参照

この動作により、以下のような問題が生じます:

  • CLI / Job / テスト環境では Request が存在せず例外が発生する
  • リポジトリやサービス層のコードが HTTP に依存してしまう
  • テストが困難になる(Request コンテキストが必要)
  • 責務が不明瞭になる(誰がページ番号を決めているのか)

つまり、「便利な魔法」の代償として、レイヤー分離が崩壊するリスクがあるのです。


3. HttpRequest が登場してよいレイヤーはどこか?

設計原則として、Illuminate\Http\Requestアプリケーション層の入り口だけに登場すべきです。具体的には以下のようなレイヤーです:

✅ コントローラー

  • HTTPリクエストを受け取り、パラメータを処理してアプリケーション層に橋渡しする責務。

✅ ミドルウェア

  • リクエストの検査・加工(認証、CORS、ロギングなど)を担当するため、直接的な Request の使用は正当。

✅ FormRequest

  • バリデーション責務を持つ Laravel 独自の仕組み。これはコントローラーの一部とみなしてよい。

そのほかの層では HttpRequest を使ってはならない というのが基本です。例えば

❌ サービス層(ユースケース)

  • ビジネスロジックを担う層。HTTP に依存すると再利用・テスト・保守が困難になります。

❌ リポジトリ層

  • データ取得の役割。データソースに対して中立であるべきです。HTTPが登場してはいけません。

❌ ジョブ / イベントリスナー

  • 非同期実行されるため、HTTPコンテキストがそもそも存在しない前提で設計されるべきです。

❌ モデル

  • データ構造の定義に徹するべきで、リクエスト情報との関係は一切持たないのが理想です。

4. 正しい設計:リクエスト依存を排除する

リポジトリやサービス層に paginate($limit) を使いたいときは、必ずページ番号も明示的に渡しましょう:

$records = $repository->fetch($limit, $page);

そして Repository 側ではこう書きます:

->paginate($limit, ['*'], 'page', $page);

これにより、HTTP に依存しないクリーンなレイヤー設計が保たれます。


おわりに

まさかクエリビルダがHTTPリクエストを直接参照するなんて考えもしなかったので驚きました。怖いですねー。

Laravel は便利で高速な開発が可能なフレームワークですが、その魔法には慎重に向き合うべきです。特に paginate() のように、見えないところでリクエスト依存が仕込まれているAPIを使う際には、責務の分離とテスト性を意識した明示的な設計を心がけましょう。

便利さの裏には責任がある。

2
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?