›
byrcsc/laravel-custom-fields · 1.x
Work out why a field is invisible, a value did not save, or a filter returned nothing.
UnknownCustomFieldException means the current tenant has no definition under
that key for that model. Check them in this order:
Which tenant is current. The message says. A queued job, a console command,
or a request that has not resolved a tenant yet all report null, and a field
defined for acme is invisible under any of them.
app(ByRcsc\LaravelCustomFields\Contracts\TenantResolver::class)->currentTenant();Which model type the definition was written against. Definitions are stored against the morph class. Adding a morph map after fields were defined leaves the old rows keyed by class name and the lookup asking for the alias:
ByRcsc\LaravelCustomFields\Models\CustomFieldDefinition::query()
->where('key', 'account_tier')
->pluck('model_type', 'tenant');Whether the manager is holding a stale set. A definition written by a mass
update, inside withoutEvents(), or with saveQuietly() never reached it. Call
CustomFields::flush() and read again. See keeping the cache
honest.
A tenant field with the same key shadows the global one, so you may be reading a different definition than the one you defined. Ask which one is winning:
$definition = CustomFields::definition(Customer::class, 'account_tier');
[$definition->tenant, $definition->type, $definition->options];A null tenant there means the global field is in play and this tenant has not overridden it. See the override rule.
Nothing was written at all. setCustomFields() resolves every key and
validates every value before writing any of them, so one unknown key or one
invalid value in the array means none of them landed. The exception says which.
The record was never saved. UnsavedModelException. Values are stored
against the record's key.
The write was validated away. A ValidationException from
setCustomField() is the field's own rules, not your form's. Read the rules the
field carries:
CustomFields::definition(Customer::class, 'account_tier')->rules;A stale instance is being read. setCustomField() clears the loaded
relation on the instance it was called on. A second instance of the same record,
loaded before the write, still holds the old values. Call fresh().
The records are reading a default. Filters match value rows, and a record that never set the field has none. A default is not stored and not filterable. See defaults are not matched.
The value was written under a different tenant. Value rows are scoped to the
tenant that wrote them, including for a global field, so a filter under globex
does not see what acme wrote.
The comparison value is a different shape. Filter values go through the same
casting a write does, so this is rarer than it looks, but a date written with a
time and filtered as a whole day will not match. A date field is normalised to
the start of the day; a datetime keeps its time.
It is a multi-select. Filtering one means contains, and it takes one option rather than a list. See multi-select fields.
Check the eager load first:
Customer::query()->with('customFieldValues')->paginate();Without it, every record loads its own values. With it, a page costs one extra query however many records it holds.
If the filter is the slow part, check which column the field uses.
text_value and json_value carry no index, so filtering a textarea or a
multi_select scans the value rows for that field. See which filters use an
index.
CannotChangeFieldTypeException names how many values are in the way. Clear
them, or migrate them, then change the type. See changing and deleting
fields.
If the field is new and the values are test data,
$definition->values()->delete() is the whole fix.
That is deliberate. Deleting a definition leaves its values, so a field removed
by mistake does not take the data with it. Run
custom-fields:prune when the removal was meant, and
check with --dry-run first.
An unsupported model key type. custom-fields.model_key_type takes int,
uuid, ulid, or string, and throws an InvalidArgumentException naming the
four.
An index name that is too long. Both migrations name their indexes from the table name. A long custom table name can still push one past MySQL's 64-character limit, and PostgreSQL truncates at 63 without saying so. Shorten the table name.
The table names changed after migrating. The package migration creates the tables named in config at the time it runs. Renaming them afterwards is a migration of your own.
InvalidTenantResolverException means the configured class is missing or does
not implement the contract, and it names what it found.
Silence is the other failure. A spatie application with the config key left at
null gets SpatieTenantResolver by detection, and an application without that
package gets NullTenantResolver, under which every field is global. If fields
are unexpectedly shared across tenants, check which resolver is bound:
app(ByRcsc\LaravelCustomFields\Contracts\TenantResolver::class)::class;