# Backend — Regles PHP / Symfony / API Platform ## Structure de fichier - Toujours `declare(strict_types=1);` en tete de tout fichier PHP - PHP CS Fixer : regles Symfony + PSR-12 + strict types (commande : `make php-cs-fixer-allow-risky`) - Commentaires (docblock, inline, bloc) **en francais** ; code (classes, methodes, variables) en anglais ## Messages de validation (obligatoire) **Toute contrainte `#[Assert\*]` portee par une entite metier doit avoir un message FR explicite**, et **`Assert\Length.max` doit refleter le `length` de la colonne ORM**. Pendant logique back de la regle de mapping d'erreur par champ cote front (ERP-101 : `useFormErrors` / `mapViolationsToRecord` affiche sous chaque champ le `message` renvoye par le back). Pourquoi : - Sans `message:` explicite, Symfony renvoie le defaut **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 metier portent en plus leur message FR pour un controle total. - Une colonne string bornee **sans `Assert\Length`** echoue au niveau Postgres (500 generique, non rattachee au champ) au lieu d'une 422 propre. Le `max` doit egaler le `length` ORM (anti-derive). Pattern par champ scalaire : ```php // Email metier #[Assert\Email(message: 'L\'adresse email n\'est pas valide.')] // Longueur calee 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')] ``` Coherence a 3 niveaux pour un champ obligatoire : colonne `nullable` (DB) <-> `Assert\NotBlank` (back) <-> `:required` + asterisque (front ERP-101). Les trois doivent s'accorder. Exceptions au miroir `Length` : un format deja borne par `Assert\Bic` / `Assert\Iban` (longueur garantie) ou par un `Assert\Regex` borne (ex. code postal `{4,5}`, couleur hex `#RRGGBB`) — whitelister alors la propriete dans `EntityConstraintsHaveFrenchMessageTest::EXCLUDED_LENGTH_MIRROR` avec justification. Les regles inter-champs (RG metier : exclusivite 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 plutot qu'en toast. ### Garde-fou architecture `tests/Architecture/EntityConstraintsHaveFrenchMessageTest` scanne reflexivement les entites sous `src/Module/*/Domain/Entity/` et echoue si : 1. une contrainte connue n'a pas de message FR explicite (compare au defaut Symfony) ; 2. une colonne string bornee writable n'a pas de `Assert\Length(max == ORM length)` (hors whitelist). Une contrainte non geree par le mapping du test le fait echouer : il faut l'ajouter explicitement (anti faux positif vert). ## API Platform (pas de controllers) - Toujours utiliser `#[ApiResource]` + Providers + Processors — pas de controllers Symfony classiques - Routes prefixees `/api` (via `config/routes/api_platform.yaml`) - Le login `/login_check` est **hors** prefix `/api` (nginx reecrit `REQUEST_URI` vers `/login_check`) - **Exception** : si tu dois creer un controller custom sous `/api/`, mettre `priority: 1` sur `#[Route]` pour eviter le conflit avec API Platform `{id}` ## Pagination (obligatoire) **Regle** : toute collection API DOIT etre paginee. Aucun retour de collection complete cote serveur. ### Standard global Pose dans `config/packages/api_platform.yaml` (section `defaults:`) et heritee par toutes les ressources : | Cle | Valeur | Effet | |---|---|---| | `pagination_enabled` | `true` | Pagination Hydra active par defaut. | | `pagination_items_per_page` | `10` | Taille de page par defaut, aligne sur l'UI `MalioDataTable`. | | `pagination_maximum_items_per_page` | `50` | Borne dure : `?itemsPerPage=999` → ramene a 50. Anti deep-fetch. | | `pagination_client_items_per_page` | `true` | Le client peut envoyer `?itemsPerPage=25` (bornee par le max). | | `pagination_client_enabled` | `true` | Le client peut envoyer `?pagination=false` pour TOUT recuperer (echappatoire selects). | ### Override par ressource (rare) Si une ressource a besoin d'un autre defaut (ex: payload lourd), utiliser les attributs sur l'operation. **JAMAIS `paginationEnabled: false`** sans whitelist explicite dans `tests/Architecture/CollectionsArePaginatedTest::EXCLUDED`. ```php new GetCollection( paginationItemsPerPage: 5, // override taille par defaut paginationMaximumItemsPerPage: 20, // override borne max ) ``` ### Selects et autocompletions Pour alimenter un `