Skip to content

feat(symfony): map ForeignKeyConstraintViolationException to 409 by default - #8548

Open
audain-dg wants to merge 1 commit into
api-platform:mainfrom
audain-dg:feat/foreign-key-violation-409
Open

audain-dg wants to merge 1 commit into
api-platform:mainfrom
audain-dg:feat/foreign-key-violation-409

Conversation

@audain-dg

@audain-dg audain-dg commented Sep 20, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? main
Tickets -
License MIT
Doc PR - (no new API: one more entry in the documented exception_to_status defaults)

exception_to_status gained UniqueConstraintViolationException => 422 in #8478. Its sibling — ForeignKeyConstraintViolationException — is still unmapped, so a referential integrity violation surfaces as a 500 carrying the raw driver message.

Measured on 5.0.0 / MySQL 8, deleting a row that is still referenced:

DELETE /sectors/1
→ 500
detail: "An exception occurred while executing a query: SQLSTATE[23000]:
 Integrity constraint violation: 1451 Cannot delete or update a parent row:
 a foreign key constraint fails (`mydatabase`.`category`,
 CONSTRAINT `FK_64C19C1DE95C867` FOREIGN KEY (`sector_id`) REFERENCES `sector` (`id`))"

Two things are wrong there. The status: the request conflicts with the current state of the resource, it is not a server fault. And the payload, which hands the client the schema, the table, the column and the constraint name — Error::createFromException() puts $exception->getMessage() straight into detail, with no kernel.debug guard on that path, so it is not a debug-only leak.

409 also matches what OptimisticLockException already maps to: both say "the current state of the resource refuses this".

Open question for reviewers

ForeignKeyConstraintViolationException covers two situations:

  • the parent is still referenced (MySQL 1451) — a DELETE conflicting with existing rows; 409 fits;
  • the referenced row does not exist (MySQL 1452) — an INSERT/UPDATE pointing at a missing parent; one could argue for 422, as with the unique constraint.

exception_to_status maps a class to a single status, so this PR picks 409 for both, on the grounds that either way the request conflicts with the current state of the graph. Happy to switch to 422 if you would rather align with the unique-constraint precedent.

Test plan

  • tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php extended with the new default, exactly as feat(symfony): map UniqueConstraintViolationException to 422 by default #8478 did.
  • vendor/bin/phpunit tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.phpOK (25 tests, 52 assertions).
  • Ablation: reverting the Configuration.php side alone turns that suite red (2 failures), so the assertion covers the default rather than passing by accident.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant