docs(catalogue): contrat exact des FK a la suppression produit (CASCADE product_ingredient)
All checks were successful
CI / php-lint (pull_request) Successful in 23s
CI / php-lint (push) Successful in 20s
CI / static-tests (push) Successful in 38s
CI / auto-merge (pull_request) Successful in 6s
CI / secret-scan (pull_request) Successful in 12s
CI / static-tests (pull_request) Successful in 32s
CI / auto-merge (push) Has been skipped
CI / secret-scan (push) Successful in 9s

Le docblock de ProductRepository pretendait que la suppression dure est bloquee
si le produit est reference (4 FK RESTRICT listees), en omettant que
product_ingredient.product_id est ON DELETE CASCADE : un produit avec une recette
mais sans reference commande/menu se supprime (la recette est cascade-supprimee),
ce qui contredit le contrat affiche.

La cascade est le comportement VOULU (la recette appartient au produit). Correctif
de contrat, sans changement de comportement :
- docblock ProductRepository : topologie FK reelle (RESTRICT bloquants vs CASCADE
  product_ingredient), + TODO trace audit du nb de lignes recette (phase stock).
- commentaire au site de suppression dans ProductController.

Differe (assume) : tracer le compte de lignes product_ingredient cascade dans
audit_log. La table est vide (domaine recettes/stock non construit) ; la trace
sera concue avec cette feature. 188 tests verts, PHPStan L6 propre.
This commit is contained in:
Imugiii 2026-06-16 12:04:50 +00:00
parent ad5203d3fc
commit 4e7a07bfe0
2 changed files with 16 additions and 4 deletions

View file

@ -8,10 +8,18 @@ use App\Core\DatabaseInterface;
/**
* Acces aux donnees de la table product (sous-domaine Catalogue). Suit le pattern
* etabli par CategoryRepository. La suppression dure peut etre bloquee par des FK
* RESTRICT (order_item, menu.burger_product_id, menu_slot_option,
* order_item_selection) : le controleur attrape la violation (SQLSTATE 23000) ->
* 422, plutot que de pre-tester chaque reference.
* etabli par CategoryRepository.
*
* Topologie des FK entrantes sur product(id) (db/migrations/0001) et effet sur la
* suppression dure :
* - RESTRICT (bloquent la suppression) : order_item, menu.burger_product_id,
* menu_slot_option, order_item_selection. Le controleur attrape la violation
* (SQLSTATE 23000) -> 422, plutot que de pre-tester chaque reference.
* - CASCADE : product_ingredient (la recette appartient au produit ; la
* supprimer avec le produit est voulu). La suppression n'est donc PAS bloquee
* par une recette existante. TODO (phase stock/recettes, table aujourd'hui
* vide) : tracer le nombre de lignes product_ingredient cascade-supprimees
* dans l'audit_log pour ne laisser aucune perte hors-trace.
*/
final class ProductRepository
{

View file

@ -240,6 +240,10 @@ class ProductController extends AdminController
$name = (string) ($product['name'] ?? '');
// FK RESTRICT (order_item / menu / menu_slot_option / order_item_selection)
// -> PDOException 23000 -> 422 (catch ci-dessous). product_ingredient est
// CASCADE (recette possedee par le produit) : supprimee avec lui, jamais
// bloquante (cf. docblock ProductRepository).
try {
$this->db()->transaction(function (DatabaseInterface $db) use ($id, $actor, $name): void {
$deleted = (new ProductRepository($db))->delete($id);