Framework/src/Http/JsonApi/JsonApiResponseFactory.php

100 lines
3.3 KiB
PHP
Raw Normal View History

2025-12-14 23:10:01 +00:00
<?php
declare(strict_types=1);
namespace Phred\Http\JsonApi;
use LogicException;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\Stream;
use Phred\Http\ApiResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Minimal JSON:API response factory stub.
* For full functionality, require "neomerx/json-api" and replace internals accordingly.
*/
class JsonApiResponseFactory implements ApiResponseFactoryInterface
{
public function ok(mixed $data, array $context = []): ResponseInterface
{
$document = $this->toResourceDocument($data, $context);
return $this->jsonApi(200, $document);
}
public function created(string $location, mixed $data, array $context = []): ResponseInterface
{
$document = $this->toResourceDocument($data, $context);
$response = $this->jsonApi(201, $document);
return $response->withHeader('Location', $location);
}
public function error(int $status, string $title, ?string $detail = null, array $meta = []): ResponseInterface
{
$payload = [
'errors' => [[
'status' => (string) $status,
'title' => $title,
'detail' => $detail,
'meta' => (object) $meta,
]],
];
return $this->jsonApi($status, $payload);
}
private function jsonApi(int $status, array $document): ResponseInterface
{
// If neomerx/json-api is installed, you can swap this simple encoding with its encoder.
$json = json_encode($document, JSON_THROW_ON_ERROR);
$stream = Stream::create($json);
return (new Response($status, ['Content-Type' => 'application/vnd.api+json']))->withBody($stream);
}
/**
* Convert domain data to a very simple JSON:API resource document.
* Context may include: 'type' (required for non-array scalars), 'id', 'includes', 'links', 'meta'.
* This is intentionally minimal until a full encoder is wired.
*
* @param mixed $data
* @param array $context
* @return array
*/
private function toResourceDocument(mixed $data, array $context): array
{
// If neomerx/json-api not present, produce a simple document requiring caller to provide 'type'.
if (!isset($context['type'])) {
// Keep developer feedback explicit to encourage proper setup.
throw new LogicException('JSON:API response requires context["type"]. Consider installing neomerx/json-api for advanced encoding.');
}
$resource = [
'type' => (string) $context['type'],
];
if (is_array($data) && array_key_exists('id', $data)) {
$resource['id'] = (string) $data['id'];
$attributes = $data;
unset($attributes['id']);
} else {
$attributes = $data;
if (isset($context['id'])) {
$resource['id'] = (string) $context['id'];
}
}
$resource['attributes'] = $attributes;
$document = ['data' => $resource];
if (!empty($context['links']) && is_array($context['links'])) {
$document['links'] = $context['links'];
}
if (!empty($context['meta']) && is_array($context['meta'])) {
$document['meta'] = $context['meta'];
}
return $document;
}
}