Builder Scoping
onlySections()
By default, every builder (CustomFields::form(), ::infolist(), ::table(),
::exporter(), ::importer()) resolves fields for the whole entity type. onlySections()
constrains that resolution to a specific set of custom_field_sections rows:
use Relaticle\CustomFields\Facades\CustomFields;
CustomFields::form()
->forModel($model)
->onlySections([$sectionA->id, $sectionB->id])
->build();
Passing [] (the default) means "no scope" — existing call sites that never call
onlySections() are unaffected.
This exists for consumers that version their form definitions — for example, cloning a
section (and its fields) per form version. Field codes are normally unique per entity
type, so two versions of "the same field" would collide on code unless resolution is
scoped by section. onlySections() lets each version's builder only see its own
section(s), so the same code can live in more than one section without one bleeding into
the other's schema.
onlySections() is inherited by every builder from BaseBuilder, including through
FormBuilder::build() / InfolistBuilder::build() — the scope is threaded through
FormContainer / InfolistContainer as well, so it applies whether you call ->build()
or ->values().
The persistence contract — read this before relying on section-scoped codes
onlySections() narrows resolution (which fields get loaded onto a form, infolist, or
table). It does not change how values are saved. UsesCustomFields::saveCustomFields()
iterates the model's custom-field-values relationship and writes each submitted value by
field code. If two sections share a code and that relationship isn't scoped to match
onlySections(), saveCustomFields() will silently write the same value to both
field rows — a data-corrupting outcome that has nothing to do with whether resolution
scoping itself is working correctly.
If you use onlySections(), scope your model's custom-field-values relationship
(customFields() by default, or your override) to the same section(s). It is
entity-scoped by default and overridable per model.
CodeGenerator::resolveUniquenessScopeUsing()
Auto-generated codes (FIELD_CODE_AUTO_GENERATE) are checked for collisions before use.
Register a callback to narrow that check the same way onlySections() narrows
resolution:
use Illuminate\Database\Eloquent\Builder;
use Relaticle\CustomFields\Support\CodeGenerator;
CodeGenerator::resolveUniquenessScopeUsing(
fn (string $entityType, string $type, int|string|null $sectionId): ?Closure => $sectionId !== null
? fn (Builder $query): Builder => $query->where('custom_field_section_id', $sectionId)
: null
);
The callback receives:
$entityType— the entity the field or section belongs to.$type—'field'or'section', so you can scope differently per kind of code.$sectionId— the section the code is being generated within, ornullwhen there isn't one (for example, the sectionless field-management table).
Return null to leave the uniqueness check global — the default, backward-compatible
behavior. Return a closure to narrow it: the closure receives the in-progress Builder
and must return the query to apply. where()-style mutation also works (it returns
the same instance), but a closure that hands back a different instance — e.g.
$query->clone()->where(...) — is honored too, since the return value is always what's
used.
Register the callback once, typically in a service provider's boot() method.