From e9333c52255a34dddacaad14fa8b5d1d3508e1b2 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 7 Nov 2025 11:25:21 +0100 Subject: [PATCH] fix(jsonld): read identifier with itemUriTemplate --- src/Metadata/IdentifiersExtractor.php | 20 +++++++++++-- tests/Functional/JsonLdTest.php | 41 ++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Metadata/IdentifiersExtractor.php b/src/Metadata/IdentifiersExtractor.php index a9f5efe801e..300afd87d82 100644 --- a/src/Metadata/IdentifiersExtractor.php +++ b/src/Metadata/IdentifiersExtractor.php @@ -62,6 +62,9 @@ public function getIdentifiersFromItem(object $item, ?Operation $operation = nul return $this->getIdentifiersFromOperation($item, $operation, $context); } + /** + * @param array $context + */ private function getIdentifiersFromOperation(object $item, Operation $operation, array $context = []): array { if ($operation instanceof HttpOperation) { @@ -75,7 +78,7 @@ private function getIdentifiersFromOperation(object $item, Operation $operation, if (1 < (is_countable($link->getIdentifiers()) ? \count($link->getIdentifiers()) : 0)) { $compositeIdentifiers = []; foreach ($link->getIdentifiers() as $identifier) { - $compositeIdentifiers[$identifier] = $this->getIdentifierValue($item, $link->getFromClass() ?? $operation->getClass(), $identifier, $link->getParameterName()); + $compositeIdentifiers[$identifier] = $this->getIdentifierValue($item, $link->getFromClass() ?? $operation->getClass(), $identifier, $link->getParameterName(), null, $context, $operation); } $identifiers[$link->getParameterName()] = CompositeIdentifierParser::stringify($compositeIdentifiers); @@ -83,7 +86,7 @@ private function getIdentifiersFromOperation(object $item, Operation $operation, } $parameterName = $link->getParameterName(); - $identifiers[$parameterName] = $this->getIdentifierValue($item, $link->getFromClass() ?? $operation->getClass(), $link->getIdentifiers()[0] ?? $k, $parameterName, $link->getToProperty()); + $identifiers[$parameterName] = $this->getIdentifierValue($item, $link->getFromClass() ?? $operation->getClass(), $link->getIdentifiers()[0] ?? $k, $parameterName, $link->getToProperty(), $context, $operation); } return $identifiers; @@ -91,8 +94,10 @@ private function getIdentifiersFromOperation(object $item, Operation $operation, /** * Gets the value of the given class property. + * + * @param array $context */ - private function getIdentifierValue(object $item, string $class, string $property, string $parameterName, ?string $toProperty = null): float|bool|int|string + private function getIdentifierValue(object $item, string $class, string $property, string $parameterName, ?string $toProperty, array $context, Operation $operation): float|bool|int|string { if ($item instanceof $class) { try { @@ -102,6 +107,15 @@ private function getIdentifierValue(object $item, string $class, string $propert } } + // ItemUriTemplate is defined on a collection and we read the identifier alghough the PHP class may be different + if (isset($context['item_uri_template']) && $operation->getClass() === $class) { + try { + return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, $property), $parameterName); + } catch (NoSuchPropertyException $e) { + throw new RuntimeException(\sprintf('Could not retrieve identifier "%s" for class "%s" using itemUriTemplate "%s". Check that the property exists and is accessible.', $property, $class, $context['item_uri_template']), $e->getCode(), $e); + } + } + if ($toProperty) { return $this->resolveIdentifierValue($this->propertyAccessor->getValue($item, "$toProperty.$property"), $parameterName); } diff --git a/tests/Functional/JsonLdTest.php b/tests/Functional/JsonLdTest.php index 250193d0ed6..90db99eeab8 100644 --- a/tests/Functional/JsonLdTest.php +++ b/tests/Functional/JsonLdTest.php @@ -22,6 +22,8 @@ use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7298\ImageModuleResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7298\PageResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7298\TitleModuleResource; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\ItemUriTemplateWithCollection\Recipe; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\ItemUriTemplateWithCollection\RecipeCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6465\Bar; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6465\Foo; use ApiPlatform\Tests\SetupClassResourcesTrait; @@ -39,7 +41,20 @@ class JsonLdTest extends ApiTestCase */ public static function getResources(): array { - return [Foo::class, Bar::class, JsonLdContextOutput::class, GenIdFalse::class, AggregateRating::class, LevelFirst::class, LevelThird::class, PageResource::class, TitleModuleResource::class, ImageModuleResource::class]; + return [ + Foo::class, + Bar::class, + JsonLdContextOutput::class, + GenIdFalse::class, + AggregateRating::class, + LevelFirst::class, + LevelThird::class, + PageResource::class, + TitleModuleResource::class, + ImageModuleResource::class, + Recipe::class, + RecipeCollection::class, + ]; } /** @@ -129,6 +144,30 @@ public function testIssue7298(): void ]); } + public function testItemUriTemplate(): void + { + self::createClient()->request( + 'GET', + '/item_uri_template_recipes', + ); + $this->assertResponseIsSuccessful(); + + $this->assertJsonContains([ + 'member' => [ + [ + '@type' => 'RecipeCollection', + '@id' => '/item_uri_template_recipes/1', + 'name' => 'Dummy Recipe', + ], + [ + '@type' => 'RecipeCollection', + '@id' => '/item_uri_template_recipes/2', + 'name' => 'Dummy Recipe 2', + ], + ], + ]); + } + protected function setUp(): void { self::bootKernel();