From 06ba9ec283e909e8135b977237980020ea22a6e9 Mon Sep 17 00:00:00 2001 From: soyuka Date: Sat, 8 Nov 2025 09:10:07 +0100 Subject: [PATCH] fix(jsonld)!: item uri template type --- features/hydra/item_uri_template.feature | 4 ++-- features/jsonld/input_output.feature | 2 +- src/JsonLd/Serializer/ItemNormalizer.php | 12 ++++++++++-- src/Symfony/Bundle/Resources/config/jsonld.php | 1 + tests/Functional/JsonLdTest.php | 10 +++++----- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/features/hydra/item_uri_template.feature b/features/hydra/item_uri_template.feature index 5653bb13e3d..0c732832351 100644 --- a/features/hydra/item_uri_template.feature +++ b/features/hydra/item_uri_template.feature @@ -146,13 +146,13 @@ Feature: Exposing a collection of objects should use the specified operation to "hydra:member":[ { "@id":"/item_referenced_in_collection/a", - "@type":"CollectionReferencingItem", + "@type":"ItemReferencedInCollection", "id":"a", "name":"hello" }, { "@id":"/item_referenced_in_collection/b", - "@type":"CollectionReferencingItem", + "@type":"ItemReferencedInCollection", "id":"b", "name":"you" } diff --git a/features/jsonld/input_output.feature b/features/jsonld/input_output.feature index 3840877de05..dbe56ab1878 100644 --- a/features/jsonld/input_output.feature +++ b/features/jsonld/input_output.feature @@ -402,7 +402,7 @@ Feature: JSON-LD DTO input and output "required": ["@id", "@type", "foo", "bar"], "properties": { "@id": {"pattern": "^/.well-known/genid/.+$"}, - "@type": {"pattern": "^DummyCollectionDtoOutput$"}, + "@type": {"pattern": "^DummyFooCollectionDto$"}, "foo": {"type": "string"}, "bar": {"type": "integer"} } diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index 5ee28e87d41..8838610ef3f 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Exception\ItemNotFoundException; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\IriConverterInterface; +use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -70,7 +71,7 @@ final class ItemNormalizer extends AbstractItemNormalizer '@vocab', ]; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilder, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilder, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null, private ?OperationMetadataFactoryInterface $operationMetadataFactory = null) { parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker, $tagCollector); } @@ -118,7 +119,9 @@ public function normalize(mixed $object, ?string $format = null, array $context $context['output']['iri'] = null; } - if ($this->resourceClassResolver->isResourceClass($resourceClass)) { + if (isset($context['item_uri_template']) && $this->operationMetadataFactory) { + $context['output']['operation'] = $this->operationMetadataFactory->create($context['item_uri_template']); + } elseif ($this->resourceClassResolver->isResourceClass($resourceClass)) { $context['output']['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); } @@ -144,6 +147,11 @@ public function normalize(mixed $object, ?string $format = null, array $context } $operation = $context['operation'] ?? null; + + if ($this->operationMetadataFactory && isset($context['item_uri_template']) && !$operation) { + $operation = $this->operationMetadataFactory->create($context['item_uri_template']); + } + if ($isResourceClass && !$operation) { $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); } diff --git a/src/Symfony/Bundle/Resources/config/jsonld.php b/src/Symfony/Bundle/Resources/config/jsonld.php index d3d1007cd09..b16d6a0c221 100644 --- a/src/Symfony/Bundle/Resources/config/jsonld.php +++ b/src/Symfony/Bundle/Resources/config/jsonld.php @@ -42,6 +42,7 @@ '%api_platform.serializer.default_context%', service('api_platform.security.resource_access_checker')->ignoreOnInvalid(), service('api_platform.http_cache.tag_collector')->ignoreOnInvalid(), + service('api_platform.metadata.operation.metadata_factory')->ignoreOnInvalid() ]) ->tag('serializer.normalizer', ['priority' => -890]); diff --git a/tests/Functional/JsonLdTest.php b/tests/Functional/JsonLdTest.php index 76c2f9484fe..3e7d8471b37 100644 --- a/tests/Functional/JsonLdTest.php +++ b/tests/Functional/JsonLdTest.php @@ -156,12 +156,12 @@ public function testItemUriTemplate(): void $this->assertJsonContains([ 'member' => [ [ - '@type' => 'RecipeCollection', + '@type' => 'ItemRecipe', '@id' => '/item_uri_template_recipes/1', 'name' => 'Dummy Recipe', ], [ - '@type' => 'RecipeCollection', + '@type' => 'ItemRecipe', '@id' => '/item_uri_template_recipes/2', 'name' => 'Dummy Recipe 2', ], @@ -204,17 +204,17 @@ public function testItemUriTemplateWithStateOption(): void $this->assertJsonContains([ 'member' => [ [ - '@type' => 'Recipe', + '@type' => 'ItemRecipe', '@id' => '/item_uri_template_recipes_state_option/1', 'name' => 'Recipe 0', ], [ - '@type' => 'Recipe', + '@type' => 'ItemRecipe', '@id' => '/item_uri_template_recipes_state_option/2', 'name' => 'Recipe 1', ], [ - '@type' => 'Recipe', + '@type' => 'ItemRecipe', '@id' => '/item_uri_template_recipes_state_option/3', 'name' => 'Recipe 2', ],