[Laravel]RouteModelBinding ルートのパラメータとモデルを紐付ける

広告Laravel
2023年11月20日

Route Model Binding 機能を使って、コントローラのパラメータにモデルを渡す方法を紹介します。 Route Model Binding は、ルートのパラメータに対応するモデルのインスタンスを受け取れる機能です。 モデルが見つからない場合 404 が返されます。

基本の使い方

ルートにパラメータを定義します。

routes/web.php
Route::get('/post/{post}', [PostController::class, 'find']);

コントローラのパラメータにモデルを型として宣言します。

app/Http/Controllers/PostController.php
class PostController extends Controller
{
    // 取得するモデルの型を宣言する
    public function find(Post $post)
    {
        // インスタンスが渡される
        return $post->title;
    }
}

実行すると、以下のような sql が実行されます。
※ Post モデルは ソフトデリート(use SoftDeletes)を定義しています。

SQL
select * from `posts` where `id` = ? and `posts`.`deleted_at` is null limit 1

削除データも含める withTrashed

削除データも含むようにするには、withTrashed メソッドを付加します。

routes/web.php
Route::get('/post/{post}', [PostController::class, 'find'])->withTrashed();
SQL
select * from `posts` where `id` = ? limit 1

カスタムキー:指定のカラムをキーにして取得する

routes/web.php
Route::get('/post/{post:slug}', [PostController::class, 'find']);

実行すると、以下のような sql が実行されます。 slugで検索しています。

SQL
select * from `posts` where `slug` = ? and `posts`.`deleted_at` is null limit 1

スコープ: 複数のパラメータをモデルにバインドし紐付けて取得する

複数のパラメータをモデルにバインドする場合、モデルに定義したリレーションで紐付けて取得することができます。

category に紐づく post を取得する例です。

モデルにリレーションを定義します。

※ 定義しない場合エラーになります。
Call to undefined method App\Models\Category::posts()

app/Models/Category.php
class Category extends Model
{
    use HasFactory;
 
    function posts() {
        return $this->hasMany(Post::class);
    }
}

カスタムキーを使用しない場合

カスタムキーを使用しない場合はscopeBindingsを付加します。

routes/web.php
Route::get('/category/{category}/post/{post}', [PostController::class, 'find'])
    ->scopeBindings();

以下のような sql が実行されます。 Post 取得時に、category_id が一致するレコードを取得しています。

SQL
select * from `categories` where `id` = ? limit 1
select * from `posts` where `posts`.`category_id` = ? and `posts`.`category_id` is not null and `id` = ? limit 1

scopeBindingsを指定しない場合は、紐付けなしのデータを取得します。

SQL
select * from `categories` where `id` = ? limit 1
select * from `posts` where `id` = ? and `posts`.`deleted_at` is null limit 1

カスタムキーを使用する場合

カスタムキーを使用する場合は、scopeBindings なしで自動で紐づけられます。

routes/web.php
Route::get('/category/{category}/post/{post:slug}', [PostController::class, 'find']);
SQL
select * from `categories` where `id` = ? limit 1
select * from `posts` where `posts`.`category_id` = ? and `posts`.`category_id` is not null and `slug` = ? and `posts`.`deleted_at` is null limit 1

紐付けたくない場合は明示的にwithoutScopedBindingsを指定すると、紐付けないデータを取得します。

routes/web.php
Route::get('/category/{category}/post/{post:slug}', [PostController::class, 'find'])
    ->withoutScopedBindings();
SQL
select * from `categories` where `id` = ? limit 1
select * from `posts` where `slug` = ? and `posts`.`deleted_at` is null limit 1

カスタムロジック: モデルバインディング処理をカスタマイズする

独自の処理でモデルバインディングするには以下ような方法があります。

  • Route::model
  • Route::bind
  • resolveRouteBinding

Route::model

モデルを明示的にバインドする場合は、Route::model を使用します。

app/Providers/RouteServiceProvider.php
public function boot(): void
{
    Route::model('post', Post::class);
}

モデル名が Laravel の命名規約と異なる場合や明示的に宣言したい場合などに使用します。

Route::bind

独自の条件でモデルをバインドする場合は、Route::bind を使用します。

app/Providers/RouteServiceProvider.php
public function boot(): void
{
    Route::bind('post', function (string $slug) {
        return Post::where('slug', $slug)->where('created_at', '>', Carbon::yesterday())->firstOrFail();
    });
}
  • ルートにpostを定義します。
routes/web.php
Route::get('/post/{post}', [PostController::class, 'find']);
  • 実行すると、以下のような sql が実行されます。
SQL
select * from `posts` where `slug` = ? and `created_at` > ? and `posts`.`deleted_at` is null limit 1

resolveRouteBindingメソッド

Eloquent モデルの resolveRouteBinding メソッドをオーバーライドする方法です。

app/Models/Post.php
class Post extends Model
{
    use HasFactory, SoftDeletes;
    public function resolveRouteBinding($value, $field = null)
    {
        return $this->where('slug', $value)->firstOrFail();
    }
}
  • ルートにpostを定義します。
routes/web.php
Route::get('/post/{post}', [PostController::class, 'find']);
  • 実行すると、以下のような sql が実行されます。
SQL
select * from `posts` where `slug` = ?  and `posts`.`deleted_at` is null limit 1

resolveRouteBindingメソッドのスコープ側でカスタム処理をする

resolveRouteBindingメソッドでスコープを指定し、カスタム処理をする場合は、 絞りこむ側のモデルのresolveChildRouteBindingをオーバーライドします。

app/Models/Category.php
class Category extends Model
{
    function posts() {
        return $this->hasMany(Post::class);
    }
    /**
     * Retrieve the child model for a bound value.
     *
     * @param  string  $childType
     * @param  mixed  $value
     * @param  string|null  $field
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveChildRouteBinding($childType, $value, $field)
    {
        return parent::resolveChildRouteBinding($childType, $value, $field);
    }
}
 

$childTypeには、紐付けるモデルのパラメータ名が渡されます。今回の例の場合は、post です。   $valueには 紐付けるモデルのパラメータの値が渡されます。

参考

Laravel - The PHP Framework For Web Artisans

Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.laravel.comlaravel.com
Laravel - The PHP Framework For Web Artisans