Skip to content

Commit 148442c

Browse files
authored
fix(metadata): item uri template with another resource (#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

+30
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

+8-5
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

+4
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

+5
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()];
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+
}
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

+3-6
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

+1
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

+1
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

+2-2
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

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</service>
1919

2020
<service id="api_platform.hal.normalizer.entrypoint" class="ApiPlatform\Hal\Serializer\EntrypointNormalizer" public="false">
21-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
21+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
2222
<argument type="service" id="api_platform.iri_converter" />
2323
<argument type="service" id="api_platform.router" />
2424

@@ -28,7 +28,7 @@
2828
<service id="api_platform.hal.normalizer.collection" class="ApiPlatform\Hal\Serializer\CollectionNormalizer" public="false">
2929
<argument type="service" id="api_platform.resource_class_resolver" />
3030
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
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

3333
<!-- Run after api_platform.hal.normalizer.object but before serializer.normalizer.object and serializer.denormalizer.array -->
3434
<tag name="serializer.normalizer" priority="-985" />
@@ -43,7 +43,7 @@
4343
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
4444
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
4545
<argument type="collection" />
46-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" on-invalid="ignore" />
46+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" on-invalid="ignore" />
4747
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
4848

4949
<!-- Run before serializer.normalizer.json_serializable -->

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<argument type="service" id="api_platform.hydra.json_schema.schema_factory.inner" />
1010
</service>
1111
<service id="api_platform.hydra.normalizer.documentation" class="ApiPlatform\Hydra\Serializer\DocumentationNormalizer" public="false">
12-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
12+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
1313
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
1414
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
1515
<argument type="service" id="api_platform.resource_class_resolver" />
@@ -39,7 +39,7 @@
3939
</service>
4040

4141
<service id="api_platform.hydra.normalizer.entrypoint" class="ApiPlatform\Hydra\Serializer\EntrypointNormalizer" public="false">
42-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
42+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
4343
<argument type="service" id="api_platform.iri_converter" />
4444
<argument type="service" id="api_platform.router" />
4545

@@ -57,7 +57,9 @@
5757
<argument type="service" id="api_platform.jsonld.context_builder" />
5858
<argument type="service" id="api_platform.resource_class_resolver" />
5959
<argument type="service" id="api_platform.iri_converter" />
60-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" on-invalid="null" />
60+
<argument>null</argument>
61+
<argument type="collection"/> <!-- default context -->
62+
6163

6264
<!-- Run after api_platform.jsonld.normalizer.object but before serializer.normalizer.object and serializer.denormalizer.array -->
6365
<tag name="serializer.normalizer" priority="-985" />
@@ -67,13 +69,13 @@
6769
<argument type="service" id="api_platform.hydra.normalizer.partial_collection_view.inner" />
6870
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
6971
<argument>%api_platform.collection.pagination.enabled_parameter_name%</argument>
70-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
72+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
7173
<argument type="service" id="api_platform.property_accessor" />
7274
</service>
7375

7476
<service id="api_platform.hydra.normalizer.collection_filters" class="ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer" decorates="api_platform.hydra.normalizer.collection" public="false">
7577
<argument type="service" id="api_platform.hydra.normalizer.collection_filters.inner" />
76-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
78+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
7779
<argument type="service" id="api_platform.resource_class_resolver" />
7880
<argument type="service" id="api_platform.filter_locator" />
7981
</service>

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</service>
1717

1818
<service id="api_platform.jsonapi.normalizer.entrypoint" class="ApiPlatform\JsonApi\Serializer\EntrypointNormalizer" public="false">
19-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
19+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
2020
<argument type="service" id="api_platform.iri_converter" />
2121
<argument type="service" id="api_platform.router" />
2222

@@ -26,7 +26,7 @@
2626
<service id="api_platform.jsonapi.normalizer.collection" class="ApiPlatform\JsonApi\Serializer\CollectionNormalizer" public="false">
2727
<argument type="service" id="api_platform.resource_class_resolver" />
2828
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
29-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
29+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
3030

3131
<!-- Run after api_platform.jsonapi.normalizer.object but before serializer.normalizer.object and serializer.denormalizer.array -->
3232
<tag name="serializer.normalizer" priority="-985" />
@@ -52,7 +52,7 @@
5252
<argument type="service" id="serializer.normalizer.object" />
5353
<argument type="service" id="api_platform.iri_converter" />
5454
<argument type="service" id="api_platform.resource_class_resolver" />
55-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
55+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
5656

5757
<!-- Run after serializer.denormalizer.array but before serializer.normalizer.object -->
5858
<tag name="serializer.normalizer" priority="-995" />
@@ -83,7 +83,7 @@
8383
</service>
8484

8585
<service id="api_platform.jsonapi.listener.request.transform_fieldsets_parameters" class="ApiPlatform\Symfony\EventListener\JsonApi\TransformFieldsetsParametersListener">
86-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" />
86+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
8787

8888
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="5" />
8989
</service>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="api_platform.metadata.operation.metadata_factory" class="ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactory" public="false">
9+
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
10+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
11+
</service>
12+
</services>
13+
</container>

src/Symfony/Bundle/Resources/config/metadata/resource.xml

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<services>
77

88
<service id="api_platform.metadata.resource.metadata_collection_factory" alias="api_platform.metadata.resource.metadata_collection_factory.attributes" />
9-
<service id="api_platform.metadata.resource.metadata_collection_factory.retro_compatible" alias="api_platform.metadata.resource.metadata_collection_factory" />
109

1110
<service id="ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface" alias="api_platform.metadata.resource.metadata_collection_factory" />
1211

0 commit comments

Comments
 (0)