From 8cf86b29dc754c09e56370ae5df6f3b995f6c580 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 17 Nov 2022 10:18:55 +0100 Subject: [PATCH 1/4] fix(serializer): read groups off the root operation --- features/serializer/groups_related.feature | 16 +++++++ src/Hydra/Serializer/CollectionNormalizer.php | 12 +++++ .../AbstractCollectionNormalizer.php | 11 +++++ src/Serializer/AbstractItemNormalizer.php | 2 +- .../RelationGroupImpactOnCollection.php | 48 +++++++++++++++++++ ...elationGroupImpactOnCollectionRelation.php | 28 +++++++++++ 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 features/serializer/groups_related.feature create mode 100644 tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php create mode 100644 tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php diff --git a/features/serializer/groups_related.feature b/features/serializer/groups_related.feature new file mode 100644 index 00000000000..73a358c1842 --- /dev/null +++ b/features/serializer/groups_related.feature @@ -0,0 +1,16 @@ +Feature: Groups to embed relations + In order to show embed relations on a Resource + As a client software developer + I need to set up groups on the Resource embed properties + + Scenario: Get a single resource + When I send a "GET" request to "/relation_group_impact_on_collections/1" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "related.title" should be equal to "foo" + + Scenario: Get a collection resource not impacted by groups + When I send a "GET" request to "/relation_group_impact_on_collections" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:member[0].related" should be equal to "/relation_group_impact_on_collection_relations/1" diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php index af5791decb6..d6ceb4fb5cd 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -106,6 +106,18 @@ public function normalize($object, $format = null, array $context = []): array } else { unset($context['operation']); } + + // We need to keep this operation for serialization groups for later + if (isset($context['operation'])) { + $context['root_operation'] = $context['operation']; + } + + if (isset($context['operation_name'])) { + $context['root_operation_name'] = $context['operation_name']; + } + + // We need to unset the operation to ensure a proper IRI generation inside items + unset($context['operation']); unset($context['operation_name'], $context['uri_variables']); foreach ($object as $obj) { diff --git a/src/Serializer/AbstractCollectionNormalizer.php b/src/Serializer/AbstractCollectionNormalizer.php index 6b66d41a96b..35170041bcd 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -99,6 +99,17 @@ public function normalize($object, $format = null, array $context = []) } else { unset($context['operation']); } + + // We need to keep this operation for serialization groups for later + if (isset($context['operation'])) { + $context['root_operation'] = $context['operation']; + } + + if (isset($context['operation_name'])) { + $context['root_operation_name'] = $context['operation_name']; + } + + unset($context['operation']); unset($context['operation_type'], $context['operation_name']); $itemsData = $this->getItemsData($object, $format, $context); diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 3fc9b19d7c0..b9725e12e4d 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -712,7 +712,7 @@ protected function getFactoryOptions(array $context): array if (isset($context['resource_class']) && $this->resourceClassResolver->isResourceClass($context['resource_class']) && $this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); // fix for abstract classes and interfaces // This is a hot spot, we should avoid calling this here but in many cases we can't - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null); + $operation = $context['root_operation'] ?? $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['root_operation_name'] ?? $context['operation_name'] ?? null); $options['normalization_groups'] = $operation->getNormalizationContext()['groups'] ?? null; $options['denormalization_groups'] = $operation->getDenormalizationContext()['groups'] ?? null; } diff --git a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php new file mode 100644 index 00000000000..8f74d613d4a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php @@ -0,0 +1,48 @@ + + * + * 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\Entity; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Operation; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ApiResource( + provider: [RelationGroupImpactOnCollection::class, 'getData'], + operations: [ + new GetCollection(), + new Get(normalizationContext: ['groups' => 'related']), + ] +)] +class RelationGroupImpactOnCollection +{ + public function __construct( + public ?int $id = null, + #[Groups('related')] + public ?RelationGroupImpactOnCollectionRelation $related = null) + { + } + + public static function getData(Operation $operation, array $uriVariables = [], array $context = []): self|array + { + $item = new self($uriVariables['id'] ?? 1, new RelationGroupImpactOnCollectionRelation(id: $uriVariables['id'] ?? 1, title: 'foo')); + if ($operation instanceof CollectionOperationInterface) { + return [$item]; + } + + return $item; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php new file mode 100644 index 00000000000..6c623716f23 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php @@ -0,0 +1,28 @@ + + * + * 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\Entity; + +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ApiResource] +class RelationGroupImpactOnCollectionRelation +{ + public function __construct( + public ?int $id = null, + #[Groups('related')] + public ?string $title = null + ) { + } +} From daffbed30a6f4ea168bc563cc8b7faf9ec0ac81b Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 17 Nov 2022 11:32:46 +0100 Subject: [PATCH 2/4] use php 7 --- .../RelationGroupImpactOnCollection.php | 19 ++++++++++--------- ...elationGroupImpactOnCollectionRelation.php | 14 +++++++++----- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php index 8f74d613d4a..4db7ccbc96a 100644 --- a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php @@ -21,19 +21,20 @@ use Symfony\Component\Serializer\Annotation\Groups; #[ApiResource( - provider: [RelationGroupImpactOnCollection::class, 'getData'], - operations: [ - new GetCollection(), - new Get(normalizationContext: ['groups' => 'related']), - ] + provider: [RelationGroupImpactOnCollection::class, 'getData'] )] +#[GetCollection] +#[Get(normalizationContext: ['groups' => 'related'])] class RelationGroupImpactOnCollection { - public function __construct( - public ?int $id = null, - #[Groups('related')] - public ?RelationGroupImpactOnCollectionRelation $related = null) + public ?int $id; + #[Groups('related')] + public ?RelationGroupImpactOnCollectionRelation $related; + + public function __construct($id = null, $related = null) { + $this->id = $id; + $this->related = $related; } public static function getData(Operation $operation, array $uriVariables = [], array $context = []): self|array diff --git a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php index 6c623716f23..adb705c9f33 100644 --- a/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.php @@ -19,10 +19,14 @@ #[ApiResource] class RelationGroupImpactOnCollectionRelation { - public function __construct( - public ?int $id = null, - #[Groups('related')] - public ?string $title = null - ) { + public ?int $id; + + #[Groups('related')] + public ?string $title; + + public function __construct($id, $title) + { + $this->id = $id; + $this->title = $title; } } From 47d9491436cfc36062707474e0c33d1d5f96d0b4 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 17 Nov 2022 11:59:44 +0100 Subject: [PATCH 3/4] whoops --- src/Hydra/Serializer/CollectionNormalizer.php | 14 ++++++-------- src/Serializer/AbstractCollectionNormalizer.php | 17 ++++++++--------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php index d6ceb4fb5cd..b2746654c14 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -101,12 +101,6 @@ public function normalize($object, $format = null, array $context = []): array $data['hydra:member'] = []; $iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY]; - if ($this->resourceMetadataCollectionFactory && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && ($itemUriTemplate = $operation->getItemUriTemplate())) { - $context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operation->getItemUriTemplate()); - } else { - unset($context['operation']); - } - // We need to keep this operation for serialization groups for later if (isset($context['operation'])) { $context['root_operation'] = $context['operation']; @@ -116,8 +110,12 @@ public function normalize($object, $format = null, array $context = []): array $context['root_operation_name'] = $context['operation_name']; } - // We need to unset the operation to ensure a proper IRI generation inside items - unset($context['operation']); + if ($this->resourceMetadataCollectionFactory && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && ($itemUriTemplate = $operation->getItemUriTemplate())) { + $context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operation->getItemUriTemplate()); + } else { + unset($context['operation']); + } + unset($context['operation_name'], $context['uri_variables']); foreach ($object as $obj) { diff --git a/src/Serializer/AbstractCollectionNormalizer.php b/src/Serializer/AbstractCollectionNormalizer.php index 35170041bcd..1a1748b9055 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -92,14 +92,6 @@ public function normalize($object, $format = null, array $context = []) $data = []; $paginationData = $this->getPaginationData($object, $context); - /** @var ResourceMetadata|ResourceMetadataCollection */ - $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? ''); - if ($metadata instanceof ResourceMetadataCollection && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && ($itemUriTemplate = $operation->getItemUriTemplate())) { - $context['operation'] = $metadata->getOperation($itemUriTemplate); - } else { - unset($context['operation']); - } - // We need to keep this operation for serialization groups for later if (isset($context['operation'])) { $context['root_operation'] = $context['operation']; @@ -109,7 +101,14 @@ public function normalize($object, $format = null, array $context = []) $context['root_operation_name'] = $context['operation_name']; } - unset($context['operation']); + /** @var ResourceMetadata|ResourceMetadataCollection */ + $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? ''); + if ($metadata instanceof ResourceMetadataCollection && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && ($itemUriTemplate = $operation->getItemUriTemplate())) { + $context['operation'] = $metadata->getOperation($itemUriTemplate); + } else { + unset($context['operation']); + } + unset($context['operation_type'], $context['operation_name']); $itemsData = $this->getItemsData($object, $format, $context); From fb0afb968c70fcb32dc76f52c1d43c07e51fd1f4 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 17 Nov 2022 12:04:08 +0100 Subject: [PATCH 4/4] ouin --- features/serializer/groups_related.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/serializer/groups_related.feature b/features/serializer/groups_related.feature index 73a358c1842..53c3f5a7086 100644 --- a/features/serializer/groups_related.feature +++ b/features/serializer/groups_related.feature @@ -1,3 +1,5 @@ +@php8 +@!mongodb Feature: Groups to embed relations In order to show embed relations on a Resource As a client software developer