Skip to content

[13.x] Add RouteKey and BindRoute attributes for route model binding#59020

Draft
Tresor-Kasenda wants to merge 2 commits intolaravel:13.xfrom
Tresor-Kasenda:feature/13x-route-binding-attributes-clean
Draft

[13.x] Add RouteKey and BindRoute attributes for route model binding#59020
Tresor-Kasenda wants to merge 2 commits intolaravel:13.xfrom
Tresor-Kasenda:feature/13x-route-binding-attributes-clean

Conversation

@Tresor-Kasenda
Copy link
Contributor

This PR adds first-class PHP attribute metadata for implicit route model binding:

  • #[RouteKey('field')] on Eloquent models
  • #[BindRoute(parameter: ..., field: ..., withTrashed: ...)] on route parameters

This PR add :

  • Reduce boilerplate for common getRouteKeyName() use cases.
  • Allow per-parameter binding overrides directly at the route signature level.
  • Keep attribute-based binding compatible with route caching.

Before / After

1) Model route key convention (RouteKey)

Before

class User extends Model
{
    public function getRouteKeyName(): string
    {
        return 'name';
    }
}

Route::get('/users/{user}', fn (User $user) => $user);

After

use Illuminate\Database\Eloquent\Attributes\RouteKey;

#[RouteKey('name')]
class User extends Model
{
    //
}

Route::get('/users/{user}', fn (User $user) => $user);

2) Per-parameter binding metadata (BindRoute)

Before

Route::get('/users/{user:name}', fn (User $user) => $user);

// No per-parameter override for trashed behavior.
// Only route-level withTrashed() is available.

After

use Illuminate\Routing\Attributes\BindRoute;

Route::get('/users/{user}', fn (#[BindRoute(field: 'name')] User $user) => $user);

Route::get('/users/{user}', fn (#[BindRoute(withTrashed: true)] User $user) => $user);

// Can opt-out per parameter, even if route is withTrashed().
Route::get('/users/{user}', fn (#[BindRoute(withTrashed: false)] User $user) => $user)
    ->withTrashed();

@shaedrich
Copy link
Contributor

While I think that #[RouteKey] is tremendously helpful, I'm not so sure about #[BindRoute]:

  • when used with field, it's actually quite a lot of boilerplate compared to {user:name}
  • withTrashed would be interesting, but I would prefer this not to be tied to soft deletes in particular but to any scope. This would be very helpful and would eliminate the need for calling loadMissing() first thing in the controller when you need non-default relations

@taylorotwell
Copy link
Member

Agree I would remove the BindRoute stuff.

@taylorotwell taylorotwell marked this pull request as draft February 27, 2026 14:07
@Tresor-Kasenda
Copy link
Contributor Author

Thanks for the feedback.
{model:field} is indeed more concise for simple cases. #[BindRoute] mainly targets more complex scenarios (scopes, eager loading, constraints).

I also agree that a generic scope-based approach would be preferable over a soft-delete-specific withTrashed.

@Tresor-Kasenda
Copy link
Contributor Author

@taylorotwell I can delete that and make another PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants