Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

UniqueEntity always rejects a resource's own unchanged value on standard PUT

Aperta
#8,476 0 commenti 1 reazione 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
php, symfony
Ambito
api, backend

Direzione di ricerca

Inizia riproducendo il flusso PUT standard descritto nell’issue, quindi esamina DeserializeListener.php e UniqueEntityValidator.php nei percorsi indicati. Segui l’oggetto passato alla validazione e il risultato del repository, quindi aggiungi una copertura di regressione per un valore univoco invariato. Il lavoro è completato quando il PUT standard restituisce 200 senza una violazione, mentre il comportamento esistente di PATCH rimane corretto.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

API Platform version(s) affected: 4.3.17 (root cause also present on main)

Description

On a "standard" PUT, replacing a resource with its own, unchanged value for a field covered by a Symfony UniqueEntity constraint incorrectly fails validation with "This value is already used.", even though the value legitimately belongs to the very record being updated.

The cause is a mismatch between two independent pieces of framework logic:

  1. For a standard PUT, API Platform deliberately does not populate the entity Doctrine already manages for that id. Instead it denormalizes the request body into a brand-new, transient object. This is intentional, it's what gives PUT its "full replace" semantics (any field omitted from the body resets to its default, unlike PATCH):
// vendor/api-platform/symfony/EventListener/DeserializeListener.php
$assignObjectToPopulate = 'POST' === $method
    || 'PATCH' === $method
    || ('PUT' === $method && !($operation->getExtraProperties()['standard_put'] ?? true));

For a standard PUT (standard_put defaulting to true), this is false, so AbstractItemNormalizer instantiates a fresh object rather than mutating the one fetched from the database.

  1. That transient object is exactly what reaches validation. Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator decides "this match is fine, it's the same record" purely by PHP object identity:
// vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php
$result = $repository->{$constraint->repositoryMethod}(...$arguments);
...
if (!$result || (1 === \count($result) && current($result) === $value)) {
    return; // no violation: it's the same entity
}

$value is the object currently being validated. current($result) is whatever Doctrine's repository returns for the field(s) in question — which, thanks to Doctrine's identity map, is normally the same managed PHP instance as $value when you're updating an already-loaded entity (that's exactly how this self-exclusion is supposed to work, and it's why the analogous PATCH flow — which does populate the existing entity — behaves correctly).

For a standard PUT, though, $value is the disconnected transient object from step 1. It can never be === to the entity Doctrine loads from the database, no matter how the values compare. So the self-exclusion branch can never be taken, and every unique field submitted back unchanged is reported as a conflict against itself.

How to reproduce

Any Doctrine ORM/ODM resource with a standard PUT (allowCreate: true or otherwise, standard_put at its default of true) and a UniqueEntity constraint on at least one field:

#[ApiResource(operations: [new Get(), new Put(allowCreate: true)])]
#[UniqueEntity(fields: ['foo'])]
#[ORM\Entity]
class Example
{
    #[ORM\Id, ORM\Column]
    public ?int $id = null;

    #[ORM\Column]
    public string $foo = '';
}
# 1. Create the resource
PUT /examples/1
Content-Type: application/ld+json
{"foo": "a"}
=> 201 Created

# 2. Replace it with the exact same value
PUT /examples/1
Content-Type: application/ld+json
{"foo": "a"}
=> 422 Unprocessable Content
{
  "violations": [
    {"propertyPath": "foo", "message": "This value is already used."}
  ]
}

Step 2 should return 200 OK — nothing changed — but instead fails as if foo: "a" belonged to a different row.

Lingua principale
PHP
Stelle
2.6k
Fork
982
Merge medio
1g 16h
PR unite (30g)
59

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di api-platform/core

Tutte le issue di api-platform/core

Issue simili

Altre issue su PHP

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.