›
byrcsc/laravel-custom-fields · 1.x
Hook the definition and value models with Laravel's standard Eloquent events; the package ships no event classes of its own.
The package dispatches no events of its own. CustomFieldDefinition and
CustomFieldValue are ordinary Eloquent models, so they fire the standard model
events, and those are the hook.
| Event | Fired on |
|---|---|
creating, created | A value or definition written for the first time |
updating, updated | An existing row changed |
saving, saved | Either of the above |
deleting, deleted | A row removed |
There are no retrieved-time hooks worth relying on for values, because reads
go through a relation that may be eager loaded or already in memory.
The common case is keeping something else in step with a custom field: a search index, a cache, a denormalised column.
use ByRcsc\LaravelCustomFields\Models\CustomFieldValue;
CustomFieldValue::saved(function (CustomFieldValue $value): void {
$value->model?->searchable();
});
CustomFieldValue::deleted(function (CustomFieldValue $value): void {
$value->model?->searchable();
});Register these in a service provider's boot(). The model relation is a
MorphTo back to the record, and definition is a BelongsTo to the field.
Handle both halves. Clearing a field deletes its row rather than writing a null,
so a listener watching only saved misses every clear.
definition is nullable, because a value can outlive the field that gave it
meaning. Check it before reading through it:
CustomFieldValue::saved(function (CustomFieldValue $value): void {
$key = $value->definition?->key;
if ($key === 'account_tier') {
// ...
}
});An observer class works the same way and is easier to test:
use App\Observers\CustomFieldValueObserver;
use ByRcsc\LaravelCustomFields\Models\CustomFieldValue;
CustomFieldValue::observe(CustomFieldValueObserver::class);Register it in a service provider's boot(). The #[ObservedBy] attribute is
not an option, since it goes on the model class and the model belongs to the
package.
Everything the trait writes fires events. setCustomField() and
setCustomFields() save through the model, and clearing a field deletes through
it.
custom-fields:prune deletes one row at a time rather
than in one statement, specifically so each row fires its deleting and
deleted events. A listener keeping a search index in step would otherwise see
every delete the trait makes and none of the command's.
What fires nothing is a write made around the models: a mass
query()->update() or ->delete() against either table, a saveQuietly(), or
anything inside withoutEvents(). Two package behaviours are built on model
events and share that blind spot: the type-change
guard and the
resolved-definitions cache.
The package itself listens to saved and deleted on CustomFieldDefinition
to clear its cache, and to updating to refuse a type change. Your own
listeners run alongside those.
A listener that throws from updating or saving cancels the save, which is
one way to add a rule of your own about which fields may change:
use ByRcsc\LaravelCustomFields\Models\CustomFieldDefinition;
CustomFieldDefinition::updating(function (CustomFieldDefinition $definition): void {
if ($definition->isDirty('key')) {
throw new RuntimeException('Field keys are fixed once created.');
}
});