From 303b03a6a753ff790fb9a4e401560a02ba2fd167 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 4 Jun 2026 16:10:44 +0200 Subject: [PATCH] docs(claude) : skill backend-entity-conventions (detail des 5 regles entites a garde-fou CI) --- .../backend-entity-conventions/SKILL.md | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 .claude/skills/backend-entity-conventions/SKILL.md diff --git a/.claude/skills/backend-entity-conventions/SKILL.md b/.claude/skills/backend-entity-conventions/SKILL.md new file mode 100644 index 0000000..0b8654a --- /dev/null +++ b/.claude/skills/backend-entity-conventions/SKILL.md @@ -0,0 +1,251 @@ +--- +name: backend-entity-conventions +description: Conventions détaillées des entités métier Starseed (back PHP/Symfony/API Platform) — messages de validation FR sur les contraintes, pagination API Platform et providers ORM/DBAL, libellé i18n du type d'entité auditée, Timestampable/Blamable, COMMENT ON COLUMN des migrations. Charger dès qu'on crée ou modifie une entité Domain, un ApiResource, un Provider/Processor, une contrainte de validation, ou une migration Doctrine. Le résumé court de chaque règle (+ nom du test garde-fou) reste dans .claude/rules/backend.md ; ce skill porte les patterns, tableaux et exemples complets. +--- + +# Conventions entités métier — détail + +Ce skill contient le détail (patterns code, tableaux, dérivations) des 5 règles back qui ont chacune +un test Architecture déterministe. L'énoncé court de chaque règle vit dans `.claude/rules/backend.md` +(chargé à chaque session) ; ici on trouve le « comment » complet. + +> Règle d'or : le **test Architecture reste le juge** (il casse `make test`). Ce skill aide à écrire +> le code juste du premier coup, il ne remplace pas le garde-fou. + +--- + +## 1. Messages de validation (Garde-fou : `EntityConstraintsHaveFrenchMessageTest`) + +**Toute contrainte `#[Assert\*]` portée par une entité métier doit avoir un message FR explicite**, et +**`Assert\Length.max` doit refléter le `length` de la colonne ORM**. C'est le pendant back du mapping +d'erreur par champ côté front (ERP-101 : `useFormErrors` / `mapViolationsToRecord` affiche sous chaque +champ le `message` renvoyé par le back). + +Pourquoi : +- Sans `message:` explicite, Symfony renvoie le défaut **anglais** (« This value is not a valid email + address. »). La locale FR globale (`default_locale: fr` dans `framework.yaml`) sert de FILET via + `validators.fr.xlf`, mais les contraintes métier portent en plus leur message FR pour un contrôle total. +- Une colonne string bornée **sans `Assert\Length`** échoue au niveau Postgres (500 générique, non + rattachée au champ) au lieu d'une 422 propre. Le `max` doit égaler le `length` ORM (anti-dérive). + +Pattern par champ scalaire : + +```php +// Email métier +#[Assert\Email(message: 'L\'adresse email n\'est pas valide.')] + +// Longueur calée sur la colonne (VARCHAR(120)) +#[ORM\Column(length: 120)] +#[Assert\Length(max: 120, maxMessage: 'Le nom ne peut dépasser {{ limit }} caractères.', normalizer: 'trim')] + +// Obligatoire (aligner nullable DB / NotBlank back / required front) +#[Assert\NotBlank(message: 'Le téléphone est obligatoire.', normalizer: 'trim')] +``` + +Cohérence à 3 niveaux pour un champ obligatoire : colonne `nullable` (DB) <-> `Assert\NotBlank` (back) +<-> `:required` + astérisque (front ERP-101). Les trois doivent s'accorder. + +Exceptions au miroir `Length` : un format déjà borné par `Assert\Bic` / `Assert\Iban` (longueur +garantie) ou par un `Assert\Regex` borné (ex. code postal `{4,5}`, couleur hex `#RRGGBB`) — whitelister +alors la propriété dans `EntityConstraintsHaveFrenchMessageTest::EXCLUDED_LENGTH_MIRROR` avec justification. + +Les règles inter-champs (RG métier : exclusivité distributor/broker RG-1.03, billingEmail RG-1.11, etc.) +passent par un `#[Assert\Callback]` qui construit la violation avec `->atPath('')` — indispensable +pour que le front la mappe en inline plutôt qu'en toast. + +### Garde-fou architecture + +`tests/Architecture/EntityConstraintsHaveFrenchMessageTest` scanne réflexivement les entités sous +`src/Module/*/Domain/Entity/` et échoue si : +1. une contrainte connue n'a pas de message FR explicite (comparé au défaut Symfony) ; +2. une colonne string bornée writable n'a pas de `Assert\Length(max == ORM length)` (hors whitelist). + +Une contrainte non gérée par le mapping du test le fait échouer : il faut l'ajouter explicitement +(anti faux positif vert). + +--- + +## 2. Pagination (Garde-fou : `CollectionsArePaginatedTest`) + +**Règle** : toute collection API DOIT être paginée. Aucun retour de collection complète côté serveur. + +### Standard global + +Posé dans `config/packages/api_platform.yaml` (section `defaults:`) et hérité par toutes les ressources : + +| Clé | Valeur | Effet | +|---|---|---| +| `pagination_enabled` | `true` | Pagination Hydra active par défaut. | +| `pagination_items_per_page` | `10` | Taille de page par défaut, alignée sur l'UI `MalioDataTable`. | +| `pagination_maximum_items_per_page` | `50` | Borne dure : `?itemsPerPage=999` → ramené à 50. Anti deep-fetch. | +| `pagination_client_items_per_page` | `true` | Le client peut envoyer `?itemsPerPage=25` (bornée par le max). | +| `pagination_client_enabled` | `true` | Le client peut envoyer `?pagination=false` pour TOUT récupérer (échappatoire selects). | + +### Override par ressource (rare) + +Si une ressource a besoin d'un autre défaut (ex: payload lourd), utiliser les attributs sur l'opération. +**JAMAIS `paginationEnabled: false`** sans whitelist explicite dans +`tests/Architecture/CollectionsArePaginatedTest::EXCLUDED`. + +```php +new GetCollection( + paginationItemsPerPage: 5, // override taille par défaut + paginationMaximumItemsPerPage: 20, // override borne max +) +``` + +### Selects et autocomplétions + +Pour alimenter un `