Skip to content

Commit dd73699

Browse files
soyukaRomaixn
authored andcommitted
fix(doctrine): get reference with identifier value (api-platform#6019)
* fix(doctrine): get reference with identifier value * cs
1 parent e0465ae commit dd73699

File tree

3 files changed

+138
-25
lines changed

3 files changed

+138
-25
lines changed

features/main/standard_put.feature

+45
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,48 @@ Feature: Spec-compliant PUT support
4747
"bar": ""
4848
}
4949
"""
50+
51+
@createSchema
52+
@!mongodb
53+
Scenario: Create a new resource identified by an uid
54+
When I add "Content-Type" header equal to "application/ld+json"
55+
And I send a "PUT" request to "/uid_identifieds/fbcf5910-d915-4f7d-ba39-6b2957c57335" with body:
56+
"""
57+
{
58+
"name": "test"
59+
}
60+
"""
61+
Then the response status code should be 201
62+
And the response should be in JSON
63+
And the JSON should be equal to:
64+
"""
65+
{
66+
"@context": "/contexts/UidIdentified",
67+
"@id": "/uid_identifieds/fbcf5910-d915-4f7d-ba39-6b2957c57335",
68+
"@type": "UidIdentified",
69+
"id": "fbcf5910-d915-4f7d-ba39-6b2957c57335",
70+
"name": "test"
71+
}
72+
"""
73+
74+
@!mongodb
75+
Scenario: Replace an existing resource
76+
When I add "Content-Type" header equal to "application/ld+json"
77+
And I send a "PUT" request to "/uid_identifieds/fbcf5910-d915-4f7d-ba39-6b2957c57335" with body:
78+
"""
79+
{
80+
"name": "bar"
81+
}
82+
"""
83+
Then the response status code should be 200
84+
And the response should be in JSON
85+
And the JSON should be equal to:
86+
"""
87+
{
88+
"@context": "/contexts/UidIdentified",
89+
"@id": "/uid_identifieds/fbcf5910-d915-4f7d-ba39-6b2957c57335",
90+
"@type": "UidIdentified",
91+
"id": "fbcf5910-d915-4f7d-ba39-6b2957c57335",
92+
"name": "bar"
93+
}
94+
"""

src/Doctrine/Common/State/PersistProcessor.php

+25-25
Original file line numberDiff line numberDiff line change
@@ -50,46 +50,46 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
5050
// https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555
5151
if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod() && ($operation->getExtraProperties()['standard_put'] ?? false)) {
5252
\assert(method_exists($manager, 'getReference'));
53-
// TODO: the call to getReference is most likely to fail with complex identifiers
5453
$newData = $data;
55-
if ($previousData = $context['previous_data']) {
56-
$newData = 1 === \count($uriVariables) ? $manager->getReference($class, current($uriVariables)) : clone $previousData;
57-
}
58-
5954
$identifiers = array_reverse($uriVariables);
6055
$links = $this->getLinks($class, $operation, $context);
6156
$reflectionProperties = $this->getReflectionProperties($data);
6257

63-
if (!$previousData) {
64-
foreach (array_reverse($links) as $link) {
65-
if ($link->getExpandedValue() || !$link->getFromClass()) {
58+
// TODO: the call to getReference is most likely to fail with complex identifiers
59+
if ($previousData = $context['previous_data']) {
60+
$classMetadata = $manager->getClassMetadata($class);
61+
$identifiers = $classMetadata->getIdentifierValues($previousData);
62+
$newData = 1 === \count($identifiers) ? $manager->getReference($class, current($identifiers)) : clone $previousData;
63+
64+
foreach ($reflectionProperties as $propertyName => $reflectionProperty) {
65+
// // Don't override the property if it's part of the subresource system
66+
if (isset($identifiers[$propertyName]) || isset($uriVariables[$propertyName])) {
6667
continue;
6768
}
6869

69-
$identifierProperties = $link->getIdentifiers();
70-
$hasCompositeIdentifiers = 1 < \count($identifierProperties);
70+
// Skip URI variables as sometime an uri variable is not the doctrine identifier
71+
foreach ($links as $link) {
72+
if (\in_array($propertyName, $link->getIdentifiers(), true)) {
73+
continue 2;
74+
}
75+
}
7176

72-
foreach ($identifierProperties as $identifierProperty) {
73-
$reflectionProperty = $reflectionProperties[$identifierProperty];
74-
$reflectionProperty->setValue($newData, $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null));
77+
if (($newValue = $reflectionProperty->getValue($data)) !== $reflectionProperty->getValue($newData)) {
78+
$reflectionProperty->setValue($newData, $newValue);
7579
}
7680
}
7781
} else {
78-
foreach ($reflectionProperties as $propertyName => $reflectionProperty) {
79-
// Don't override the property if it's part of the subresource system
80-
if (isset($uriVariables[$propertyName])) {
82+
foreach (array_reverse($links) as $link) {
83+
if ($link->getExpandedValue() || !$link->getFromClass()) {
8184
continue;
8285
}
8386

84-
foreach ($links as $link) {
85-
$identifierProperties = $link->getIdentifiers();
86-
if (\in_array($propertyName, $identifierProperties, true)) {
87-
continue;
88-
}
87+
$identifierProperties = $link->getIdentifiers();
88+
$hasCompositeIdentifiers = 1 < \count($identifierProperties);
8989

90-
if (($newValue = $reflectionProperty->getValue($data)) !== $reflectionProperty->getValue($newData)) {
91-
$reflectionProperty->setValue($newData, $newValue);
92-
}
90+
foreach ($identifierProperties as $identifierProperty) {
91+
$reflectionProperty = $reflectionProperties[$identifierProperty];
92+
$reflectionProperty->setValue($newData, $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null));
9393
}
9494
}
9595
}
@@ -113,7 +113,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
113113
private function isDeferredExplicit(DoctrineObjectManager $manager, $data): bool
114114
{
115115
$classMetadata = $manager->getClassMetadata($this->getObjectClass($data));
116-
if (method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) {
116+
if ($classMetadata && method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) { // @phpstan-ignore-line metadata can be null
117117
return $classMetadata->isChangeTrackingDeferredExplicit();
118118
}
119119

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Put;
18+
use Doctrine\ORM\Mapping as ORM;
19+
use Symfony\Component\Serializer\Annotation\Ignore as LegacyIgnore;
20+
use Symfony\Component\Serializer\Annotation\SerializedName as LegacySerializedName;
21+
use Symfony\Component\Serializer\Attribute\Ignore;
22+
use Symfony\Component\Serializer\Attribute\SerializedName;
23+
use Symfony\Component\Uid\Uuid;
24+
use Symfony\Component\Validator\Constraints as Assert;
25+
26+
#[Put(
27+
allowCreate: true,
28+
extraProperties: [
29+
'standard_put' => true,
30+
]
31+
)]
32+
#[ORM\Entity]
33+
class UidIdentified
34+
{
35+
/**
36+
* The entity ID.
37+
*/
38+
#[ORM\Id]
39+
#[ORM\Column(type: 'integer')]
40+
#[ORM\GeneratedValue]
41+
#[ApiProperty(identifier: false)]
42+
#[Ignore]
43+
#[LegacyIgnore]
44+
private ?int $id = null;
45+
46+
#[ORM\Column(type: 'symfony_uuid', unique: true, nullable: false)]
47+
#[ApiProperty(identifier: true)]
48+
#[SerializedName('id')]
49+
#[LegacySerializedName('id')]
50+
private ?Uuid $uuid = null;
51+
52+
/**
53+
* A nice person.
54+
*/
55+
#[ORM\Column]
56+
#[Assert\NotBlank]
57+
public string $name = '';
58+
59+
public function getId(): ?int
60+
{
61+
return $this->id;
62+
}
63+
64+
public function getUuid(): ?Uuid
65+
{
66+
return $this->uuid;
67+
}
68+
}

0 commit comments

Comments
 (0)