Events and Hooks
The package gives you three ways to run your code around an endpoint:
- Resource events, such as
searching()ordestroyedAll(): methods of the resource that run once per request, at a fixed moment. - Row hooks, such as
mutating()ordestroyed(): methods of the resource that run for each row. - Laravel events, such as
ResourceMutated: dispatched once a write is committed, for every resource.
Resource events
Override the event on the resource. Each one receives a typed payload and returns nothing:
| Event | When it runs |
|---|---|
describing(): void | once viewAny passed, before the details are built |
described(array $details): void | after the details are built |
searching(SearchContext $search): void | after validation, before any query; also for the search of an action or a metric |
searched(LengthAwarePaginator $page): void | after the query and the includes are read, before the rows are rendered |
mutatingAll(array $entries): void | inside the transaction, after the row checks, before the first write |
mutatedAll(array $operations): void | inside the transaction, after the last write |
operating(Action $action, ActionFields $fields): void | after the row checks of the action, before the first chunk |
operated(Action $action, ActionFields $fields, int $impacted): void | after the last chunk, before respond() |
destroyingAll(Collection $models), restoringAll(), forceDestroyingAll() | inside the transaction, after the row checks, before the first row |
destroyedAll(Collection $models), restoredAll(), forceDestroyedAll() | inside the transaction, after the last row |
- The events run after the entry checks and the validation. The events that receive rows also run after the row checks: a row that a policy refuses runs no event.
- An event that throws stops the endpoint. A
ValidationExceptiongives a422, anAuthorizationExceptiona403. Inside a transaction, the throw rolls it back:mutatedAll()anddestroyedAll()can undo the write. - An event cannot change what the endpoint does: its payload is read-only. Read the request with
$this->request(). operating()andoperated()run inside the transaction of an action withActionTransaction::Whole. Otherwise each chunk is committed, or each row withActionTransaction::RowandActionTransaction::None, and a throw inoperated()leaves the rows written and skipsrespond().- The queued jobs of an action run no event: the events run in the request.
Refusing a search
searching() receives a Lomkit\Rest\Search\SearchContext built from the validated search. Throw a ValidationException to refuse it before any query:
use Illuminate\Validation\ValidationException;
use Lomkit\Rest\Search\SearchContext;
public function searching(SearchContext $search): void
{
if ($search->filtersOn('from') && ! $search->filtersOn('to')) {
throw ValidationException::withMessages(['search.filters' => 'The from filter requires a to filter.']);
}
}
filtersOn() and sortsOn() take a field, a relation path such as author.last_name, or the key of an aggregate. scopes() and includes() list the names of the scopes and includes. The check also applies to the search of an action and of a metric.
Logging a deletion
use Illuminate\Database\Eloquent\Collection;
public function destroyedAll(Collection $tickets): void
{
ActivityLog::record('tickets.destroyed', $tickets->modelKeys());
}
Row hooks
Row hooks run for each row, inside the transaction of their endpoint:
| Hook | When it runs |
|---|---|
mutating(MutationNode $entry, Model $model): void | before a row of mutate is saved |
mutated(MutationNode $entry, Model $model): void | after a row of mutate is saved |
destroying(Model $model), destroyed(Model $model) | around the deletion of each row |
restoring(Model $model), restored(Model $model) | around the restore of each row |
forceDestroying(Model $model), forceDestroyed(Model $model) | around the force deletion of each row |
mutating() and mutated() receive the entry of the request as a Lomkit\Rest\Mutations\MutationNode: operation, key, attributes, pivot, withoutDetaching, relations, type, depth and path.
use Illuminate\Database\Eloquent\Model;
use Lomkit\Rest\Fields\MutationOperation;
use Lomkit\Rest\Mutations\MutationNode;
public function mutating(MutationNode $entry, Model $model): void
{
if ($entry->operation === MutationOperation::Create) {
$model->setAttribute('created_by', $this->request()->user()->id);
}
}
mutating()andmutated()run for each row that an entry writes, at every level: acreate, anupdate, an entry withattributes, and anattach,detach,syncortoggleentry whose ownrelationschange. They do not run for a target that is only linked, withoutattributesorrelations.- To keep a history row for each written row, write it in these hooks: see Recording each written row.
performDelete(Model $model),performRestore(Model $model)andperformForceDelete(Model $model)replace the write itself.- A
deleteentry of adeletable()relation inmutategoes throughperformDelete()and runs nodestroying()ordestroyed()hook.
Laravel events
After each write, the package dispatches a Laravel event, for every resource:
Event (Lomkit\Rest\Events) | Endpoint | Properties |
|---|---|---|
ResourceMutated | mutate | resource, operations (the operations of the response, as arrays) |
ResourceDestroyed, ResourceForceDestroyed | destroy, forceDestroy | resource, model (the class of the model), keys (the keys of the rows, in the order of resources) |
ResourceRestored | restore | resource, models |
ActionOperated | operate | resource, action (the class of the action), uriKey, fields (the values of the fields, by name), impacted, batch (the id of the batch, or null) |
use Illuminate\Support\Facades\Event;
use Lomkit\Rest\Events\ResourceDestroyed;
Event::listen(ResourceDestroyed::class, function (ResourceDestroyed $event) {
if ($event->resource === TicketResource::class) {
ActivityLog::record($event->resource, 'destroyed', $event->keys);
}
});
resourceis the class of the resource: filter on it.- The event is dispatched once the write is committed, once per request: never for a write that rolls back, nor for an attempt that a deadlock runs again. The events implement
ShouldDispatchAfterCommit. ActionOperatedis dispatched once the run ends without error. For a queued action, it is dispatched in the request, once the jobs are queued. The queued jobs dispatch no event.- Searches, details and metrics dispatch no event.
- A listener cannot refuse or change the write: refuse it in the resource events.
- A listener may implement
ShouldQueue.ResourceDestroyedandResourceForceDestroyedcarry keys, since a deleted row may no longer load.
Keys out of reach
When a request names keys that the perimeterQuery() of their resource does not return, the package dispatches Lomkit\Rest\Events\KeysOutOfReach, so that a listener can record it. The request still answers its 422, The selected resources.1 is invalid.:
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Lomkit\Rest\Events\KeysOutOfReach;
Event::listen(KeysOutOfReach::class, function (KeysOutOfReach $event) {
Log::warning('Keys out of reach', [
'resource' => $event->resource,
'endpoint' => $event->endpoint->value,
'keys' => $event->keys,
'paths' => $event->paths,
'user' => $event->user,
]);
});
| Property | Value |
|---|---|
resource | the class of the resource of the keys |
model | the class of its model |
endpoint | the Lomkit\Rest\Http\Routing\Endpoint of the request, such as Endpoint::Destroy |
keys | the keys out of reach, each once, at most 50 |
paths | the payload path of each refused key, such as mutate.0.relations.shelves.0.key.0, at most 50 |
capped | true when keys or paths were left out |
guard | the guard of the request |
user | the identifier of the user, or null for a guest |
- It covers the
keyof amutateentry and of a relation entry (attach,detach,update,sync,toggle,delete), theresourcesof an action, and theresourcesofdestroy,restoreandforceDestroy. - One event per resource and per request, dispatched during validation, before the
422. The response does not change. - A key refused for another reason, its type, a repeat or a list over its limit, dispatches nothing.
- A Precognition request dispatches it when it validates the keys.