Route Model Binding 機能を使って、コントローラのパラメータにモデルを渡す方法を紹介します。 Route Model Binding は、ルートのパラメータに対応するモデルのインスタンスを受け取れる機能です。 モデルが見つからない場合 404 が返されます。
基本の使い方
ルートにパラメータを定義します。
Route::get('/post/{post}', [PostController::class, 'find']);
コントローラのパラメータにモデルを型として宣言します。
class PostController extends Controller
{
// 取得するモデルの型を宣言する
public function find(Post $post)
{
// インスタンスが渡される
return $post->title;
}
}
実行すると、以下のような sql が実行されます。
※ Post モデルは ソフトデリート(use SoftDeletes)を定義しています。
select * from `posts` where `id` = ? and `posts`.`deleted_at` is null limit 1
削除データも含める withTrashed
削除データも含むようにするには、withTrashed メソッドを付加します。
Route::get('/post/{post}', [PostController::class, 'find'])->withTrashed();
select * from `posts` where `id` = ? limit 1
カスタムキー:指定のカラムをキーにして取得する
Route::get('/post/{post:slug}', [PostController::class, 'find']);
実行すると、以下のような sql が実行されます。 slug
で検索しています。
select * from `posts` where `slug` = ? and `posts`.`deleted_at` is null limit 1
スコープ: 複数のパラメータをモデルにバインドし紐付けて取得する
複数のパラメータをモデルにバインドする場合、モデルに定義したリレーションで紐付けて取得することができます。
category に紐づく post を取得する例です。
モデルにリレーションを定義します。
※ 定義しない場合エラーになります。
Call to undefined method App\Models\Category::posts()
class Category extends Model
{
use HasFactory;
function posts() {
return $this->hasMany(Post::class);
}
}
カスタムキーを使用しない場合
カスタムキーを使用しない場合はscopeBindings
を付加します。
Route::get('/category/{category}/post/{post}', [PostController::class, 'find'])
->scopeBindings();
以下のような sql が実行されます。 Post 取得時に、category_id が一致するレコードを取得しています。
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
を指定しない場合は、紐付けなしのデータを取得します。
select * from `categories` where `id` = ? limit 1
select * from `posts` where `id` = ? and `posts`.`deleted_at` is null limit 1
カスタムキーを使用する場合
カスタムキーを使用する場合は、scopeBindings
なしで自動で紐づけられます。
Route::get('/category/{category}/post/{post:slug}', [PostController::class, 'find']);
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
を指定すると、紐付けないデータを取得します。
Route::get('/category/{category}/post/{post:slug}', [PostController::class, 'find'])
->withoutScopedBindings();
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 を使用します。
public function boot(): void
{
Route::model('post', Post::class);
}
モデル名が Laravel の命名規約と異なる場合や明示的に宣言したい場合などに使用します。
Route::bind
独自の条件でモデルをバインドする場合は、Route::bind を使用します。
public function boot(): void
{
Route::bind('post', function (string $slug) {
return Post::where('slug', $slug)->where('created_at', '>', Carbon::yesterday())->firstOrFail();
});
}
- ルートに
post
を定義します。
Route::get('/post/{post}', [PostController::class, 'find']);
- 実行すると、以下のような sql が実行されます。
select * from `posts` where `slug` = ? and `created_at` > ? and `posts`.`deleted_at` is null limit 1
resolveRouteBinding
メソッド
Eloquent モデルの resolveRouteBinding メソッドをオーバーライドする方法です。
class Post extends Model
{
use HasFactory, SoftDeletes;
public function resolveRouteBinding($value, $field = null)
{
return $this->where('slug', $value)->firstOrFail();
}
}
- ルートに
post
を定義します。
Route::get('/post/{post}', [PostController::class, 'find']);
- 実行すると、以下のような sql が実行されます。
select * from `posts` where `slug` = ? and `posts`.`deleted_at` is null limit 1
resolveRouteBinding
メソッドのスコープ側でカスタム処理をする
resolveRouteBinding
メソッドでスコープを指定し、カスタム処理をする場合は、
絞りこむ側のモデルのresolveChildRouteBinding
をオーバーライドします。
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
には 紐付けるモデルのパラメータの値が渡されます。