Uh oh!
There was an error while loading. Please reload this page.
fix: stop reporting an edited record as its own duplicate - #25
Merged
66Ton99 merged 1 commit intoAug 28, 2026
Merged
Conversation
66Ton99
commented
Aug 28, 2026
Author
The two red jobs here are not from this change — it touches PHP only. #26 repairs it and is green on all six jobs. Re-running the checks here after that lands should turn this one green; nothing in this branch needs to change. |
A form that edits a record submits the value that record already holds, so the lookup found the record itself and the browser reported "already used" for every edit of an entity carrying a UniqueEntity constraint. The submit was blocked client side even though Symfony's own validator, which skips the object it is validating, would have accepted it. The browser already sends the identifier of the object the form is bound to as "entityId", and the documentation already told custom controllers to use it. The default controller now does the same: a value is free when every record holding it is the one that identifier names. That identifier is the one part of the lookup the request decides, so a caller can ask for an answer that ignores one record. It changes an answer, never data, and the server side validator is unaffected; the route stays the existence oracle documented in 3_9.
66Ton99force-pushed
the
fix/unique-entity-ignores-edited-record
branch
from
August 28, 2026 06:40
a0a9dc2 to
062bf05CompareUh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Found while running a single page CRUD against the bundle: every edit of a record whose entity carries a
UniqueEntityconstraint is blocked in the browser, even when the value was not changed.An edit form submits the value the record already holds, so
checkUniqueEntityActionfinds that record and answersfalse. The browser shows "This value is already used." and refuses to send the form. Symfony's ownUniqueEntityValidatordoes not do this — it compares the found entity with the one it is validating and skips it — so the client refuses a submit the server would have accepted.Reproduced end to end: a
Productwith#[UniqueEntity(fields: ['sku'])], opened from an edit route, name changed, SKU untouched. The only request made was the uniqueness check; the form POST never went out and the field showed the duplicate message.Change
The browser already sends the identifier of the object the form is bound to as
entityId—Form\Constraint\UniqueEntityfills it fromgetId(),constraints/UniqueEntity.jsputs it in the request, andsrc/Resources/doc/3_9.mdalready documents it and tells custom controllers to use it to exclude the edited entity. The default controller now does the same: the value is free when every record holding it is the one that identifier names.The identifier of a match is read from Doctrine's class metadata when a manager knows the class, and falls back to
getId(); a composite key, or a match whose identifier cannot be read, is never taken for the edited record. A request with noentityId— a create form — behaves exactly as before.toList()was added because a constraint may declare arepositoryMethodthat answers with a single entity or withnullrather than a list; the previousempty()check accepted all three shapes and this keeps that.Trust
entityIdis the one part of the lookup the request decides, so a caller can ask "is this value free if we ignore record N?" and gettruefor a value record N holds. It changes an answer, never data, and the server side validator is unaffected. The route remains the unauthenticated existence check3_9.mdalready documents and asks applications to restrict; this does not widen what it can be asked about — the field combination and repository method still have to match a declared constraint. Noted in3_9.mdand in the design risks inAGENTS.md.Tests
Four tests in
Tests/Controller/AjaxControllerTest.php: the edited record is not its own duplicate, another record holding the value still is, an existing value with noentityIdstill is, and a match whose identifier cannot be read still is. The first fails onmain.Verified in the Nix shell on PHP 8.5.6:
composer test99 tests / 272 assertions,composer phpstanno errors,composer coverage95.85% lines.🤖 Generated with Claude Code