Skip to content

Commit 6ee19ab

Browse files
committed
add flag metadata + few fixes + headers/status
1 parent ea5077d commit 6ee19ab

28 files changed

+436
-201
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@
110110
"symfony/deprecation-contracts": "^3.1",
111111
"symfony/http-foundation": "^6.4 || ^7.0",
112112
"symfony/http-kernel": "^6.4 || ^7.0",
113-
"symfony/json-streamer": "^7.3",
114113
"symfony/property-access": "^6.4 || ^7.0",
115114
"symfony/property-info": "^6.4 || ^7.1",
116115
"symfony/serializer": "^6.4 || ^7.0",
@@ -173,9 +172,10 @@
173172
"symfony/expression-language": "^6.4 || ^7.0",
174173
"symfony/finder": "^6.4 || ^7.0",
175174
"symfony/form": "^6.4 || ^7.0",
176-
"symfony/framework-bundle": "^6.4 || ^7.0",
175+
"symfony/framework-bundle": "7.4.x-dev",
177176
"symfony/http-client": "^6.4 || ^7.0",
178177
"symfony/intl": "^6.4 || ^7.0",
178+
"symfony/json-streamer": "7.4.x-dev",
179179
"symfony/maker-bundle": "^1.24",
180180
"symfony/mercure-bundle": "*",
181181
"symfony/messenger": "^6.4 || ^7.0",

src/Hydra/Collection.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
declare(strict_types=1);
413

514
namespace ApiPlatform\Hydra;

src/Hydra/CollectionId.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
declare(strict_types=1);
413

514
namespace ApiPlatform\Hydra;

src/Hydra/State/JsonStreamerProcessor.php

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,106 @@
1919
use ApiPlatform\Hydra\PartialCollectionView;
2020
use ApiPlatform\Metadata\CollectionOperationInterface;
2121
use ApiPlatform\Metadata\Error;
22+
use ApiPlatform\Metadata\IriConverterInterface;
2223
use ApiPlatform\Metadata\Operation;
24+
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
2325
use ApiPlatform\Metadata\QueryParameterInterface;
26+
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2427
use ApiPlatform\Metadata\UrlGeneratorInterface;
2528
use ApiPlatform\Metadata\Util\IriHelper;
2629
use ApiPlatform\State\Pagination\PaginatorInterface;
2730
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
2831
use ApiPlatform\State\ProcessorInterface;
32+
use ApiPlatform\State\Util\HttpResponseHeadersTrait;
33+
use ApiPlatform\State\Util\HttpResponseStatusTrait;
2934
use Symfony\Component\HttpFoundation\Response;
3035
use Symfony\Component\HttpFoundation\StreamedResponse;
3136
use Symfony\Component\JsonStreamer\StreamWriterInterface;
3237
use Symfony\Component\TypeInfo\Type;
3338

