Skip to content

Commit 350390b

Browse files
feat(state): add headers to comply with LDP specification (#6917)
Co-authored-by: soyuka <soyuka@users.noreply.github.com>
1 parent a7ad092 commit 350390b

14 files changed

Lines changed: 287 additions & 1 deletion

File tree

src/Hydra/State/JsonStreamerProcessor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use ApiPlatform\Metadata\IriConverterInterface;
2323
use ApiPlatform\Metadata\Operation;
2424
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
25+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2526
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2627
use ApiPlatform\Metadata\UrlGeneratorInterface;
2728
use ApiPlatform\State\Pagination\PaginatorInterface;
@@ -57,10 +58,12 @@ public function __construct(
5758
private readonly string $pageParameterName = 'page',
5859
private readonly string $enabledParameterName = 'pagination',
5960
private readonly int $urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH,
61+
?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null,
6062
) {
6163
$this->resourceClassResolver = $resourceClassResolver;
6264
$this->iriConverter = $iriConverter;
6365
$this->operationMetadataFactory = $operationMetadataFactory;
66+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
6467
}
6568

6669
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])

src/Laravel/ApiPlatformProvider.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,13 @@ public function register(): void
404404
$this->app->bind(ProviderInterface::class, ContentNegotiationProvider::class);
405405

