Skip to content

Dispatcher

The Dispatcher sits at the centre of every request. It matches the route, resolves all controller method parameters, invokes the method, and converts the return value into a PSR-7 response.

Parameters are resolved in priority order:

ConditionResolution
Parameter has #[Guards(GuardClass::class)]Guard is instantiated and resolve($request) is called
Type is ServerRequestInterfaceThe raw PSR-7 request is injected
Type is UploadedFileInterfaceThe uploaded file matching the parameter name is injected
Name matches a route segment (/users/{id})Value is cast to int, float, bool, or string
Type is a class annotated with #[Dto]Body is decoded and hydrated+validated via the Hydrator
Type is any other non-builtin classResolved via $container->make()
Name matches a query parameterValue is cast and injected
Parameter has a default valueDefault is used

The return value of the controller method determines the response:

Return typeResponse
ResponseInterfaceReturned as-is
null / voidEmpty body with the route’s status code
arrayJSON-encoded, Content-Type: application/json
Object annotated with #[ResponseDto]Serialized to array by the Serializer, then JSON-encoded

Guards let you authenticate or authorise a request and inject the resolved principal into the controller:

use Antares\Http\Attributes\Guards;
final class JwtGuard
{
public function __construct(private readonly JwtService $jwt) {}
public function resolve(ServerRequestInterface $request): AuthUser
{
$token = ltrim($request->getHeaderLine('Authorization'), 'Bearer ');
return $this->jwt->decode($token) ?? throw new HttpException(401, 'Unauthorized');
}
}
#[Post('/posts', 201)]
public function store(
#[Guards(JwtGuard::class)] AuthUser $user,
CreatePostRequest $request,
): PostResponse {
// $user is the decoded JWT principal
}

The guard runs before any other parameter is resolved. If it throws, the request is rejected immediately.

Set response headers from anywhere in your application:

use Antares\Http\ResponseBag;
ResponseBag::header('X-Request-Id', uniqid());
ResponseBag::header('X-Rate-Limit-Remaining', '99');

Headers are applied to the final response and cleared after each request.