| name | acc-create-responder |
| description | Generates ADR Responder classes for PHP 8.5. Creates HTTP response builders with PSR-7/PSR-17 support. Includes unit tests. |
Responder Generator
Generate ADR-compliant Responder classes for HTTP response building.
Responder Characteristics
- Response Building: Creates complete HTTP Response (status, headers, body)
- No Business Logic: Only format and transform data
- No Domain Access: No repository or service calls
- Error Mapping: Maps domain errors to HTTP status codes
- Content Type: Sets appropriate Content-Type header
- PSR Compliance: Uses PSR-7 and PSR-17 interfaces
Template
<?php
declare(strict_types=1);
namespace Presentation\Api\{Context}\{Action};
use Application\{Context}\UseCase\{Action}\{Action}Result;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
final readonly class {Action}Responder
{
public function __construct(
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
) {
}
public function respond({Action}Result $result): ResponseInterface
{
if ($result->isFailure()) {
return $this->handleFailure($result);
}
return $this->success($result);
}
private function success({Action}Result $result): ResponseInterface
{
{successResponse}
}
private function handleFailure({Action}Result $result): ResponseInterface
{
return match ($result->failureReason()) {
{errorMapping}
default => $this->badRequest($result->errorMessage()),
};
}
private function json(array $data, int $status = 200): ResponseInterface
{
$body = $this->streamFactory->createStream(
json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE)
);
return $this->responseFactory->createResponse($status)
->withHeader('Content-Type', 'application/json; charset=utf-8')
->withBody($body);
}
{helperMethods}
}
Test Template
<?php
declare(strict_types=1);
namespace Tests\Unit\Presentation\Api\{Context}\{Action};
use Application\{Context}\UseCase\{Action}\{Action}Result;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use Presentation\Api\{Context}\{Action}\{Action}Responder;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\;
()
({}::)
{Action}ResponderTest TestCase
{
ResponseFactoryInterface ;
StreamFactoryInterface ;
{Action}Responder ;
{
->responseFactory = ->(::);
->streamFactory = ->(::);
->responder = {Action}(
->responseFactory,
->streamFactory,
);
->();
}
{ExpectedStatus}():
{
= {Action}::({successData});
= ->responder->();
::({expectedStatusCode}, ->());
}
{failureTests}
{
= ->(::);
->streamFactory->()->();
= ->(::);
->()->();
->()->();
->()->(
fn () => ->responseFactory->lastStatus ??
);
->responseFactory->()->(
function ( ) ($) {
$->-> = $;
= ;
->()->();
;
}
);
}
}
Responder Patterns
Create Responder (201)
<?php
declare(strict_types=1);
namespace Presentation\Api\User\Create;
use Application\User\UseCase\CreateUser\CreateUserResult;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
final readonly class CreateUserResponder
{
public function __construct(
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
) {
}
public function respond(CreateUserResult $result): ResponseInterface
{
if ($result->isFailure()) {
return match (->()) {
=> ->(),
=> ->(),
=> ->(->()),
};
}
->([
=> ->(),
=> ->(),
]);
}
{
->(, );
}
{
->([ => ], );
}
{
->([ => ], );
}
{
= ->streamFactory->(
(, JSON_THROW_ON_ERROR)
);
->responseFactory->()
->(, )
->();
}
}
Get Responder (200/404)
<?php
declare(strict_types=1);
namespace Presentation\Api\User\GetById;
use Application\User\UseCase\GetUserById\GetUserByIdResult;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
final readonly class GetUserByIdResponder
{
public function __construct(
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
) {
}
public function respond(GetUserByIdResult $result): ResponseInterface
{
if ($result->isNotFound()) {
return ->();
}
= ->();
->([
=> ->()->(),
=> ->()->(),
=> ->(),
=> ->()->(),
]);
}
{
->([ => ], );
}
{
= ->streamFactory->(
(, JSON_THROW_ON_ERROR)
);
->responseFactory->()
->(, )
->();
}
}
List Responder with Pagination
<?php
declare(strict_types=1);
namespace Presentation\Api\User\ListAll;
use Application\User\UseCase\ListUsers\ListUsersResult;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
final readonly class ListUsersResponder
{
public function __construct(
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
) {
}
public function respond(ListUsersResult $result): ResponseInterface
{
$users = array_map(
fn ($user) => [
'id' => ->()->(),
=> ->()->(),
=> ->(),
],
->()
);
->([
=> ,
=> [
=> ->(),
=> ->(),
=> ->(),
=> ->(),
],
]);
}
{
= ->streamFactory->(
(, JSON_THROW_ON_ERROR)
);
->responseFactory->()
->(, )
->();
}
}
Delete Responder (204)
<?php
declare(strict_types=1);
namespace Presentation\Api\User\Delete;
use Application\User\UseCase\DeleteUser\DeleteUserResult;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
final readonly class DeleteUserResponder
{
public function __construct(
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
) {
}
public function respond(DeleteUserResult $result): ResponseInterface
{
if ($result->isNotFound()) {
return $this->();
}
(->()) {
->(->());
}
->();
}
{
->responseFactory->();
}
{
->([ => ], );
}
{
->([ => ], );
}
{
= ->streamFactory->(
(, JSON_THROW_ON_ERROR)
);
->responseFactory->()
->(, )
->();
}
}
HTTP Status Mapping
| Domain Condition | HTTP Status | Method |
|---|
| Success (create) | 201 | created() |
| Success (read) | 200 | json() |
| Success (update) | 200 | json() |
| Success (delete) | 204 | noContent() |
| Not found | 404 | notFound() |
| Already exists | 409 | conflict() |
| Validation error | 422 | unprocessableEntity() |
| Invalid input | 400 | badRequest() |
| Unauthorized | 401 | unauthorized() |
| Forbidden | 403 | forbidden() |
File Placement
| Component | Path |
|---|
| Responder | src/Presentation/Api/{Context}/{Action}/{Action}Responder.php |
| Interface | src/Presentation/Shared/Responder/ResponderInterface.php |
| Abstract | src/Presentation/Shared/Responder/AbstractJsonResponder.php |
| Test | tests/Unit/Presentation/Api/{Context}/{Action}/{Action}ResponderTest.php |
Generation Instructions
When asked to create a Responder:
- Identify operation type (create, read, update, delete)
- Determine success status (201, 200, 204)
- List possible failures and their HTTP codes
- Define response structure (what data to return)
- Generate Responder class with proper namespace
- Generate test for each status code path
Naming Conventions
| HTTP Method | Responder Name | Success Status |
|---|
| GET (single) | Get{Resource}ByIdResponder | 200 |
| GET (list) | List{Resource}sResponder | 200 |
| POST | Create{Resource}Responder | 201 |
| PUT | Update{Resource}Responder | 200 |
| PATCH | Patch{Resource}Responder | 200 |
| DELETE | Delete{Resource}Responder | 204 |
References
For detailed patterns and examples:
references/templates.md — Additional Responder templates
references/examples.md — Real-world Responder examples