Skip to content

Commit 148442c

Browse files
authored
fix(metadata): item uri template with another resource (api-platform#5176)
* fix(metadata): item uri template with another resource
1 parent d188135 commit 148442c

File tree

22 files changed

+283
-42
lines changed

22 files changed

+283
-42
lines changed

features/hydra/item_uri_template.feature

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,33 @@ Feature: Exposing a collection of objects should use the specified operation to
129129
}
130130
}
131131
"""
132+
133+
Scenario: Get a collection referencing another resource for its IRI
134+
When I add "Content-Type" header equal to "application/json"
135+
And I send a "GET" request to "/item_referenced_in_collection"
136+
Then the response status code should be 200
137+
And the response should be in JSON
138+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
139+
And the JSON should be equal to:
140+
"""
141+
{
142+
"@context":"/contexts/CollectionReferencingItem",
143+
"@id":"/item_referenced_in_collection",
144+
"@type":"hydra:Collection",
145+
"hydra:member":[
146+
{
147+
"@id":"/item_referenced_in_collection/a",
148+
"@type":"CollectionReferencingItem",
149+
"id":"a",
150+
"name":"hello"
151+
},
152+
{
153+
"@id":"/item_referenced_in_collection/b",
154+
"@type":"CollectionReferencingItem",
155+
"id":"b",
156+
"name":"you"
157+
}
158+
],
159+
"hydra:totalItems":2
160+
}
161+
"""

src/Hydra/Serializer/CollectionNormalizer.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use ApiPlatform\Api\UrlGeneratorInterface;
1919
use ApiPlatform\JsonLd\ContextBuilderInterface;
2020
use ApiPlatform\JsonLd\Serializer\JsonLdContextTrait;
21-
use ApiPlatform\Metadata\CollectionOperationInterface;
2221
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2322
use ApiPlatform\Serializer\ContextTrait;
2423
use ApiPlatform\State\Pagination\PaginatorInterface;
@@ -50,6 +49,10 @@ final class CollectionNormalizer implements NormalizerInterface, NormalizerAware
5049
public function __construct(private readonly ContextBuilderInterface $contextBuilder, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly IriConverterInterface $iriConverter, private readonly ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, array $defaultContext = [])
5150
{
5251
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
52+
53+
if ($this->resourceMetadataCollectionFactory) {
54+
trigger_deprecation('api-platform/core', '3.0', sprintf('Injecting "%s" within "%s" is not needed anymore and this dependency will be removed in 4.0.', ResourceMetadataCollectionFactoryInterface::class, self::class));
55+
}
5356
}
5457

5558
/**
@@ -80,11 +83,11 @@ public function normalize(mixed $object, string $format = null, array $context =
8083
$data['hydra:member'] = [];
8184
$iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY];
8285

83-
if ($this->resourceMetadataCollectionFactory && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && method_exists($operation, 'getItemUriTemplate') && ($itemUriTemplate = $operation->getItemUriTemplate())) {
84-
$context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operation->getItemUriTemplate());
85-
} else {
86-
unset($context['operation']);
86+
if (($operation = $context['operation'] ?? null) && method_exists($operation, 'getItemUriTemplate')) {
87+
$context['item_uri_template'] = $operation->getItemUriTemplate();
8788
}
89+
90+
unset($context['operation']);
8891
unset($context['operation_name'], $context['uri_variables']);
8992

9093
foreach ($object as $obj) {

src/JsonApi/Serializer/ItemNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ public function normalize(mixed $object, string $format = null, array $context =
8383
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null);
8484
}
8585

86+
if (($operation = $context['operation'] ?? null) && method_exists($operation, 'getItemUriTemplate')) {
87+
$context['item_uri_template'] = $operation->getItemUriTemplate();
88+
}
89+
8690
$context = $this->initContext($resourceClass, $context);
8791
$iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context);
8892
$context['iri'] = $iri;

src/JsonLd/Serializer/ItemNormalizer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ public function normalize(mixed $object, string $format = null, array $context =
9494
unset($context['operation'], $context['operation_name']);
9595
}
9696

97+
if (($operation = $context['operation'] ?? null) && method_exists($operation, 'getItemUriTemplate') && ($itemUriTemplate = $operation->getItemUriTemplate())) {
98+
$context['item_uri_template'] = $itemUriTemplate;
99+
}
100+
97101
if ($iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) {
98102
$context['iri'] = $iri;
99103
$metadata['@id'] = $iri;
@@ -108,6 +112,7 @@ public function normalize(mixed $object, string $format = null, array $context =
108112

109113
if (!isset($metadata['@type']) && $isResourceClass) {
110114
$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
115+
111116
$types = $operation instanceof HttpOperation ? $operation->getTypes() : null;
112117
if (null === $types) {
113118
$types = [$operation->getShortName()];
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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\Metadata\Operation\Factory;
15+
16+
use ApiPlatform\Metadata\Operation;
17+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
18+
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
19+
20+
final class OperationMetadataFactory implements OperationMetadataFactoryInterface
21+
{
22+
public function __construct(private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory)
23+
{
24+
}
25+
26+
public function create(string $uriTemplate, array $context = []): ?Operation
27+
{
28+
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
29+
foreach ($this->resourceMetadataCollectionFactory->create($resourceClass) as $resource) {
30+
foreach ($resource->getOperations() as $operation) {
31+
if ($operation->getUriTemplate() === $uriTemplate || $operation->getName() === $uriTemplate) {
32+
return $operation;
33+
}
34+
}
35+
}
36+
}
37+
38+
return null;
39+
}
40+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Metadata\Operation\Factory;
15+
16+
use ApiPlatform\Metadata\Operation;
17+
18+
interface OperationMetadataFactoryInterface
19+
{
20+
public function create(string $uriTemplate, array $context = []): ?Operation;
21+
}

src/Serializer/AbstractCollectionNormalizer.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
namespace ApiPlatform\Serializer;
1515

1616
use ApiPlatform\Api\ResourceClassResolverInterface;
17-
use ApiPlatform\Metadata\CollectionOperationInterface;
1817
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1918
use ApiPlatform\State\Pagination\PaginatorInterface;
2019
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
@@ -75,12 +74,10 @@ public function normalize(mixed $object, string $format = null, array $context =
7574
$data = [];
7675
$paginationData = $this->getPaginationData($object, $context);
7776

78-
$metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? '');
79-
if (($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && method_exists($operation, 'getItemUriTemplate') && ($itemUriTemplate = $operation->getItemUriTemplate())) {
80-
$context['operation'] = $metadata->getOperation($itemUriTemplate);
81-
} else {
82-
unset($context['operation']);
77+
if (($operation = $context['operation'] ?? null) && method_exists($operation, 'getItemUriTemplate')) {
78+
$context['item_uri_template'] = $operation->getItemUriTemplate();
8379
}
80+
unset($context['operation']);
8481
unset($context['operation_type'], $context['operation_name']);
8582
$itemsData = $this->getItemsData($object, $format, $context);
8683

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ private function registerMetadataConfiguration(ContainerBuilder $container, arra
259259
$loader->load('metadata/links.xml');
260260
$loader->load('metadata/property.xml');
261261
$loader->load('metadata/resource.xml');
262+
$loader->load('metadata/operation.xml');
262263

263264
$container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0, $xmlResources);
264265
$container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0, $xmlResources);

src/Symfony/Bundle/Resources/config/api.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
165165
<argument type="service" id="api_platform.uri_variables.converter" />
166166
<argument type="service" id="api_platform.symfony.iri_converter.skolem" />
167+
<argument type="service" id="api_platform.metadata.operation.metadata_factory" />
167168
</service>
168169
<service id="ApiPlatform\Api\IriConverterInterface" alias="api_platform.symfony.iri_converter" />
169170

src/Symfony/Bundle/Resources/config/elasticsearch.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
</service>
2323

2424
<service id="api_platform.elasticsearch.metadata.document.metadata_factory.attribute" class="ApiPlatform\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory" decorates="api_platform.elasticsearch.metadata.document.metadata_factory" decoration-priority="20" public="false">
25-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
25+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
2626
<argument type="service" id="api_platform.elasticsearch.metadata.document.metadata_factory.attribute.inner" />
2727
</service>
2828

2929
<service id="api_platform.elasticsearch.metadata.document.metadata_factory.cat" class="ApiPlatform\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory" decorates="api_platform.elasticsearch.metadata.document.metadata_factory" decoration-priority="10" public="false">
3030
<argument type="service" id="api_platform.elasticsearch.client" />
31-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
31+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
3232
<argument type="service" id="api_platform.elasticsearch.metadata.document.metadata_factory.cat.inner" />
3333
</service>
3434

0 commit comments

Comments
 (0)