Skip to content

Commit bc38744

Browse files
committed
fix
1 parent bb878fd commit bc38744

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

src/Hydra/Serializer/CollectionNormalizer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,21 @@ protected function getPaginationData(iterable $object, array $context = []): arr
7474

7575
if (null !== $this->resourceMetadataCollectionFactory && ($context['hydra_operations'] ?? $this->defaultContext['hydra_operations'] ?? false)) {
7676
$allHydraOperations = [];
77+
$operationNames = [];
7778
foreach ($this->resourceMetadataCollectionFactory->create($resourceClass) as $resourceMetadata) {
7879
$hydraOperations = $this->getHydraOperations(
7980
true,
8081
$resourceMetadata,
8182
$hydraPrefix
8283
);
8384
if (!empty($hydraOperations)) {
84-
$allHydraOperations = array_merge($allHydraOperations, $hydraOperations);
85+
foreach ($hydraOperations as $operation) {
86+
$operationName = $operation['method'];
87+
if (!\in_array($operationName, $operationNames, true)) {
88+
$operationNames[] = $operationName;
89+
$allHydraOperations[] = $operation;
90+
}
91+
}
8592
}
8693
}
8794

src/Hydra/Tests/Serializer/CollectionNormalizerTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,4 +622,80 @@ public function testNormalizeResourceCollectionWithHydraOperationsMultipleApiRes
622622
],
623623
], $actual);
624624
}
625+
626+
public function testNormalizeResourceCollectionWithHydraOperationsMultipleApiResourceWithOperationInDuplicate(): void
627+
{
628+
$fooOne = new Foo();
629+
$fooOne->id = 1;
630+
$fooOne->bar = 'baz';
631+
632+
$data = [$fooOne];
633+
634+
$normalizedFooOne = [
635+
'@id' => '/foos/1',
636+
'@type' => 'Foo',
637+
'bar' => 'baz',
638+
];
639+
640+
$contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
641+
$contextBuilderProphecy->getResourceContextUri(Foo::class)->willReturn('/contexts/Foo');
642+
643+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
644+
$resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
645+
646+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
647+
$iriConverterProphecy->getIriFromResource(Foo::class, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
648+
649+
$resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
650+
$resourceMetadataCollectionFactoryProphecy->create(Foo::class)->willReturn(new ResourceMetadataCollection('Foo', [
651+
(new ApiResource())
652+
->withShortName('Foo')
653+
->withOperations(new Operations(['get' => (new GetCollection())->withShortName('Foo')])),
654+
(new ApiResource())
655+
->withShortName('Foo')
656+
->withOperations(new Operations(['post' => (new GetCollection())->withShortName('Foo')])),
657+
]));
658+
659+
$delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
660+
$delegateNormalizerProphecy->normalize($fooOne, CollectionNormalizer::FORMAT, Argument::allOf(
661+
Argument::withEntry('resource_class', Foo::class),
662+
Argument::withEntry('api_sub_level', true)
663+
))->willReturn($normalizedFooOne);
664+
665+
$normalizer = new CollectionNormalizer(
666+
$contextBuilderProphecy->reveal(),
667+
$resourceClassResolverProphecy->reveal(),
668+
$iriConverterProphecy->reveal(),
669+
['hydra_prefix' => false, 'hydra_operations' => true],
670+
$resourceMetadataCollectionFactoryProphecy->reveal()
671+
);
672+
$normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
673+
674+
$actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
675+
'operation_name' => 'get',
676+
'resource_class' => Foo::class,
677+
]);
678+
679+
$this->assertEquals([
680+
'@context' => '/contexts/Foo',
681+
'@id' => '/foos',
682+
'@type' => 'Collection',
683+
'member' => [
684+
$normalizedFooOne,
685+
],
686+
'totalItems' => 1,
687+
'operation' => [
688+
[
689+
'@type' => [
690+
'Operation',
691+
'schema:FindAction',
692+
],
693+
'description' => 'Retrieves the collection of Foo resources.',
694+
'method' => 'GET',
695+
'returns' => 'Collection',
696+
'title' => 'getFooCollection',
697+
],
698+
],
699+
], $actual);
700+
}
625701
}

