filament icon indicating copy to clipboard operation
filament copied to clipboard

N +1 on Select input with relationship when used within a repeater

Open GeorgeFourkas opened this issue 2 months ago • 3 comments

Package

filament/filament

Package Version

4.1.6

Laravel Version

12.33

Livewire Version

3.6.4

PHP Version

8.3

Problem description

When adding a Select input with relationship() method (many to many) inside a repeater, instead of eager loading all the items' filament retrieves the relationships one by one. the result is n+1 problem. Below i am attaching a screenshot from the laravel debugbar in a simple filament form.

For the context, i have created a sample project using 3 models. Project, Task, and User.

I added a repeater within the project resource which has 2 fields. The task name, and the Select element which relates to the many to many task-user relationship.

Image

Expected behavior

It should eager load the many to many relationship between Task and User models.

Steps to reproduce

download the project. run composer install, copy .env.example to .env, run php artisan key:generate, run php artisan db:seed, run php artisan make:filament-user and enter the user credentials. open the /admin/projects user. click a project and open laravel debugbar panel.

Reproduction repository (issue will be closed if this is not valid)

https://github.com/GeorgeFourkas/filament_repeater_issue

Relevant log output


GeorgeFourkas avatar Oct 11 '25 22:10 GeorgeFourkas

It's my two cents, but I believe the relationship() function of Select is responsible for querying the relationship, since it also accepts query builder modifications in the form of the $modifyQueryUsing parameter, which would make no sense it used a collection rather than a query.

If you want the Select to use a custom logic with preloaded values, you can use something like this (even though it's not the nicest solution imo):

// App\Models\Task.php

// ...
protected $with = ['users'];
// ...
// App\Filament\Resources\Projects\Schemas\ProjectForm.php

// Code for repeater...
->schema([
    TextInput::make('name'),
    Select::make('users')
        ->multiple()
        ->options(User::pluck('name', 'id'))
        ->afterStateHydrated(function ($component, $state, $record) {
                $component->state($record->users->pluck('id')->toArray());
        })
        ->saveRelationshipsUsing(function ($component, $state, $record) {
                $record->users()->sync($state ?? []);
                $record->load('users');
        }),
]),

balintcodes avatar Oct 12 '25 12:10 balintcodes

Yeah, I'm on the edge about if this is a bug or not. I think there are some situations where the relation is loaded and there is no modifyQueryUsing that we could use the eager-loaded records from the model instead of querying again. But there would also be preloaded options queries to optimize too which couldn't be eager loaded with with().

danharrin avatar Oct 12 '25 12:10 danharrin

Hello, thank you for taking the time to look into this :) Well i implemented a solution kinda like the one @balintcodes suggested, while it doesnt load the relationships one by one when the form loads, it does that when we save the form, but its a workaround.

I was also on the edge about that, if its a bug or not, but since it requires a more complex fix to avoid N+1 (which in the end you do not avoid it, you just delay it) i thought i should report it as a bug on the Select input

GeorgeFourkas avatar Oct 12 '25 17:10 GeorgeFourkas