406406
$this->app->singleton(RespondProcessor::class, function (Application $app) {
407-
$decorated = new RespondProcessor();
407+
$decorated = new RespondProcessor(
408+
$app->make(IriConverterInterface::class),
409+
$app->make(ResourceClassResolverInterface::class),
410+
$app->make(OperationMetadataFactoryInterface::class),
411+
$app->make(ResourceMetadataCollectionFactoryInterface::class)
412+
);
413+
408414
if (class_exists(AddHeadersProcessor::class)) {
409415
/** @var ConfigRepository */
410416
$config = $app['config']->get('api-platform.http_cache') ?? [];

src/Serializer/State/JsonStreamerProcessor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use ApiPlatform\Metadata\IriConverterInterface;
2020
use ApiPlatform\Metadata\Operation;
2121
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
22+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2223
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2324
use ApiPlatform\State\ProcessorInterface;
2425
use ApiPlatform\State\Util\HttpResponseHeadersTrait;
@@ -46,10 +47,12 @@ public function __construct(
4647
?IriConverterInterface $iriConverter = null,
4748
?ResourceClassResolverInterface $resourceClassResolver = null,
4849
?OperationMetadataFactoryInterface $operationMetadataFactory = null,
50+
?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null,
4951
) {
5052
$this->resourceClassResolver = $resourceClassResolver;
5153
$this->iriConverter = $iriConverter;
5254
$this->operationMetadataFactory = $operationMetadataFactory;
55+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
5356
}
5457

5558
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])

src/State/Processor/RespondProcessor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\Metadata\IriConverterInterface;
1818
use ApiPlatform\Metadata\Operation;
1919
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
20+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2021
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2122
use ApiPlatform\State\ProcessorInterface;
2223
use ApiPlatform\State\StopwatchAwareInterface;
@@ -40,10 +41,12 @@ public function __construct(
4041
?IriConverterInterface $iriConverter = null,
4142
?ResourceClassResolverInterface $resourceClassResolver = null,
4243
?OperationMetadataFactoryInterface $operationMetadataFactory = null,
44+
?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null,
4345
) {
4446
$this->iriConverter = $iriConverter;
4547
$this->resourceClassResolver = $resourceClassResolver;
4648
$this->operationMetadataFactory = $operationMetadataFactory;
49+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
4750
}
4851

4952
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\State\Tests\Fixtures\ApiResource;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
18+
#[ApiResource()]
19+
class Dummy
20+
{
21+
public int $id;
22+
}

src/State/Util/HttpResponseHeadersTrait.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313

1414
namespace ApiPlatform\State\Util;
1515

16+
use ApiPlatform\Metadata\Error;
1617
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
1718
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1819
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
1920
use ApiPlatform\Metadata\Exception\RuntimeException;
2021
use ApiPlatform\Metadata\HttpOperation;
2122
use ApiPlatform\Metadata\IriConverterInterface;
2223
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
24+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
25+
use ApiPlatform\Metadata\ResourceClassResolverInterface;
2326
use ApiPlatform\Metadata\UrlGeneratorInterface;
2427
use ApiPlatform\Metadata\Util\ClassInfoTrait;
2528
use ApiPlatform\Metadata\Util\CloneTrait;
@@ -38,6 +41,8 @@ trait HttpResponseHeadersTrait
3841
use CloneTrait;
3942
private ?IriConverterInterface $iriConverter;
4043
private ?OperationMetadataFactoryInterface $operationMetadataFactory;
44+
private ?ResourceClassResolverInterface $resourceClassResolver;
45+
private ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory;
4146

4247
/**
4348
* @param array<string, mixed> $context
@@ -122,6 +127,41 @@ private function getHeaders(Request $request, HttpOperation $operation, array $c
122127
}
123128
}
124129

130+
if (
131+
!$operation instanceof Error
132+
&& $operation->getUriTemplate()
133+
&& $this->resourceClassResolver?->isResourceClass($operation->getClass())
134+
) {
135+
$this->addLinkedDataPlatformHeaders($headers, $operation);
136+
}
137+
125138
return $headers;
126139
}
140+
141+
private function addLinkedDataPlatformHeaders(array &$headers, HttpOperation $operation): void
142+
{
143+
if (!$this->resourceMetadataCollectionFactory) {
144+
return;
145+
}
146+
147+
$acceptPost = null;
148+
$allowedMethods = ['OPTIONS', 'HEAD'];
149+
$resourceCollection = $this->resourceMetadataCollectionFactory->create($operation->getClass());
150+
foreach ($resourceCollection as $resource) {
151+
foreach ($resource->getOperations() as $op) {
152+
if ($op->getUriTemplate() === $operation->getUriTemplate()) {
153+
$allowedMethods[] = $method = $op->getMethod();
154+
if ('POST' === $method && \is_array($outputFormats = $op->getOutputFormats())) {
155+
$acceptPost = implode(', ', array_merge(...array_values($outputFormats)));
156+
}
157+
}
158+
}
159+
}
160+
161+
if ($acceptPost) {
162+
$headers['Accept-Post'] = $acceptPost;
163+
}
164+
165+
$headers['Allow'] = implode(', ', $allowedMethods);
166+
}
127167
}

src/Symfony/Bundle/Resources/config/json_streamer/events.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
'%api_platform.collection.pagination.page_parameter_name%',
2727
'%api_platform.collection.pagination.enabled_parameter_name%',
2828
'%api_platform.url_generation_strategy%',
29+
service('api_platform.metadata.resource.metadata_collection_factory'),
2930
]);
3031

3132
$services->set('api_platform.jsonld.state_provider.json_streamer', 'ApiPlatform\Hydra\State\JsonStreamerProvider')
@@ -41,6 +42,7 @@
4142
service('api_platform.iri_converter'),
4243
service('api_platform.resource_class_resolver'),
4344
service('api_platform.metadata.operation.metadata_factory'),
45+
service('api_platform.metadata.resource.metadata_collection_factory'),
4446
]);
4547

4648
$services->set('api_platform.state_provider.json_streamer', 'ApiPlatform\Serializer\State\JsonStreamerProvider')

src/Symfony/Bundle/Resources/config/json_streamer/hydra.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'%api_platform.collection.pagination.page_parameter_name%',
2828
'%api_platform.collection.pagination.enabled_parameter_name%',
2929
'%api_platform.url_generation_strategy%',
30+
service('api_platform.metadata.resource.metadata_collection_factory'),
3031
]);
3132

3233
$services->set('api_platform.jsonld.state_provider.json_streamer', 'ApiPlatform\Hydra\State\JsonStreamerProvider')

src/Symfony/Bundle/Resources/config/json_streamer/json.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
service('api_platform.iri_converter'),
2525
service('api_platform.resource_class_resolver'),
2626
service('api_platform.metadata.operation.metadata_factory'),
27+
service('api_platform.metadata.resource.metadata_collection_factory'),
2728
]);
2829

2930
$services->set('api_platform.state_provider.json_streamer', 'ApiPlatform\Serializer\State\JsonStreamerProvider')

src/Symfony/Bundle/Resources/config/state/processor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
service('api_platform.iri_converter'),
3939
service('api_platform.resource_class_resolver'),
4040
service('api_platform.metadata.operation.metadata_factory'),
41+
service('api_platform.metadata.resource.metadata_collection_factory'),
4142
]);
4243

4344
$services->set('api_platform.state_processor.add_link_header', 'ApiPlatform\State\Processor\AddLinkHeaderProcessor')

0 commit comments

Comments
 (0)