diff --git a/features/serializer/groups_related.feature b/features/serializer/groups_related.feature new file mode 100644 index 00000000000..53c3f5a7086 --- /dev/null +++ b/features/serializer/groups_related.feature @@ -0,0 +1,18 @@ +@php8 +@!mongodb +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..b2746654c14 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -101,11 +101,21 @@ public function normalize($object, $format = null, array $context = []): array $data['hydra:member'] = []; $iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY]; + // 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']; + } + 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 6b66d41a96b..1a1748b9055 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -92,6 +92,15 @@ public function normalize($object, $format = null, array $context = []) $data = []; $paginationData = $this->getPaginationData($object, $context); + // 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']; + } + /** @var ResourceMetadata|ResourceMetadataCollection */ $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? ''); if ($metadata instanceof ResourceMetadataCollection && ($operation = $context['operation'] ?? null) instanceof CollectionOperationInterface && ($itemUriTemplate = $operation->getItemUriTemplate())) { @@ -99,6 +108,7 @@ public function normalize($object, $format = null, array $context = []) } else { 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..4db7ccbc96a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollection.php @@ -0,0 +1,49 @@ + + * + * 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'] +)] +#[GetCollection] +#[Get(normalizationContext: ['groups' => 'related'])] +class RelationGroupImpactOnCollection +{ + 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 + { + $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..adb705c9f33 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/RelationGroupImpactOnCollectionRelation.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\Entity; + +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\Serializer\Annotation\Groups; + +#[ApiResource] +class RelationGroupImpactOnCollectionRelation +{ + public ?int $id; + + #[Groups('related')] + public ?string $title; + + public function __construct($id, $title) + { + $this->id = $id; + $this->title = $title; + } +}