Actions
new ComponentAction( name: 'rename', // [a-z][a-zA-Z0-9_-]{0,63} permission: 'projects.update', // required, never empty handler: $this->rename(...), // Closure(ComponentRequest): ComponentResponse rules: ['name' => 'required|string|max_length[100]'], mutates: true, // default);| Parameter | Description |
|---|---|
name |
the value of _action; an invalid name throws an InvalidArgumentException |
permission |
checked with the gate before the handler runs (403 otherwise). Gate::AUTHENTICATED ('@authenticated') allows every logged in user; use it only for the user’s own data |
handler |
usually a private method as first-class callable; other public methods of the component are never reachable |
rules |
CodeIgniter validation rules per input field (string, list, or ['label' => …, 'rules' => …]); failures answer 422 with field errors |
mutates |
true: the handler runs in a database transaction and the call is audited as component.action. false for read-only actions (loading, searching, counting) |
The handler only receives input that has a rule; everything else is dropped. An action without rules receives no input at all.
$request->string('name'); // validated scalar as string, '' if missing$request->string('mode', 'compact'); // with default$request->input; // all validated input, nested fields includedDynamic actions
Section titled “Dynamic actions”actions() runs on every request, so actions can be built from data. The
task inbox (Anvil\Tasks\Ui\InboxComponent) creates one action per open task
(complete-<uuid>) with the rules of that task’s form. Keep actions()
cheap: it is called before the permission check.
Errors
Section titled “Errors”Throw Anvil\Endpoint\EndpointException from a handler:
| Factory | Status | Shown to the user |
|---|---|---|
EndpointException::forValidation(['field' => 'Message']) |
422 | messages next to the fields, see Forms in a component |
EndpointException::forForbidden($reason) |
403 | generic message |
EndpointException::forNotFound($reason) |
404 | generic message |
$reason is internal: it is written to the security log, never sent to the
browser. In a mutating action, every exception rolls back the transaction,
including changes made before it was thrown.
Choosing the permission
Section titled “Choosing the permission”| Action | Permission |
|---|---|
| reads or changes data of a resource | the permission of the data: tickets.view, tickets.update |
| something special, e.g. approving | an own permission, e.g. tickets.approve; add it to Config\Anvil::$permissions so it appears in the role editor |
| only the user’s own data (profile, own preferences) | Gate::AUTHENTICATED |
The permission of the action is checked in addition to the permission of the page. A policy for the record is not checked automatically – call the gate in the handler when the decision depends on the record:
private function close(ComponentRequest $request): ComponentResponse{ $ticket = (new TicketModel())->findByUuid($request->claims->record) ?? throw EndpointException::forNotFound('ticket');
if (! service('gate')->allows('tickets.update', $ticket)) { throw EndpointException::forForbidden('policy'); }
// …}Records
Section titled “Records”- Bound to one record: take it from
$request->claims->record, never from the input. - Chosen by the user (a list): send its UUID with the rule
valid_uuidand look it up through the Anvil model, which applies the tenant scope. - Never accept internal ids in the input.