Skip to content

Commit 4f9626f

Browse files
authored
fix(serializer): use data if no uri_variables provided (#5743)
fixes #5736
1 parent ccad636 commit 4f9626f

File tree

5 files changed

+126
-27
lines changed

5 files changed

+126
-27
lines changed

features/main/patch.feature

+22
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,25 @@ Feature: Sending PATCH requets
5858
}
5959
}
6060
"""
61+
62+
Scenario: Patch a relation with uri variables that are not `id`
63+
When I add "Content-Type" header equal to "application/merge-patch+json"
64+
And I send a "PATCH" request to "/betas/1" with body:
65+
"""
66+
{
67+
"alpha": "/alphas/2"
68+
}
69+
"""
70+
Then the response should be in JSON
71+
And the response status code should be 200
72+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
73+
And the JSON should be equal to:
74+
"""
75+
{
76+
"@context": "/contexts/Beta",
77+
"@id": "/betas/1",
78+
"@type": "Beta",
79+
"betaId": 1,
80+
"alpha": "/alphas/2"
81+
}
82+
"""

src/Serializer/AbstractItemNormalizer.php

+31-22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ApiPlatform\Exception\ItemNotFoundException;
2121
use ApiPlatform\Metadata\ApiProperty;
2222
use ApiPlatform\Metadata\CollectionOperationInterface;
23+
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
2324
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2425
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2526
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
@@ -512,12 +513,7 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
512513

513514
$collectionKeyType = $type->getCollectionKeyTypes()[0] ?? null;
514515
$collectionKeyBuiltinType = $collectionKeyType?->getBuiltinType();
515-
$childContext = $this->createChildContext(['resource_class' => $className] + $context, $attribute, $format);
516-
unset($childContext['uri_variables']);
517-
if ($this->resourceMetadataCollectionFactory) {
518-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($className)->getOperation();
519-
}
520-
516+
$childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
521517
$values = [];
522518
foreach ($value as $index => $obj) {
523519
if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
@@ -637,8 +633,7 @@ protected function getAttributeValue(object $object, string $attribute, string $
637633
}
638634

639635
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
640-
$childContext = $this->createChildContext($context, $attribute, $format);
641-
unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['operation']);
636+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
642637

643638
return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
644639
}
@@ -653,12 +648,7 @@ protected function getAttributeValue(object $object, string $attribute, string $
653648
}
654649

655650
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
656-
$childContext = $this->createChildContext($context, $attribute, $format);
657-
$childContext['resource_class'] = $resourceClass;
658-
if ($this->resourceMetadataCollectionFactory) {
659-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
660-
}
661-
unset($childContext['iri'], $childContext['uri_variables']);
651+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
662652

663653
return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
664654
}
@@ -670,17 +660,16 @@ protected function getAttributeValue(object $object, string $attribute, string $
670660
unset($context['resource_class']);
671661
unset($context['force_resource_class']);
672662

663+
// Anonymous resources
673664
if ($type && $type->getClassName()) {
674665
$childContext = $this->createChildContext($context, $attribute, $format);
675-
unset($childContext['iri'], $childContext['uri_variables']);
676666
$childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
677667

678668
return $this->serializer->normalize($attributeValue, $format, $childContext);
679669
}
680670

681671
if ($type && 'array' === $type->getBuiltinType()) {
682672
$childContext = $this->createChildContext($context, $attribute, $format);
683-
unset($childContext['iri'], $childContext['uri_variables']);
684673

685674
return $this->serializer->normalize($attributeValue, $format, $childContext);
686675
}
@@ -804,12 +793,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
804793
&& $this->resourceClassResolver->isResourceClass($className)
805794
) {
806795
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
807-
$childContext = $this->createChildContext($context, $attribute, $format);
808-
$childContext['resource_class'] = $resourceClass;
809-
unset($childContext['uri_variables']);
810-
if ($this->resourceMetadataCollectionFactory) {
811-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
812-
}
796+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
813797

814798
return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext);
815799
}
@@ -899,4 +883,29 @@ private function setValue(object $object, string $attributeName, mixed $value):
899883
// Properties not found are ignored
900884
}
901885
}
886+
887+
private function createOperationContext(array $context, string $resourceClass = null): array
888+
{
889+
if (isset($context['operation']) && !isset($context['root_operation'])) {
890+
$context['root_operation'] = $context['operation'];
891+
$context['root_operation_name'] = $context['operation_name'];
892+
}
893+
894+
unset($context['iri'], $context['uri_variables']);
895+
if (!$resourceClass) {
896+
return $context;
897+
}
898+
899+
unset($context['operation'], $context['operation_name']);
900+
$context['resource_class'] = $resourceClass;
901+
if ($this->resourceMetadataCollectionFactory) {
902+
try {
903+
$context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
904+
$context['operation_name'] = $context['operation']->getName();
905+
} catch (OperationNotFoundException) {
906+
}
907+
}
908+
909+
return $context;
910+
}
902911
}

src/Serializer/ItemNormalizer.php

+1-5
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,7 @@ private function updateObjectToPopulate(array $data, array &$context): void
8686

8787
private function getContextUriVariables(array $data, $operation, array $context): array
8888
{
89-
if (!isset($context['uri_variables'])) {
90-
return ['id' => $data['id']];
91-
}
92-
93-
$uriVariables = $context['uri_variables'];
89+
$uriVariables = $context['uri_variables'] ?? $data;
9490

9591
/** @var Link $uriVariable */
9692
foreach ($operation->getUriVariables() as $uriVariable) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5736;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Operation;
19+
20+
#[Get(
21+
provider: [Alpha::class, 'provide'],
22+
)]
23+
final class Alpha
24+
{
25+
public function __construct(#[ApiProperty(identifier: true)] public int $alphaId)
26+
{
27+
}
28+
29+
public static function provide(Operation $operation, array $uriVariables = []): self
30+
{
31+
return new self(alphaId: $uriVariables['alphaId']);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5736;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\Metadata\Patch;
19+
20+
#[Patch(
21+
processor: [Beta::class, 'process'],
22+
provider: [Beta::class, 'provide'],
23+
)]
24+
final class Beta
25+
{
26+
public function __construct(#[ApiProperty(identifier: true)] public int $betaId, public ?Alpha $alpha = null)
27+
{
28+
}
29+
30+
public static function provide(Operation $operation, array $uriVariables = []): self
31+
{
32+
return new self(betaId: $uriVariables['betaId']);
33+
}
34+
35+
public static function process($body)
36+
{
37+
return $body;
38+
}
39+
}

0 commit comments

Comments
 (0)