fcacde2a34
Auto Tag Develop / tag (push) Successful in 7s
Allege le contexte CLAUDE charge a chaque session, sans perdre de garantie de comportement (pur deplacement de doc, zero fichier de code touche). ## backend.md (1771 -> 702 mots) Les 5 sections deja couvertes par un test Architecture deterministe deviennent des pointeurs courts (enonce + nom du test garde-fou). Le detail (patterns, tableaux, exemples) part dans un nouveau skill `backend-entity-conventions` charge a la demande : - Messages de validation FR -> EntityConstraintsHaveFrenchMessageTest - Pagination -> CollectionsArePaginatedTest - Libelle i18n audit -> AuditableEntitiesHaveI18nLabelTest - Timestampable/Blamable -> EntitiesAreTimestampableBlamableTest - COMMENT ON COLUMN -> ColumnsHaveSqlCommentTest ## frontend.md Ajoute une reference : tout nouvel ecran de formulaire doit ressembler a l'ecran Client (structure, marges, blocs de collection, validation inline 422). ## Garanties - Aucun test modifie : les tests Architecture restent le juge, le build casse comme avant. - Chaque regle garde son pointeur (enonce + test) charge a chaque session ; le detail revient via le skill. - Reversible en un revert. --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #62 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
91 lines
5.9 KiB
Markdown
91 lines
5.9 KiB
Markdown
# 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\*]` d'une entite metier : **message FR explicite**, et `Assert\Length.max` = `length` de la colonne ORM (coherence 3 niveaux nullable DB <-> NotBlank back <-> required front, ERP-101). RG inter-champs via `#[Assert\Callback]->atPath('<champ>')` (mapping inline front, pas toast). Exceptions miroir Length (Bic/Iban/Regex borne) : whitelist `EntityConstraintsHaveFrenchMessageTest::EXCLUDED_LENGTH_MIRROR`.
|
|
|
|
Garde-fou : `tests/Architecture/EntityConstraintsHaveFrenchMessageTest` (casse `make test`).
|
|
→ patterns code + exemples + justification complete : skill `backend-entity-conventions`.
|
|
|
|
## 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)
|
|
|
|
Toute collection API est paginee (defaut 10, max 50 ; `?pagination=false` = echappatoire selects, `?itemsPerPage=25` borne par le max). Standard global dans `config/packages/api_platform.yaml`. Jamais `paginationEnabled: false` hors whitelist `CollectionsArePaginatedTest::EXCLUDED`. Provider custom : ne jamais retourner un `array` brut sur une `CollectionOperationInterface` (court-circuite Hydra) — wrapper un Paginator (ORM : `ApiPlatform\Doctrine\Orm\Paginator` ; DBAL : `DbalPaginator`) et gerer `?pagination=false` via `$this->pagination->isEnabled(...)`.
|
|
|
|
Garde-fou : `tests/Architecture/CollectionsArePaginatedTest` (casse `make test`).
|
|
→ tableau des cles `pagination_*` + selects + providers ORM/DBAL detailles : skill `backend-entity-conventions`.
|
|
|
|
## Repositories
|
|
|
|
- Interface : `*RepositoryInterface` dans `Domain/Repository/`
|
|
- Implementation Doctrine : `Doctrine*Repository` dans `Infrastructure/Doctrine/`
|
|
- Le domaine garde les attributs ORM (approche pragmatique)
|
|
|
|
## RBAC (permissions)
|
|
|
|
Format obligatoire : `module.resource[.subresource].action` en snake_case.
|
|
- Exemples : `core.users.view`, `commercial.clients.contacts.edit`, `core.audit_log.view`
|
|
- Declarees via la methode statique `permissions()` des `*Module.php`
|
|
- Synchronisation : `app:sync-permissions`
|
|
- Verification API Platform : `is_granted('module.resource.action')`
|
|
- Verification front : `usePermissions()`
|
|
|
|
## Roles
|
|
|
|
- Hierarchie dans `config/packages/security.yaml` : `ROLE_ADMIN`, `ROLE_USER`
|
|
- Le role ne remplace pas la permission RBAC — deux niveaux complementaires
|
|
|
|
## Audit (obligatoire)
|
|
|
|
- Toute entite metier (nouvelle ou existante) : `#[Auditable]` (de `Shared/Domain/Attribute/`)
|
|
- Champs sensibles (password, token, secret) : `#[AuditIgnore]`
|
|
- Audit ManyToMany : trace automatiquement `{fieldName: {added: [ids], removed: [ids]}}` — aucune action supplementaire
|
|
- Spec complete : @doc/audit-log.md
|
|
|
|
### Libelle i18n du type d'entite (obligatoire avec `#[Auditable]`)
|
|
|
|
Toute entite `#[Auditable]` doit avoir sa cle `audit.entity.<module>_<entity>` dans `frontend/i18n/locales/fr.json` (cle = `strtolower(module)` + `_` + `strtolower(Entity)`, decision ERP-99). Sans elle, le filtre « Type d'entite » de l'audit-log retombe silencieusement sur le type technique brut (ex: `commercial.Client`). Fait partie de la definition de fini d'une entite auditee.
|
|
|
|
Garde-fou : `tests/Architecture/AuditableEntitiesHaveI18nLabelTest` (casse `make test`).
|
|
→ derivation detaillee + exemples : skill `backend-entity-conventions`.
|
|
|
|
## Timestampable + Blamable (obligatoire pour entites metier)
|
|
|
|
Toute **nouvelle** entite metier sous `src/Module/*/Domain/Entity/` : `implements TimestampableInterface, BlamableInterface` + `use TimestampableBlamableTrait` (porte les 4 colonnes `created_at` / `updated_at` / `created_by` / `updated_by`, remplies par `TimestampableBlamableSubscriber` au prePersist/preUpdate). La migration cree les 4 colonnes (`created_at`/`updated_at` NOT NULL, `created_by`/`updated_by` nullable `ON DELETE SET NULL`). Referentiel statique justifie : whitelist `EntitiesAreTimestampableBlamableTest::EXCLUDED`.
|
|
|
|
Garde-fou : `tests/Architecture/EntitiesAreTimestampableBlamableTest` (casse `make test`).
|
|
→ snippet complet : skill `backend-entity-conventions` ; spec : @docs/specs/M0-categories/spec-back.md § 2.8 + § 2.8.bis.
|
|
|
|
## Serialization
|
|
|
|
Pour embarquer une relation dans le JSON (au lieu d'un IRI Hydra), ajouter le groupe du parent sur les proprietes de l'entite cible.
|
|
|
|
Exemple : pour qu'`User.profile` soit embarque au lieu d'un lien IRI sous le groupe `user:read`, annoter `Profile.$firstName` avec `#[Groups(['user:read'])]`.
|
|
|
|
## Upload de fichiers
|
|
|
|
- Valider cote serveur avec `$file->getMimeType()` — **jamais** `getClientMimeType()` (spoofable par le client)
|
|
|
|
## PostgreSQL
|
|
|
|
- Noms de colonnes toujours en **minuscules** dans le SQL brut (commun a tous les projets MALIO)
|
|
|
|
## Migrations Doctrine
|
|
|
|
Toute migration creant/modifiant une colonne d'une table metier pose un `COMMENT ON COLUMN` (FR, ≤ 200 caracteres, semantique + contrainte/RG, cible pour les FK). Les 4 colonnes Timestampable/Blamable recoivent leur description via le helper centralise `addStandardTimestampableBlamableComments($schema, 'table')`. Bonus : `COMMENT ON TABLE` pour decrire la table.
|
|
|
|
Garde-fou : `tests/Architecture/ColumnsHaveSqlCommentTest` parcourt `information_schema.columns` (schema `public`) ; une seule colonne sans `col_description` casse `make test` (hors `EXCLUDED_TABLES`).
|
|
→ exemples SQL + textes du helper : skill `backend-entity-conventions`.
|