Skip to content

Commit 6038a8b

Browse files
committed
Add JSON:API object meta support
1 parent fb724cd commit 6038a8b

File tree

17 files changed

+110
-76
lines changed

17 files changed

+110
-76
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ and this project adheres to
3030

3131
### Added
3232

33+
- Add `Context::createResponse()` method for building JSON:API responses with
34+
automatic `jsonapi` object inclusion
35+
- Add `JsonApi::meta()` method for including meta information in the `jsonapi`
36+
object
3337
- Add full support for JSON:API profiles:
3438
- Parse profile URIs from `Accept` header
3539
- `Context::profileRequested(string $uri): bool` - check if a profile was

docs/context.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,8 @@ class Context
9494

9595
// Activate a JSON:API profile for the current response
9696
public function activateProfile(string $uri): static;
97+
98+
// Create a JSON:API response with document data
99+
public function createResponse(array $document): Response;
97100
}
98101
```

docs/extensions.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ use Psr\Http\Message\ResponseInterface;
2121
use Tobyz\JsonApiServer\Context\Context;
2222
use Tobyz\JsonApiServer\Extension\Extension;
2323

24-
use function Tobyz\JsonApiServer\json_api_response;
25-
2624
class MyExtension extends Extension
2725
{
2826
public function uri(): string
@@ -33,7 +31,7 @@ class MyExtension extends Extension
3331
public function handle(Context $context): ?ResponseInterface
3432
{
3533
if ($context->path() === 'my-extension') {
36-
return json_api_response([
34+
return $context->createResponse([
3735
'my-extension:greeting' => 'Hello world!',
3836
]);
3937
}

src/Context.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use ArrayObject;
66
use HttpAccept\AcceptParser;
7+
use Nyholm\Psr7\Response;
8+
use Nyholm\Psr7\Stream;
9+
use Psr\Http\Message\ResponseInterface;
710
use Psr\Http\Message\ServerRequestInterface;
811
use RuntimeException;
912
use Tobyz\JsonApiServer\Endpoint\Endpoint;
@@ -412,4 +415,45 @@ public function forModel(array $collections, ?object $model): static
412415
'No resource type defined to represent model ' . get_class($model),
413416
);
414417
}
418+
419+
/**
420+
* Create a JSON:API response.
421+
*/
422+
public function createResponse(array $document = []): ResponseInterface
423+
{
424+
$response = (new Response())->withHeader('Content-Type', $this->api::MEDIA_TYPE);
425+
426+
if ($document) {
427+
$jsonapi = ['version' => $this->api::VERSION];
428+
429+
if ($meta = $this->api->serializeMeta($this)) {
430+
$jsonapi['meta'] = $meta;
431+
}
432+
433+
$document += ['jsonapi' => $jsonapi];
434+
435+
if ($meta = $this->documentMeta->getArrayCopy()) {
436+
$document['meta'] = array_merge($document['meta'] ?? [], $meta);
437+
}
438+
439+
if ($links = $this->documentLinks->getArrayCopy()) {
440+
$document['links'] = array_merge($document['links'] ?? [], $links);
441+
}
442+
443+
$response = $response->withBody(
444+
Stream::create(
445+
json_encode(
446+
$document,
447+
JSON_HEX_TAG |
448+
JSON_HEX_APOS |
449+
JSON_HEX_AMP |
450+
JSON_HEX_QUOT |
451+
JSON_UNESCAPED_SLASHES,
452+
),
453+
),
454+
);
455+
}
456+
457+
return $response;
458+
}
415459
}

src/Endpoint/CollectionAction.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
use Tobyz\JsonApiServer\Schema\Concerns\HasDescription;
1717
use Tobyz\JsonApiServer\Schema\Concerns\HasVisibility;
1818

19-
use function Tobyz\JsonApiServer\json_api_response;
20-
2119
class CollectionAction implements Endpoint, OpenApiPathsProvider
2220
{
2321
use HasVisibility;
@@ -62,7 +60,7 @@ public function handle(Context $context): ?ResponseInterface
6260

6361
($this->handler)($context);
6462

65-
$response = json_api_response($this->buildDocument($context), status: 204);
63+
$response = $context->createResponse($this->buildDocument($context))->withStatus(204);
6664

6765
return $this->applyResponseHooks($response, $context);
6866
}

src/Endpoint/Concerns/BuildsDocument.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,10 @@ private function buildDocument(Context $context): array
1313
{
1414
$document = [];
1515

16-
if ($meta = $context->documentMeta->getArrayCopy()) {
17-
$document['meta'] = $meta;
18-
}
19-
2016
foreach ($this->serializeMeta($context) as $key => $value) {
2117
$document['meta'][$key] = $value;
2218
}
2319

24-
if ($links = $context->documentLinks->getArrayCopy()) {
25-
$document['links'] = $links;
26-
}
27-
2820
return $document;
2921
}
3022
}

src/Endpoint/Create.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use Tobyz\JsonApiServer\Schema\Concerns\HasVisibility;
2323

2424
use function Tobyz\JsonApiServer\has_value;
25-
use function Tobyz\JsonApiServer\json_api_response;
2625
use function Tobyz\JsonApiServer\set_value;
2726

2827
class Create implements Endpoint, OpenApiPathsProvider
@@ -83,14 +82,14 @@ public function handle(Context $context): ?ResponseInterface
8382

8483
if ($asyncResult !== null) {
8584
if (is_string($asyncResult)) {
86-
$response = json_api_response($this->buildDocument($context))->withHeader(
85+
$response = $context->createResponse($this->buildDocument($context))->withHeader(
8786
'Location',
8887
$context->api->basePath . '/' . ltrim($asyncResult, '/'),
8988
);
9089
} else {
9190
$context = $context->forModel([$this->asyncCollection], $asyncResult);
9291

93-
$response = json_api_response(
92+
$response = $context->createResponse(
9493
$this->buildResourceDocument($asyncResult, $context),
9594
)->withHeader(
9695
'Content-Location',
@@ -110,9 +109,9 @@ public function handle(Context $context): ?ResponseInterface
110109

111110
$this->saveFields($context, $data, true);
112111

113-
$response = json_api_response(
114-
$document = $this->buildResourceDocument($model, $context),
115-
)->withStatus(201);
112+
$response = $context
113+
->createResponse($document = $this->buildResourceDocument($model, $context))
114+
->withStatus(201);
116115

117116
if ($location = $document['data']['links']['self'] ?? null) {
118117
$response = $response->withHeader('Location', $location);

src/Endpoint/Delete.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
use Tobyz\JsonApiServer\Schema\Concerns\HasDescription;
1919
use Tobyz\JsonApiServer\Schema\Concerns\HasVisibility;
2020

21-
use function Tobyz\JsonApiServer\json_api_response;
22-
2321
class Delete implements Endpoint, OpenApiPathsProvider
2422
{
2523
use HasDescription;
@@ -64,7 +62,7 @@ public function handle(Context $context): ?ResponseInterface
6462

6563
$resource->delete($model, $context);
6664

67-
$response = json_api_response($this->buildDocument($context), status: 204);
65+
$response = $context->createResponse($this->buildDocument($context))->withStatus(204);
6866

6967
return $this->applyResponseHooks($response, $context);
7068
}

src/Endpoint/Index.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
use Tobyz\JsonApiServer\Schema\Concerns\HasDescription;
2323
use Tobyz\JsonApiServer\Schema\Concerns\HasVisibility;
2424

25-
use function Tobyz\JsonApiServer\json_api_response;
26-
2725
class Index implements Endpoint, OpenApiPathsProvider
2826
{
2927
use HasDescription;
@@ -99,7 +97,7 @@ public function handle(Context $context): ?Response
9997

10098
$document['links']['self'] ??= $context->currentUrl();
10199

102-
$response = json_api_response($document);
100+
$response = $context->createResponse($document);
103101

104102
return $this->applyResponseHooks($response, $context);
105103
}

src/Endpoint/ResourceAction.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
use Tobyz\JsonApiServer\Schema\Concerns\HasDescription;
1919
use Tobyz\JsonApiServer\Schema\Concerns\HasVisibility;
2020

21-
use function Tobyz\JsonApiServer\json_api_response;
22-
2321
class ResourceAction implements Endpoint, OpenApiPathsProvider
2422
{
2523
use HasVisibility;
@@ -72,7 +70,7 @@ public function handle(Context $context): ?ResponseInterface
7270

7371
($this->handler)($model, $context);
7472

75-
$response = json_api_response($this->buildResourceDocument($model, $context));
73+
$response = $context->createResponse($this->buildResourceDocument($model, $context));
7674

7775
return $this->applyResponseHooks($response, $context);
7876
}

0 commit comments

Comments
 (0)