Files
Starseed/.claude/rules/backend.md
T
matthieu fcacde2a34
Auto Tag Develop / tag (push) Successful in 7s
docs(claude) : allege backend.md (pointeurs + skill) + ref ecran Client pour les formulaires (#62)
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>
2026-06-04 14:51:38 +00:00

5.9 KiB

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.