Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

motinska94's avatar

When I make category slugs unique by user_id, it confuses route-model binding

I said category on the title to make it more clear, but in this case I am using a model named "subject" with the same purpose.

I'm using the code below to make sure each user can have any subject name they want, but each name can only be used once per user. And I am using static::creating method to generate an Str::slug for each subject.

$request->validate([
            'name' => [
				... ,
                Rule::unique('subjects')->where(function ($query){
                    return $query->where('user_id', auth()->id());
                }),    
            ],
        ]);

And so far this has been working well for me. But I noticed that this causes confusion for the route-model binding (especially since I am using Route::resource for the models)

Route::resource('subject', SubjectController::class)
        ->parameters(['subject' => 'subject:slug']);

For example, I had to tweak my destroy model (along with every single route-model binded controller method) to use this logic to make sure I'm deleting the correct subject, the one that belongs to the authenticated user :

public function destroy(Subject $subject)
{
    $slug = $subject->slug;
    $subject = auth()->user()->subjects()->whereSlug($slug)->firstOrFail();
    $subject->delete();
    return to_route('subject.index')->with(['success' => 'Subject deleted.']);
}

And again, this also works as intended. But I'm thinking this probably isn't the best way to do this. For one, it's code repetition, since I am using these first two lines on each controller method.

Is there a simpler way to do this? How would you do this logic yourself?


The "bug" :

When I wasn't using those said two lines in my controller methods, the code below would return the link to the first occurrence of the subject with the given slug. Which doesn't always correspond to the authenticated user's subject record with the same slug.

@foreach (auth()->user()->subjects as $subject)
	<a href="{{ route('subject.show', $subject) }}"> {{ $subject->name }} </a>
@endforeach
0 likes
3 replies
Ben Taylor's avatar
Level 35

Read through explicit route model binding in the docs. https://laravel.com/docs/11.x/routing#explicit-binding

You could end up with something like this in the boot method of your application's AppServiceProvider:

use App\Models\Subject;
use Illuminate\Support\Facades\Route;

public function boot(): void
{
Route::bind('subject', function (string $value) {
        return Subject::where('slug', $value)->where('user_id', auth()->user()->id)->firstOrFail();
    });
}

Just beware that this is expecting an authenticated user.

1 like
motinska94's avatar

@Ben Taylor Thanks so much!

This worked for the test scenario I was using (after removing the re-declaration of model bindings) and I think it's the cleanest approach.

Ben Taylor's avatar

@motinska94 No worries. Glad to help. If you don't want it in the app service provider, you can also do the logic on the model by changing the resolveRouteBinding method. Check the docs in the link above to see how.

1 like

Please or to participate in this conversation.