src/JsonLd/Serializer/ItemNormalizer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,21 @@ public function normalize(mixed $data, ?string $format = null, array $context =
192192

193193
if ($isResourceClass && ($context['hydra_operations'] ?? $this->itemNormalizerDefaultContext['hydra_operations'] ?? false)) {
194194
$allHydraOperations = [];
195+
$operationNames = [];
195196
foreach ($this->resourceMetadataCollectionFactory->create($resourceClass) as $resourceMetadata) {
196197
$hydraOperations = $this->getHydraOperations(
197198
false,
198199
$resourceMetadata,
199200
$this->getHydraPrefix($context + $this->itemNormalizerDefaultContext)
200201
);
201202
if (!empty($hydraOperations)) {
202-
$allHydraOperations = array_merge($allHydraOperations, $hydraOperations);
203+
foreach ($hydraOperations as $operation) {
204+
$operationName = $operation['method'];
205+
if (!\in_array($operationName, $operationNames, true)) {
206+
$operationNames[] = $operationName;
207+
$allHydraOperations[] = $operation;
208+
}
209+
}
203210
}
204211
}
205212

tests/JsonLd/Serializer/ItemNormalizerTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,77 @@ public function testNormalizeWithHydraOperationsMultipleApiResource(): void
256256
];
257257
$this->assertEquals($expected, $normalizer->normalize($dummy));
258258
}
259+
260+
public function testNormalizeWithHydraOperationsMultipleApiResourceWithOperationInDuplicate(): void
261+
{
262+
$dummy = new Dummy();
263+
$dummy->setName('hello');
264+
265+
$resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
266+
$resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [
267+
(new ApiResource())
268+
->withShortName('Dummy')
269+
->withOperations(new Operations(['get' => (new Get())->withShortName('Dummy')])),
270+
(new ApiResource())
271+
->withShortName('Dummy')
272+
->withOperations(new Operations(['get' => (new Get())->withShortName('Dummy')])),
273+
]));
274+
$propertyNameCollection = new PropertyNameCollection(['name']);
275+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
276+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->willReturn($propertyNameCollection);
277+
278+
$propertyMetadata = (new ApiProperty())->withReadable(true);
279+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
280+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata);
281+
282+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
283+
$iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_PATH, null, Argument::any())->willReturn('/dummies/1990');
284+
285+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
286+
$resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
287+
$resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
288+
$resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
289+
$resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
290+
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
291+
292+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
293+
$serializerProphecy->willImplement(NormalizerInterface::class);
294+
$serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello');
295+
$contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
296+
$contextBuilderProphecy->getResourceContextUri(Dummy::class)->willReturn('/contexts/Dummy');
297+
298+
$normalizer = new ItemNormalizer(
299+
$resourceMetadataCollectionFactoryProphecy->reveal(),
300+
$propertyNameCollectionFactoryProphecy->reveal(),
301+
$propertyMetadataFactoryProphecy->reveal(),
302+
$iriConverterProphecy->reveal(),
303+
$resourceClassResolverProphecy->reveal(),
304+
$contextBuilderProphecy->reveal(),
305+
null,
306+
null,
307+
null,
308+
['hydra_prefix' => false, 'hydra_operations' => true]
309+
);
310+
$normalizer->setSerializer($serializerProphecy->reveal());
311+
312+
$expected = [
313+
'@context' => '/contexts/Dummy',
314+
'@id' => '/dummies/1990',
315+
'@type' => 'Dummy',
316+
'operation' => [
317+
[
318+
'@type' => [
319+
'Operation',
320+
'schema:FindAction',
321+
],
322+
'description' => 'Retrieves a Dummy resource.',
323+
'method' => 'GET',
324+
'returns' => 'Dummy',
325+
'title' => 'getDummy',
326+
],
327+
],
328+
'name' => 'hello',
329+
];
330+
$this->assertEquals($expected, $normalizer->normalize($dummy));
331+
}
259332
}

0 commit comments

Comments
 (0)