Controllers
Controllers are thin orchestrators handling HTTP concerns. They delegate business logic to Commands, Queries, or Use Cases.
Responsibilities
Controllers should:
- Parse HTTP request data
- Create Command/Query objects
- Dispatch to appropriate bus
- Handle exceptions and return HTTP responses
Controllers should NOT:
- Contain business logic
- Access database directly
- Format data (use Presenters)
HTTP Controller
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$queryResponse = $this->queryBus->dispatch(new ListBooksQuery());
$viewModel = $this->presenter->present($queryResponse)->getViewModel();
$html = $this->twig->render('ListBooks.html.twig', (array) $viewModel);
return $this->responseBuilder
->withResponse($response)
->withHtml($html)
->build();
}
API Controller
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$queryResponse = $this->queryBus->dispatch(new ListBooksQuery());
$viewModel = $this->presenter->present($queryResponse)->getViewModel();
return $this->jsonSuccess($response, $viewModel);
}
Request Data Access
$id = $args['id']; // Route parameters
$page = $request->getQueryParams()['page']; // Query string
$data = (array) $request->getParsedBody(); // POST body
Request Objects
Request objects encapsulate HTTP request parsing and validation for form submissions. They provide a clean interface between HTTP data and command creation.
Structure
final readonly class CreateBookRequest
{
private function __construct(
public string $title,
public string $author,
public string $isbn
) {}
public static function fromHttpRequest(ServerRequestInterface $request): self
{
$data = (array) $request->getParsedBody();
return new self(
$data['title'] ?? '',
$data['author'] ?? '',
$data['isbn'] ?? ''
);
}
public function validate(): array
{
// Returns array of field => error message
}
public function toArray(): array
{
return [
'title' => $this->title,
'author' => $this->author,
'isbn' => $this->isbn,
];
}
}
Usage in Controllers
public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$bookRequest = CreateBookRequest::fromHttpRequest($request);
$errors = $bookRequest->validate();
if ($errors !== []) {
return $this->renderFormWithErrors($response, $bookRequest->toArray(), $errors);
}
$command = new CreateBookCommand(
$this->idGenerator->generate(),
$bookRequest->title,
$bookRequest->author,
$bookRequest->isbn
);
$this->commandBus->dispatch($command);
// ...
}
Separation of Concerns
Request objects handle HTTP-level validation (required fields, format checks), while domain validation occurs in Value Objects and Commands. This separation allows:
- Consistent error handling across similar forms
- Reusable validation logic via shared validators
- Clean controller code without inline parsing
Source Files
app/demo/Library/Presentation/Request/CreateBookRequest.phpapp/demo/Library/Presentation/Request/UpdateBookRequest.phpapp/demo/Loan/Presentation/Request/BorrowBookRequest.php
Response Patterns
// HTML
return $this->responseBuilder->withResponse($response)->withHtml($html)->build();
// JSON
return $this->jsonSuccess($response, $data);
// Redirect
return $this->responseBuilder
->withResponse($response)
->withStatus(302)
->withHeader('Location', '/books')
->build();
See Also
- Command Bus - Command dispatching
- Query Bus - Query dispatching
- Presenters - Transform responses to ViewModels
- Twig Integration - Template rendering
- HTTP Responses - Response building patterns