flanger.dev

How to Avoid Duplicate Queries in Filament Closures For Eloquent Records

You may run into this problem when you want to display or use additional properties from an Eloquent model, but you only have its ID. This may happen with select fields, where the model isn't fully loaded and a user may select any record. Here is a small but notable trick:

The Scenario

You may have a select field bound to a relation:

1Forms\Components\Select::make('company_id')
2 ->label(__('company.singular'))
3 ->live()
4 ->searchable()
5 ->relationship('company', 'name')
6 ->required(),

Now you want to display the company name, if the user has selected one:

1Forms\Components\Placeholder::make('company_name')
2 ->hidden(fn (Get $get) => ! $get('company_id'))
3 ->content(fn (Get $get): string => Company::find($get('company_id'))->name ?? ''),
4 
5Forms\Components\Placeholder::make('company_address')
6 ->hidden(fn (Get $get) => ! $get('company_id'))
7 ->content(fn (Get $get): string => Company::find($get('company_id'))->address ?? ''),

The Problem

With the code above, you are firing an additional unnecessary query for every Company::find() call. This can lead to a huge N+1 problem, depending on how many records or fields you are displaying.

The Solution

In Laravel 11 and above, you can easily mitigate this problem by caching the response using the once() helper in a static method:

1public static function getCompany(int|string $id)
2 {
3 return once(fn () => Company::find($id));
4 }

And use it like so:

1Forms\Components\Placeholder::make('company_name')
2 ->hidden(fn (Get $get) => ! $get('company_id'))
3 ->content(fn (Get $get): string => static::getCompany($get('company_id'))->name ?? ''),
4 
5Forms\Components\Placeholder::make('company_address')
6 ->hidden(fn (Get $get) => ! $get('company_id'))
7 ->content(fn (Get $get): string => static::getCompany($get('company_id'))->address ?? ''),

Now you can use and display all available properties in your Eloquent model as much as you like or need. :)