From 4e5741cfc90c355f8ffd6aef149a966ec8d0854b Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 30 Jun 2025 17:54:57 +0200 Subject: [PATCH 1/2] fix(jsonld): reset gen_id configuration --- src/JsonLd/Serializer/ItemNormalizer.php | 3 +- src/Serializer/OperationContextTrait.php | 2 +- .../ApiResource/GenIdFalse/LevelFirst.php | 32 +++++++++++++++++++ .../ApiResource/GenIdFalse/LevelSecond.php | 24 ++++++++++++++ .../ApiResource/GenIdFalse/LevelThird.php | 25 +++++++++++++++ tests/Functional/JsonLdTest.php | 18 ++++++++++- 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelThird.php diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index c668df6fd62..3b0a480a409 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -124,7 +124,8 @@ public function normalize(mixed $object, ?string $format = null, array $context unset($context['operation'], $context['operation_name']); } - if (true === ($context['output']['gen_id'] ?? true) && true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) { + $operation = $context['operation'] ?? null; + if (true === ($context['output']['gen_id'] ?? true) && true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, $operation?->getUrlGenerationStrategy() ?? UrlGeneratorInterface::ABS_PATH, $operation, $context)) { $context['iri'] = $iri; $metadata['@id'] = $iri; } diff --git a/src/Serializer/OperationContextTrait.php b/src/Serializer/OperationContextTrait.php index 515d2a0ded8..7cb9a51bbda 100644 --- a/src/Serializer/OperationContextTrait.php +++ b/src/Serializer/OperationContextTrait.php @@ -34,7 +34,7 @@ protected function createOperationContext(array $context, ?string $resourceClass $context['root_operation_name'] = $context['operation_name'] ?? $context['graphql_operation_name']; } - unset($context['iri'], $context['uri_variables'], $context['item_uri_template'], $context['force_resource_class']); + unset($context['iri'], $context['uri_variables'], $context['item_uri_template'], $context['force_resource_class'], $context['output']['gen_id']); // At some point we should merge the jsonld context here, there's a TODO to simplify this somewhere else if ($propertyMetadata) { diff --git a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php new file mode 100644 index 00000000000..4910547d490 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Operation; + +#[ApiResource(operations: [new Get(uriTemplate: '/levelfirst/{id}', provider: [self::class, 'provider'])])] +class LevelFirst +{ + public function __construct(public string $id, #[ApiProperty(genId: false)] public LevelSecond $levelSecond) + { + } + + public static function provider(Operation $operation, array $uriVariables = [], array $context = []): self + { + return new self($uriVariables['id'], new LevelSecond(new LevelThird('3', 'L3 Name'))); + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php new file mode 100644 index 00000000000..93064ad1c5b --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse; + +use ApiPlatform\Metadata\NotExposed; + +#[NotExposed()] +class LevelSecond +{ + public function __construct(public LevelThird $levelThird) + { + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelThird.php b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelThird.php new file mode 100644 index 00000000000..d8c5a35e8b3 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelThird.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; + +#[ApiResource(operations: [new Get(uriTemplate: '/levelthird/{id}')])] +class LevelThird +{ + public function __construct(public string $id, public string $name) + { + } +} diff --git a/tests/Functional/JsonLdTest.php b/tests/Functional/JsonLdTest.php index 2b2e8f698bb..c1d78f81060 100644 --- a/tests/Functional/JsonLdTest.php +++ b/tests/Functional/JsonLdTest.php @@ -16,6 +16,9 @@ use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\AggregateRating; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\GenIdFalse; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelFirst; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelSecond; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelThird; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6810\JsonLdContextOutput; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6465\Bar; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6465\Foo; @@ -34,7 +37,7 @@ class JsonLdTest extends ApiTestCase */ public static function getResources(): array { - return [Foo::class, Bar::class, JsonLdContextOutput::class, GenIdFalse::class, AggregateRating::class]; + return [Foo::class, Bar::class, JsonLdContextOutput::class, GenIdFalse::class, AggregateRating::class, LevelFirst::class, LevelSecond::class, LevelThird::class]; } /** @@ -81,6 +84,19 @@ public function testGenIdFalseOnResource(): void $this->assertArrayNotHasKey('@id', $r->toArray()['aggregateRating']); } + public function testGenIdFalseOnNestedResource(): void + { + $r = self::createClient()->request( + 'GET', + '/levelfirst/1', + ); + $this->assertJsonContains([ + 'levelSecond' => [ + 'levelThird' => '/levelthird/3', + ], + ]); + } + public function testShouldIgnoreProperty(): void { $r = self::createClient()->request( From d153826118f5dba02eac446a1f32996a502864f9 Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 1 Jul 2025 07:46:21 +0200 Subject: [PATCH 2/2] edit --- src/JsonLd/Serializer/ItemNormalizer.php | 16 +++++++++------- src/Serializer/OperationContextTrait.php | 2 +- .../ApiResource/GenIdFalse/LevelFirst.php | 7 +++++-- .../ApiResource/GenIdFalse/LevelSecond.php | 3 --- tests/Functional/JsonLdTest.php | 11 ++++------- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index 3b0a480a409..cc523adf6bd 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -119,13 +119,12 @@ public function normalize(mixed $object, ?string $format = null, array $context $metadata = $this->createJsonLdContext($this->contextBuilder, $object, $context); } - // maybe not needed anymore - if (isset($context['operation']) && $previousResourceClass !== $resourceClass) { - unset($context['operation'], $context['operation_name']); + // Special case: non-resource got serialized and contains a resource therefore we need to reset part of the context + if ($previousResourceClass !== $resourceClass) { + unset($context['operation'], $context['operation_name'], $context['output']); } - $operation = $context['operation'] ?? null; - if (true === ($context['output']['gen_id'] ?? true) && true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, $operation?->getUrlGenerationStrategy() ?? UrlGeneratorInterface::ABS_PATH, $operation, $context)) { + if (true === ($context['output']['gen_id'] ?? true) && true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) { $context['iri'] = $iri; $metadata['@id'] = $iri; } @@ -137,9 +136,12 @@ public function normalize(mixed $object, ?string $format = null, array $context return $data; } - if (!isset($metadata['@type']) && $isResourceClass) { - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); + $operation = $context['operation'] ?? null; + if ($isResourceClass && !$operation) { + $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); + } + if (!isset($metadata['@type']) && $operation) { $types = $operation instanceof HttpOperation ? $operation->getTypes() : null; if (null === $types) { $types = [$operation->getShortName()]; diff --git a/src/Serializer/OperationContextTrait.php b/src/Serializer/OperationContextTrait.php index 7cb9a51bbda..515d2a0ded8 100644 --- a/src/Serializer/OperationContextTrait.php +++ b/src/Serializer/OperationContextTrait.php @@ -34,7 +34,7 @@ protected function createOperationContext(array $context, ?string $resourceClass $context['root_operation_name'] = $context['operation_name'] ?? $context['graphql_operation_name']; } - unset($context['iri'], $context['uri_variables'], $context['item_uri_template'], $context['force_resource_class'], $context['output']['gen_id']); + unset($context['iri'], $context['uri_variables'], $context['item_uri_template'], $context['force_resource_class']); // At some point we should merge the jsonld context here, there's a TODO to simplify this somewhere else if ($propertyMetadata) { diff --git a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php index 4910547d490..804eab26212 100644 --- a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php +++ b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelFirst.php @@ -21,12 +21,15 @@ #[ApiResource(operations: [new Get(uriTemplate: '/levelfirst/{id}', provider: [self::class, 'provider'])])] class LevelFirst { - public function __construct(public string $id, #[ApiProperty(genId: false)] public LevelSecond $levelSecond) + /** + * @param list $levelSecond + */ + public function __construct(public string $id, #[ApiProperty(genId: false)] public array $levelSecond) { } public static function provider(Operation $operation, array $uriVariables = [], array $context = []): self { - return new self($uriVariables['id'], new LevelSecond(new LevelThird('3', 'L3 Name'))); + return new self($uriVariables['id'], [new LevelSecond(new LevelThird('3', 'L3 Name'))]); } } diff --git a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php index 93064ad1c5b..7145fe7dcba 100644 --- a/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php +++ b/tests/Fixtures/TestBundle/ApiResource/GenIdFalse/LevelSecond.php @@ -13,9 +13,6 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse; -use ApiPlatform\Metadata\NotExposed; - -#[NotExposed()] class LevelSecond { public function __construct(public LevelThird $levelThird) diff --git a/tests/Functional/JsonLdTest.php b/tests/Functional/JsonLdTest.php index c1d78f81060..62188b6bffb 100644 --- a/tests/Functional/JsonLdTest.php +++ b/tests/Functional/JsonLdTest.php @@ -17,7 +17,6 @@ use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\AggregateRating; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\GenIdFalse; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelFirst; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelSecond; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\LevelThird; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6810\JsonLdContextOutput; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6465\Bar; @@ -37,7 +36,7 @@ class JsonLdTest extends ApiTestCase */ public static function getResources(): array { - return [Foo::class, Bar::class, JsonLdContextOutput::class, GenIdFalse::class, AggregateRating::class, LevelFirst::class, LevelSecond::class, LevelThird::class]; + return [Foo::class, Bar::class, JsonLdContextOutput::class, GenIdFalse::class, AggregateRating::class, LevelFirst::class, LevelThird::class]; } /** @@ -90,11 +89,9 @@ public function testGenIdFalseOnNestedResource(): void 'GET', '/levelfirst/1', ); - $this->assertJsonContains([ - 'levelSecond' => [ - 'levelThird' => '/levelthird/3', - ], - ]); + $res = $r->toArray(); + $this->assertArrayNotHasKey('@id', $res['levelSecond']); + $this->assertArrayHasKey('@id', $res['levelSecond'][0]['levelThird']); } public function testShouldIgnoreProperty(): void