Phred/src/Http/Responses/DelegatingApiResponseFactory.php
Funky Waddle fd1c9d23df refactor(core): enforce SOLID across HTTP pipeline; add small contracts and defaults; align tests
- Introduce small interfaces and default adapters (DIP):
  - Support\Contracts\ConfigInterface + Support\DefaultConfig
  - Http\Contracts\ErrorFormatNegotiatorInterface + Http\Support\DefaultErrorFormatNegotiator
  - Http\Contracts\RequestIdProviderInterface + Http\Support\DefaultRequestIdProvider
  - Http\Contracts\ExceptionToStatusMapperInterface + Http\Support\DefaultExceptionToStatusMapper
- Kernel: bind new contracts in the container; keep DelegatingApiResponseFactory wiring
- ContentNegotiationMiddleware: depend on ConfigInterface + negotiator; honor Accept for JSON:API
- ProblemDetailsMiddleware: inject negotiator + config; split into small helpers; deterministic content negotiation; stable Whoops HTML; include X-Request-Id
- DispatchMiddleware: SRP refactor into small methods; remove hidden coupling; normalize non-Response returns
- Add/adjust tests:
  - tests/ErrorHandlingTest.php for problem details, JSON:API errors, and Whoops HTML
  - tests/ContentNegotiationTest.php for format selection
  - tests/MakeCommandTest.php aligned with create:command scaffolder
- Docs/Meta: update README and MILESTONES; .gitignore to ignore .junie.json

No runtime behavior changes intended beyond clearer DI boundaries and content-negotiation determinism. All tests green.
2025-12-15 09:15:49 -06:00

55 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Phred\Http\Responses;
use Phred\Http\Contracts\ApiResponseFactoryInterface;
use Phred\Http\Middleware\ContentNegotiationMiddleware as Negotiation;
use Phred\Http\RequestContext;
use Psr\Http\Message\ResponseInterface;
/**
* Delegates to REST or JSON:API factory depending on current request format.
* Controllers receive this via DI and call its methods; it inspects
* RequestContext (set in DispatchMiddleware) to choose the underlying factory.
*/
final class DelegatingApiResponseFactory implements ApiResponseFactoryInterface
{
public function __construct(
private RestResponseFactory $rest,
private JsonApiResponseFactory $jsonapi
) {}
public function ok(array $data = []): ResponseInterface
{
return $this->delegate()->ok($data);
}
public function created(array $data = [], ?string $location = null): ResponseInterface
{
return $this->delegate()->created($data, $location);
}
public function noContent(): ResponseInterface
{
return $this->delegate()->noContent();
}
public function error(int $status, string $title, ?string $detail = null, array $extra = []): ResponseInterface
{
return $this->delegate()->error($status, $title, $detail, $extra);
}
public function fromArray(array $payload, int $status = 200): ResponseInterface
{
return $this->delegate()->fromArray($payload, $status);
}
private function delegate(): ApiResponseFactoryInterface
{
$req = RequestContext::get();
$format = $req?->getAttribute(Negotiation::ATTR_API_FORMAT) ?? 'rest';
return $format === 'jsonapi' ? $this->jsonapi : $this->rest;
}
}