100 lines
3.3 KiB
PHP
100 lines
3.3 KiB
PHP
|
|
<?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;
|
||
|
|
}
|
||
|
|
}
|