Requests

Three sources of input, and no fourth

A Request describes what was asked for and nothing else. It has three arrays on it, and where a value came from is visible at the point you read it.

$id    = $this->request->params['id'] ?? '';        // from the path
$page  = $this->request->query['page'] ?? '1';      // from the query string
$title = $this->request->payload['title'] ?? '';    // from the body

They are plain public arrays, read with ??. There is deliberately no input() accessor over the top: it would be a second way to read the same value, and it would hide which of the three it came from.

params — the path

Whatever a dynamic route captured, passed through exactly as it was sent:

$router->get('/posts/{slug}', Actions\Post\Show::class);

// GET /posts/My-First-Post
$this->request->params['slug'];   // 'My-First-Post'

Route matching ignores case, but it does that by comparing case-insensitively rather than by rewriting the URL — so a slug or a UUID survives intact.

query — the query string

Parsed off the URL and never used for matching, so /posts?page=2 matches the route /posts:

// GET /posts?page=2&q=tether
$this->request->query;   // ['page' => '2', 'q' => 'tether']

An index page pages, filters and searches from here. An absent query string is an empty array, never unset.

payload — the body

Parsed for every verb, whatever the client sent:

// a form POST, a form-encoded PUT, or a JSON PATCH
$this->request->payload['title'] ?? '';

JSON bodies are decoded. Form-encoded bodies are parsed whichever verb carried them — PHP only fills $_POST for a POST, so an update sent as a form-encoded PUT would otherwise arrive empty. Multipart bodies still come from PHP, so file uploads are not lost.

All three are populated before any middleware runs, so a middleware can read the body too.

What a Domain sees

Nothing. A Domain knows no HTTP. The Action reads the request and hands over what the Domain needs, through its constructor:

public function __construct(protected Request $request)
{
    $this->domain = new UpdateDomain(
        $request->params['id'] ?? '',
        $request->payload,
    );
    $this->responder = new UpdateResponder($request);
}

Through the constructor and not through handle(): the base Domain declares handle(): DomainResult with no parameters, and PHP will not let an override add a required one.