$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[September 26, 2025] Laravel

Laravel Route Model Binding: Implicit vs Explicit

Laravel Route Model Binding: Implicit vs Explicit

────────────────────────────────────────────────────────

Laravel Route Model Binding: Implicit vs Explicit

Tip: Implicit Binding is Automatic

Route::get('/posts/{post}', function (Post $post) {
    return $post;
});

Laravel resolves the Post by its ID automatically. No manual Post::findOrFail() needed.

Gotcha: Implicit Binding Uses id Column

If your route parameter is {post}, Laravel looks up by id. To use a different column:

public function getRouteKeyName(): string
{
    return 'slug';
}

Tip: Explicit Binding for Custom Resolution

Route::bind('post', fn($value) => Post::where('slug', $value)->firstOrFail());

Use Route::bind() when you need custom logic beyond a simple column lookup.

Gotcha: Soft Deleted Models Still Resolve

By default, route model binding includes soft-deleted records. Add a global scope or check manually.

Tip: Scoped Binding for Nested Routes

Route::get('/users/{user}/posts/{post:slug}', ...);

The {post:slug} binds Post by slug, scoped to the user.

Gotcha: Binding Resolution Happens Before Middleware

If your middleware checks permissions, the model is already loaded. Use explicit binding to defer loading.

Tip: Use route:cache Carefully

php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.

Tip: Model APP_KEY Rotation

Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.

Gotcha: Local Scope Leaks

Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.

Senior Insight

Route model binding is one of those 'magic' features that works beautifully until you need something slightly different. I've seen teams fight implicit binding for weeks, adding workarounds, when explicit binding with Route::bind() would have been five lines of code. My approach: use implicit binding for straightforward id lookups, and always use explicit binding when your route key isn't the primary key or when you need to scope by the current user.

Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)

────────────────────────────────────────────────────────
<-- back to posts