39+
/**
40+
* @implements ProcessorInterface<mixed,mixed>
41+
*/
3442
final class JsonStreamerProcessor implements ProcessorInterface
3543
{
44+
use HttpResponseHeadersTrait;
45+
use HttpResponseStatusTrait;
46+
47+
/**
48+
* @param ProcessorInterface<mixed,mixed> $processor
49+
* @param StreamWriterInterface<array<string,mixed>> $jsonStreamer
50+
*/
3651
public function __construct(
3752
private readonly ProcessorInterface $processor,
3853
private readonly StreamWriterInterface $jsonStreamer,
54+
?IriConverterInterface $iriConverter = null,
55+
?ResourceClassResolverInterface $resourceClassResolver = null,
56+
?OperationMetadataFactoryInterface $operationMetadataFactory = null,
3957
private readonly string $pageParameterName = 'page',
4058
private readonly string $enabledParameterName = 'pagination',
41-
private readonly int $urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH
59+
private readonly int $urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH,
4260
) {
61+
$this->resourceClassResolver = $resourceClassResolver;
62+
$this->iriConverter = $iriConverter;
63+
$this->operationMetadataFactory = $operationMetadataFactory;
4364
}
4465

66+
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
67+
{
68+
if (!$operation->getJsonStream() || !($request = $context['request'] ?? null)) {
69+
return $this->processor->process($data, $operation, $uriVariables, $context);
70+
}
71+
72+
// TODO: remove this before merging
73+
if ($request->query->has('skip_json_stream')) {
74+
return $this->processor->process($data, $operation, $uriVariables, $context);
75+
}
76+
77+
if ($operation instanceof Error || $data instanceof Response) {
78+
return $this->processor->process($data, $operation, $uriVariables, $context);
79+
}
80+
81+
if ($operation instanceof CollectionOperationInterface) {
82+
$requestUri = $request->getRequestUri() ?? '';
83+
$collection = new Collection();
84+
$collection->member = $data;
85+
$collection->view = $this->getView($data, $requestUri, $operation);
86+
87+
if ($operation->getParameters()) {
88+
$collection->search = $this->getSearch($operation, $requestUri);
89+
}
90+
91+
if ($data instanceof PaginatorInterface) {
92+
$collection->totalItems = $data->getTotalItems();
93+
}
94+
95+
if (\is_array($data) || ($data instanceof \Countable && !$data instanceof PartialPaginatorInterface)) {
96+
$collection->totalItems = \count($data);
97+
}
98+
99+
$data = $this->jsonStreamer->write(
100+
$collection,
101+
Type::generic(Type::object($collection::class), Type::object($operation->getClass())),
102+
['data' => $data, 'operation' => $operation],
103+
);
104+
} else {
105+
$data = $this->jsonStreamer->write($data, Type::object($operation->getClass()), [
106+
'data' => $data,
107+
'operation' => $operation,
108+
]);
109+
}
110+
111+
/** @var iterable<string> $data */
112+
$response = new StreamedResponse(
113+
$data,
114+
$this->getStatus($request, $operation, $context),
115+
$this->getHeaders($request, $operation, $context)
116+
);
117+
118+
return $this->processor->process($response, $operation, $uriVariables, $context);
119+
}
120+
121+
// TODO: These come from our Hydra collection normalizer, try to share the logic
45122
private function getSearch(Operation $operation, string $requestUri): IriTemplate
46123
{
47124
/** @var list<IriTemplateMapping> */
@@ -67,6 +144,7 @@ private function getSearch(Operation $operation, string $requestUri): IriTemplat
67144
}
68145

69146
$parts = parse_url($requestUri);
147+
70148
return new IriTemplate(
71149
variableRepresentation: 'BasicRepresentation',
72150
mapping: $mapping,
@@ -91,11 +169,11 @@ private function getView(mixed $object, string $requestUri, Operation $operation
91169
// TODO: This needs to be changed as well as I wrote in the CollectionFiltersNormalizer
92170
// We should not rely on the request_uri but instead rely on the UriTemplate
93171
// This needs that we implement the RFC and that we do more parsing before calling the serialization (MainController)
94-
$parsed = IriHelper::parseIri($requestUri ?? '/', $this->pageParameterName);
172+
$parsed = IriHelper::parseIri($requestUri, $this->pageParameterName);
95173
$appliedFilters = $parsed['parameters'];
96174
unset($appliedFilters[$this->enabledParameterName]);
97175

98-
$urlGenerationStrategy = $operation?->getUrlGenerationStrategy() ?? $this->urlGenerationStrategy;
176+
$urlGenerationStrategy = $operation->getUrlGenerationStrategy() ?? $this->urlGenerationStrategy;
99177
$id = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated ? $currentPage : null, $urlGenerationStrategy);
100178
if (!$appliedFilters && !$paginated) {
101179
return new PartialCollectionView($id);
@@ -117,46 +195,4 @@ private function getView(mixed $object, string $requestUri, Operation $operation
117195

118196
return new PartialCollectionView($id, $first, $last, $previous, $next);
119197
}
120-
121-
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
122-
{
123-
if ($context['request']->query->has('skip_json_stream')) {
124-
return $this->processor->process($data, $operation, $uriVariables, $context);
125-
}
126-
127-
if ($operation instanceof Error || $data instanceof Response) {
128-
return $this->processor->process($data, $operation, $uriVariables, $context);
129-
}
130-
131-
if ($operation instanceof CollectionOperationInterface) {
132-
$requestUri = $context['request']->getRequestUri() ?? '';
133-
$collection = new Collection();
134-
$collection->member = $data;
135-
$collection->view = $this->getView($data, $requestUri, $operation);
136-
137-
if ($operation->getParameters()) {
138-
$collection->search = $this->getSearch($operation, $requestUri);
139-
}
140-
141-
if ($data instanceof PaginatorInterface) {
142-
$collection->totalItems = $data->getTotalItems();
143-
}
144-
145-
if (\is_array($data) || ($data instanceof \Countable && !$data instanceof PartialPaginatorInterface)) {
146-
$collection->totalItems = \count($data);
147-
}
148-
149-
$response = new StreamedResponse($this->jsonStreamer->write($collection, Type::generic(Type::object($collection::class), Type::object($operation->getClass())), [
150-
'data' => $data,
151-
'operation' => $operation,
152-
]));
153-
} else {
154-
$response = new StreamedResponse($this->jsonStreamer->write($data, Type::object($operation->getClass()), [
155-
'data' => $data,
156-
'operation' => $operation,
157-
]));
158-
}
159-
160-
return $this->processor->process($response, $operation, $uriVariables, $context);
161-
}
162198
}

src/JsonLd/JsonStreamer/ReadPropertyMetadataLoader.php

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\JsonLd\JsonStreamer\ValueTransformer;
15+
16+
use ApiPlatform\Metadata\UrlGeneratorInterface;
17+
use Symfony\Component\JsonStreamer\ValueTransformer\ValueTransformerInterface;
18+
use Symfony\Component\TypeInfo\Type;
19+
20+
final class ContextValueTransformer implements ValueTransformerInterface
21+
{
22+
public function __construct(
23+
private readonly UrlGeneratorInterface $urlGenerator,
24+
) {
25+
}
26+
27+
public function transform(mixed $value, array $options = []): mixed
28+
{
29+
return $this->urlGenerator->generate('api_jsonld_context', ['shortName' => $options['operation']->getShortName()], $options['operation']->getUrlGenerationStrategy());
30+
}
31+
32+
public static function getStreamValueType(): Type
33+
{
34+
return Type::string();
35+
}
36+
}

src/JsonLd/JsonStreamer/IriValueTransformer.php renamed to src/JsonLd/JsonStreamer/ValueTransformer/IriValueTransformer.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
<?php
22

3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
declare(strict_types=1);
413

5-
namespace ApiPlatform\JsonLd\JsonStreamer;
14+
namespace ApiPlatform\JsonLd\JsonStreamer\ValueTransformer;
615

716
use ApiPlatform\Hydra\Collection;
817
use ApiPlatform\Metadata\CollectionOperationInterface;

src/JsonLd/JsonStreamer/WritePropertyMetadataLoader.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
1313

1414
namespace ApiPlatform\JsonLd\JsonStreamer;
1515

16-
use ApiPlatform\Hydra\IriTemplate;
1716
use ApiPlatform\Hydra\Collection;
17+
use ApiPlatform\Hydra\IriTemplate;
1818
use ApiPlatform\Metadata\ResourceClassResolverInterface;
19-
use ApiPlatform\Metadata\UrlGeneratorInterface;
2019
use ApiPlatform\Metadata\Util\TypeHelper;
2120
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadata;
2221
use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
@@ -27,7 +26,6 @@ final class WritePropertyMetadataLoader implements PropertyMetadataLoaderInterfa
2726
public function __construct(
2827
private readonly PropertyMetadataLoaderInterface $loader,
2928
private readonly ResourceClassResolverInterface $resourceClassResolver,
30-
private readonly UrlGeneratorInterface $urlGenerator,
3129
) {
3230
}
3331

@@ -63,7 +61,7 @@ public function load(string $className, array $options = [], array $context = []
6361
$properties['@context'] = new PropertyMetadata(
6462
'id', // virual property
6563
Type::string(), // virtual property
66-
staticValue: $this->urlGenerator->generate('api_jsonld_context', ['shortName' => $options['operation']->getShortName()], $options['operation']->getUrlGenerationStrategy()),
64+
['api_platform.jsonld.json_streamer.write.value_transformer.context'],
6765
);
6866
}
6967

src/Metadata/ApiResource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,7 @@ public function __construct(
965965
array|Parameters|null $parameters = null,
966966
protected ?bool $strictQueryParameterValidation = null,
967967
protected ?bool $hideHydraOperation = null,
968+
?bool $jsonStream = null,
968969
protected array $extraProperties = [],
969970
) {
970971
parent::__construct(
@@ -1011,6 +1012,7 @@ class: $class,
10111012
middleware: $middleware,
10121013
strictQueryParameterValidation: $strictQueryParameterValidation,
10131014
hideHydraOperation: $hideHydraOperation,
1015+
jsonStream: $jsonStream,
10141016
extraProperties: $extraProperties
10151017
);
10161018

src/Metadata/Delete.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public function __construct(
100100
array|string|null $middleware = null,
101101
?bool $strictQueryParameterValidation = null,
102102
protected ?bool $hideHydraOperation = null,
103+
?bool $jsonStream = null,
103104
array $extraProperties = [],
104105
) {
105106
parent::__construct(
@@ -177,6 +178,7 @@ class: $class,
177178
rules: $rules,
178179
policy: $policy,
179180
middleware: $middleware,
181+
jsonStream: $jsonStream,
180182
extraProperties: $extraProperties,
181183
collectDenormalizationErrors: $collectDenormalizationErrors,
182184
parameters: $parameters,

0 commit comments

Comments
 (0)