Request Binding
Action method parameters are resolved and type-coerced automatically from the incoming request. You rarely need to touch the raw request object.
Resolution order
For each parameter of a controller action, the framework checks sources in this order:
- Route path parameters — extracted and typed from the URL (e.g.
{int:id}→int $id). - Query string — scalar parameters matched by name from
?key=value. - Parsed request body — for
POST,PUT,PATCH,DELETErequests. - PSR-7 request — if the parameter type is
ServerRequestInterface, the request object is injected directly. - DTO / object — any non-scalar class is treated as a request object and constructed from the flat input.
Scalar normalization
All scalar values are automatically normalized:
| PHP type | Behavior |
|---|---|
string |
Trimmed. |
int |
Digits-only strings cast to int; non-numeric input → null. |
float |
Numeric strings cast to float; non-numeric → null. |
bool |
"1", "true", "on", "yes" → true; "0", "false", "off", "no" → false. |
Returning null for invalid numeric input lets your action or DTO detect missing/malformed data explicitly:
public function show(?int $id): ActionResponse
{
if ($id === null) {
return $this->view(name: 'errors/not-found', statusCode: StatusCode::NotFound);
}
// ...
}
DTO binding
Non-scalar parameters are constructed using named constructor arguments:
final class CreateArticleRequest
{
public function __construct(
public readonly string $title,
public readonly string $body,
public readonly int $categoryId,
) {}
}
// Controller action:
public function store(CreateArticleRequest $request): ActionResponse { ... }
The input keys title, body, and categoryId (from the request body) are mapped to constructor parameters by name.
Dotted keys for nested objects
final class AddressRequest
{
public function __construct(
public readonly string $street,
public readonly string $city,
) {}
}
final class ShippingRequest
{
public function __construct(
public readonly AddressRequest $address,
) {}
}
HTML form fields:
<input name="address.street" value="123 Main St">
<input name="address.city" value="Springfield">
Array binding
final class BatchRequest
{
/**
* @param array<TagRequest> $tags
*/
public function __construct(
public readonly array $tags,
) {}
}
HTML:
<input name="tags[0][name]" value="php">
<input name="tags[1][name]" value="mvc">
The @param array<Type> docblock is required to infer the array element type.
Injecting the PSR-7 request
For cases where you need the raw request (e.g. reading headers):
use Psr\Http\Message\ServerRequestInterface;
public function upload(ServerRequestInterface $request): ActionResponse
{
$contentType = $request->getHeaderLine('Content-Type');
// ...
}
The framework detects the ServerRequestInterface type hint and injects the current request object directly.