Skip to content

perf: reuse eager-loaded relations in forModel via loadMissing - #197

Merged
ManukMinasyan merged 1 commit into
relaticle:3.xfrom
MACscr:perf/formodel-loadmissing
Aug 8, 2026
Merged

perf: reuse eager-loaded relations in forModel via loadMissing#197
ManukMinasyan merged 1 commit into
relaticle:3.xfrom
MACscr:perf/formodel-loadmissing

Conversation

@MACscr

@MACscr MACscr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

BaseBuilder::forModel() unconditionally calls $model->load('customFieldValues.customField.options') on every Infolist/Form schema generation. When the host application already eager-loads those relations — e.g. a Filament resource's getEloquentQuery() loading custom field values so the table avoids an N+1 — load() refetches them anyway, so a view/edit page runs the three relation queries twice: once from the host's eager-load, once from forModel().

This switches load()loadMissing(), so forModel() reuses relations that are already loaded and only queries when they're absent.

Why it's safe

  • The un-eager path is unchanged: loadMissing() still loads the relations when they aren't present (covered by a test).
  • It matches the package's own behaviour elsewhere — BackendVisibilityService::extractFieldValues() and LookupPreloader already guard on relationLoaded() rather than force-loading.
  • The TableBuilder guard is untouched; only Infolist/Form/Exporter/Importer builders are affected, and Exporter/Importer call forModel(new $model) on a keyless instance where the load is inert.

Caveat

loadMissing() no longer force-refetches. If code mutates a record's custom field values and re-renders the same in-memory instance within one request, it should $record->unsetRelation('customFieldValues') before re-rendering. Standard Filament view/edit flows re-resolve the record per request, so they're unaffected.

Test

tests/Feature/Integration/Builders/ForModelLoadMissingTest.php:

  • asserts forModel() issues no custom_field_values query when the record is already eager-loaded (fails on load(), passes on loadMissing())
  • asserts the relations still load when the record isn't eager-loaded (regression guard)

Local run of the full CI gate passes: Pint, PHPStan, Rector, and Pest (785 passed).

BaseBuilder::forModel() unconditionally called
$model->load('customFieldValues.customField.options') for every
Infolist/Form schema generation. When the host application already
eager-loads those relations (e.g. a Filament resource's
getEloquentQuery() eager-loading custom field values so the table
avoids an N+1), load() refetched them anyway, so a view/edit page
ran the three relation queries twice.

Switching to loadMissing() reuses already-loaded relations and only
queries when they are absent, so the un-eager path is unchanged. This
matches the package's existing behaviour elsewhere
(BackendVisibilityService and LookupPreloader already guard on
relationLoaded()). The TableBuilder guard is untouched.

Note: loadMissing() no longer force-refetches, so if a record's custom
field values are mutated and the same in-memory instance is re-rendered
within one request, call $record->unsetRelation('customFieldValues')
first. Standard Filament view/edit flows re-resolve the record per
request and are unaffected.
@ManukMinasyan
ManukMinasyan merged commit ae65a03 into relaticle:3.x Aug 8, 2026
1 check passed
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.

2 participants