RRelayer
Home/Features

Server Actions#

A server action is a closure that runs when a form bearing an issued token is submitted. CSRF verification is automatic, and the handler runs before render().

A server action handler is void. Declare a redirect with $ctx->redirect() or header()+exit, and a 4xx/5xx abort with $ctx->abort() / $ctx->notFound() (see "Redirects and aborts" below). An API route (route.php) is a different contract where the handler returns a Response (→ API Routes).

Function style#

<?php
use App\Service\UserRepository;
use Polidog\Relayer\Router\Component\PageContext;

return function (PageContext $ctx, UserRepository $users): Closure {
    $save = $ctx->action('save', function (array $form) use ($users, $ctx): void {
        $users->create($form['name']);
        $ctx->redirect('/users'); // 303 See Other (PRG)
    });

    return fn () => (
        <form action={$save}>
            <input name="name" />
            <button>save</button>
        </form>
    );
};

$ctx->action(name, handler) registers it and returns the token to embed in the form. Since the factory re-runs on every request, the token only needs to encode (pageId, name).

Class style#

public function render(): Element
{
    return (
        <form method="post">
            <input type="hidden" name="_usephp_action"
                   value={$this->action([$this, 'save'])} />
            <input name="title" />
        </form>
    );
}

public function save(array $form): void
{
    // process $form['title']
    header('Location: /dashboard', true, 303);
    exit;
}

Redirects and aborts#

Control flow inside a handler/factory splits into three.

PurposeCallExceptionDefault status
Send to another URL (3xx)$ctx->redirect($path)RedirectException303 See Other
Abort with an error (4xx/5xx)$ctx->abort($status)HttpExceptionmust be specified
Assert "not found"$ctx->notFound()HttpException404 (= abort(404))
Normalreturn an element200

$ctx->redirect('/path') throws a RedirectException, which AppRouter converts into a Location response (default 303, ideal for Post/Redirect/Get after a POST). No code after it runs.

$ctx->abort($status) / $ctx->notFound() throw an HttpException, and AppRouter sets the status and renders error.psx (or the built-in error page if absent). The page author declares "intent" rather than touching http_response_code() directly.

$post = $repo->find($ctx->params['id']) ?? $ctx->notFound();
if ($post->isDraft && null === $ctx->user()) {
    $ctx->abort(403);
}

abort() is for 4xx/5xx only. Passing a 3xx throws an InvalidArgumentException (use redirect() for redirects, return an element for success). HttpException carries a standard reason phrase, and for statuses other than 404 the status/message is passed to error.psx (404 is always the unified Not Found page).

Also valid in API routes (route.php): within a handler, $ctx->abort() / notFound() are converted not to an HTML error page but to JSON of {"error":"<reason>"} plus the corresponding status (the same API/HTML boundary as the JSON-ification of authentication failures → API Routes).

If the CSRF token is invalid, it returns 403.

Last updated: 2026-05-19
Change history (1)
  • Created