Writing
Deleting and restoring
Delete, restore and force delete rows by key with destroy, restore and forceDestroy, in one transaction with per-row policy checks.
Three endpoints manage the lifecycle of rows: destroy deletes them (a soft delete on a SoftDeletes model), restore brings soft-deleted rows back, and forceDestroy removes them for good.
routes/api.php
use Lomkit\Rest\Facades\Rest;
Rest::resource('documents', DocumentsController::class)->withSoftDeletes();
| Method | URI | Endpoint | Policy ability |
|---|---|---|---|
DELETE | /api/documents | destroy | delete |
POST | /api/documents/restore | restore | restore |
DELETE | /api/documents/force | forceDestroy | forceDelete |
destroyis registered by default.restoreandforceDestroyare opt-in:withSoftDeletes()registers both,withSoftDeletes('restore')only one.withSoftDeletes()throwsInvalidResourceExceptionwhen the routes are registered if the model does not useSoftDeletes.except('destroy')leavesdestroyout.restoreandforceDeleteexist only for a model that usesSoftDeletes. On another model, they are not gates, and the details leave them out ofgatesandauthorizations.
Request and response
DELETE /api/documents
{
"resources": [1, 2]
}
200 OK
{
"data": [
{"id": 1, "title": "Annual report", "archived": false},
{"id": 2, "title": "Budget", "archived": false}
],
"meta": {}
}
- The response holds the rows that the endpoint wrote, with the selectable fields of the resource, without gates or aggregates.
restoreandforceDestroytake the same body and answer the same way.forceDestroyaccepts live and soft-deleted rows.
Keys
resourcesis a list of keys, typed like theIDfield of the resource: an integer for integer keys, a string, a UUID or a ULID otherwise. A list, an object ornullas an entry gives a 422 underresources.0, such asThe resources.0 field must be an integer.resourcesmust be a JSON list of at mostrest.limits.sizes.resourceskeys, 1000 by default:The resources field must be a list.- A repeated key gives a 422 on every copy. UUID and ULID keys compare without case.
- The keys are read through the
perimeterQuery()of the resource, with the global scopes of the model, trashed rows included onrestoreandforceDestroy. A key that it does not return givesThe selected resources.0 is invalid.Ondestroy, a key that is already trashed gives the same 422. - A key outside the perimeter also dispatches the
KeysOutOfReachevent. A key refused for another reason, its type or a repeat, dispatches nothing. confirmationcarries the token of a confirmation. It must be a string:The confirmation field must be a string.- The endpoints take no
search. An empty array is accepted, and any other value givesThe search field is prohibited., orThe search field must be an array.fornull. Nothing is written then.
Checks and transaction
viewAnyon the resource is checked before validation.- Validation reads the rows of the keys, through the perimeter, with one query per 1,000 keys.
- Every row is checked with
delete,restoreorforceDeletebefore any row is written. A denied row answers403, and nothing is written. destroyConfirmation(),restoreConfirmation()orforceDestroyConfirmation()can ask for a confirmation: the endpoint then answers428, and nothing is written.- The rows are written in one database transaction, on the connection of the model, in the order of
resources.
Inside the transaction:
destroy | restore | forceDestroy |
|---|---|---|
destroyingAll($models) | restoringAll($models) | forceDestroyingAll($models) |
per row: destroying($model), performDelete(), destroyed($model) | per row: restoring($model), performRestore(), restored($model) | per row: forceDestroying($model), performForceDelete(), forceDestroyed($model) |
destroyedAll($models) | restoredAll($models) | forceDestroyedAll($models) |
- A failure on any row, in a hook, in a
performmethod or in the database, rolls back the rows written before it, and the request answers with the error. - An observer or a listener that runs after commit runs only when the transaction commits. The
ResourceDestroyed,ResourceRestoredandResourceForceDestroyedevents are dispatched once, after the commit. - With
transactions.attemptsabove 1, a deadlock runs the transaction again, see Deadlock retries. - A
performmethod that writes on another connection writes outside the transaction.
See Events and hooks.
Deleting children with their parent
To delete child rows in the same request as an update of their parent, use a delete entry in mutate on a relation declared deletable(), see Relations. The destroy hooks do not run there.
Errors
403 Forbidden
{
"message": "This action is unauthorized."
}
422 Unprocessable Content
{
"message": "The selected resources.0 is invalid.",
"errors": {
"resources.0": ["The selected resources.0 is invalid."]
}
}
| Status | Cause |
|---|---|
403 | viewAny is denied, or a row is denied by delete, restore or forceDelete. The message is the one of the first denied row in the order of resources |
422 | A key outside the perimeter, missing, already trashed on destroy, repeated, or of the wrong type |
422 | A search key that is not an empty array |
428 | The resource asks for a confirmation, see Confirmation |
With the DenialsRenderer registered, a 403 also lists every denied row under denials, with its path (resources.1), ability, key and message. See Policies.