Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 14969aa

Browse files
authored
fix(serializer): put replaces embed collection (#5604)
fixes #5587 #5603
1 parent a09e258 commit 14969aa

4 files changed

Lines changed: 193 additions & 4 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Feature: Update an embed collection with PUT
2+
As a client software developer
3+
I need to be able to update an embed collection
4+
5+
Background:
6+
Given I add "Content-Type" header equal to "application/ld+json"
7+
8+
@createSchema
9+
Scenario: Update embed collection
10+
And I send a "POST" request to "/issue5584_employees" with body:
11+
"""
12+
{"name": "One"}
13+
"""
14+
Then I add "Content-Type" header equal to "application/ld+json"
15+
And I send a "POST" request to "/issue5584_employees" with body:
16+
"""
17+
{"name": "Two"}
18+
"""
19+
Then print last JSON response
20+
Then I add "Content-Type" header equal to "application/ld+json"
21+
And I send a "POST" request to "/issue5584_businesses" with body:
22+
"""
23+
{"name": "Business"}
24+
"""
25+
Then I add "Content-Type" header equal to "application/ld+json"
26+
And I send a "PUT" request to "/issue5584_businesses/1" with body:
27+
"""
28+
{"name": "Business", "businessEmployees": [{"@id": "/issue5584_employees/1", "id": 1}, {"@id": "/issue5584_employees/2", "id": 2}]}
29+
"""
30+
And the JSON node "businessEmployees[0].name" should contain 'One'
31+
And the JSON node "businessEmployees[1].name" should contain 'Two'

src/Serializer/AbstractItemNormalizer.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,14 +495,19 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
495495

496496
$collectionKeyType = $type->getCollectionKeyTypes()[0] ?? null;
497497
$collectionKeyBuiltinType = $collectionKeyType?->getBuiltinType();
498+
$childContext = $this->createChildContext(['resource_class' => $className] + $context, $attribute, $format);
499+
unset($childContext['uri_variables']);
500+
if ($this->resourceMetadataCollectionFactory) {
501+
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($className)->getOperation();
502+
}
498503

499504
$values = [];
500505
foreach ($value as $index => $obj) {
501506
if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
502507
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s", "%s" given.', $index, $collectionKeyBuiltinType, \gettype($index)), $index, [$collectionKeyBuiltinType], ($context['deserialization_path'] ?? false) ? sprintf('key(%s)', $context['deserialization_path']) : null, true);
503508
}
504509

505-
$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $this->createChildContext($context, $attribute, $format));
510+
$values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $childContext);
506511
}
507512

508513
return $values;
@@ -753,7 +758,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
753758
return $value;
754759
}
755760

756-
if (null === $value && ($type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false))) {
761+
if (null === $value && $type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false)) {
757762
return $value;
758763
}
759764

@@ -773,7 +778,6 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
773778
&& $this->resourceClassResolver->isResourceClass($className)
774779
) {
775780
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
776-
$context['resource_class'] = $resourceClass;
777781

778782
return $this->denormalizeCollection($attribute, $propertyMetadata, $type, $resourceClass, $value, $format, $context);
779783
}
@@ -785,6 +789,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
785789
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
786790
$childContext = $this->createChildContext($context, $attribute, $format);
787791
$childContext['resource_class'] = $resourceClass;
792+
unset($childContext['uri_variables']);
788793
if ($this->resourceMetadataCollectionFactory) {
789794
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
790795
}
@@ -857,7 +862,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
857862
}
858863
}
859864

860-
if ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false) {
865+
if ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false) { // @phpstan-ignore-line
861866
return $value;
862867
}
863868

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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\Entity\Issue5587;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Post;
19+
use ApiPlatform\Metadata\Put;
20+
use Doctrine\Common\Collections\ArrayCollection;
21+
use Doctrine\Common\Collections\Collection;
22+
use Doctrine\ORM\Mapping as ORM;
23+
use Symfony\Component\Serializer\Annotation\Groups;
24+
25+
#[ApiResource(
26+
shortName: 'issue5584_business',
27+
operations: [
28+
new Get(uriTemplate: 'issue5584_businesses/{id}'),
29+
new Post(uriTemplate: 'issue5584_businesses'),
30+
new Put(uriTemplate: 'issue5584_businesses/{id}'),
31+
],
32+
normalizationContext: [
33+
'groups' => ['r'],
34+
],
35+
denormalizationContext: [
36+
'groups' => ['w'],
37+
],
38+
)]
39+
#[ORM\Table(name: 'issue5584_business')]
40+
#[ORM\Entity()]
41+
class Business
42+
{
43+
#[ORM\Id]
44+
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
45+
#[ORM\Column(type: 'integer')]
46+
#[Groups(['w', 'r'])]
47+
private $id;
48+
#[ORM\Column(type: 'string', length: 255, nullable: true)]
49+
#[Groups(['w', 'r'])]
50+
private $name;
51+
/** @var Collection<int, Employee>|Employee[] */
52+
#[ORM\JoinTable(name: 'issue5584_business_users')]
53+
#[ORM\ManyToMany(targetEntity: Employee::class, inversedBy: 'businesses')]
54+
#[Groups(['w', 'r'])]
55+
private $businessEmployees;
56+
57+
public function __construct()
58+
{
59+
$this->businessEmployees = new ArrayCollection();
60+
}
61+
62+
public function getId(): ?int
63+
{
64+
return $this->id;
65+
}
66+
67+
public function getName(): ?string
68+
{
69+
return $this->name;
70+
}
71+
72+
public function setName(?string $name): self
73+
{
74+
$this->name = $name;
75+
76+
return $this;
77+
}
78+
79+
public function getBusinessEmployees(): Collection
80+
{
81+
return $this->businessEmployees;
82+
}
83+
84+
public function addBusinessEmployee(Employee $businessEmployee): self
85+
{
86+
if (!$this->businessEmployees->contains($businessEmployee)) {
87+
$this->businessEmployees[] = $businessEmployee;
88+
}
89+
90+
return $this;
91+
}
92+
93+
public function removeBusinessEmployee(Employee $businessEmployee): self
94+
{
95+
if ($this->businessEmployees->contains($businessEmployee)) {
96+
$this->businessEmployees->removeElement($businessEmployee);
97+
}
98+
99+
return $this;
100+
}
101+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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\Entity\Issue5587;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Post;
18+
use Doctrine\ORM\Mapping as ORM;
19+
use Symfony\Component\Serializer\Annotation\Groups;
20+
21+
#[ApiResource(
22+
shortName: 'issue5584_employee',
23+
operations: [
24+
new Post(uriTemplate: 'issue5584_employees'),
25+
],
26+
normalizationContext: [
27+
'groups' => ['r'],
28+
],
29+
denormalizationContext: [
30+
'groups' => ['w'],
31+
],
32+
)]
33+
#[ORM\Table(name: 'issue5584_employee')]
34+
#[ORM\Entity()]
35+
class Employee
36+
{
37+
#[ORM\Id]
38+
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
39+
#[ORM\Column(type: 'integer')]
40+
#[Groups(['w', 'r'])]
41+
private $id;
42+
#[ORM\Column(type: 'string', length: 255, nullable: true)]
43+
#[Groups(['w', 'r'])]
44+
public $name;
45+
#[ORM\ManyToMany(targetEntity: Business::class, mappedBy: 'businessEmployees')]
46+
public $businesses;
47+
48+
public function getId(): ?int
49+
{
50+
return $this->id;
51+
}
52+
}

0 commit comments

Comments
 (0)