Files
Starseed/.claude/rules/backend.md
T
matthieu 6efe7aa8ea
Auto Tag Develop / tag (push) Successful in 9s
[ERP-52] Créer le pattern Timestampable + Blamable Shared (#13)
## Contexte
Ticket Lesstime : [#52](https://project.malio-dev.fr/projects/6/tasks/463)
Position dans le groupe M0 : 0.0 (prérequis transverse)

## Implémentation
- 2 interfaces (`TimestampableInterface`, `BlamableInterface`) dans `Shared/Domain/Contract/`
- 1 trait (`TimestampableBlamableTrait`) dans `Shared/Domain/Trait/`
- 1 Subscriber Doctrine (`TimestampableBlamableSubscriber`) dans `Shared/Infrastructure/Doctrine/`
- 1 ligne `resolve_target_entities` ajoutée à `config/packages/doctrine.yaml` (`UserInterface` → `User`)
- 1 test architecture (`EntitiesAreTimestampableBlamableTest`) garde-fou L3 de la spec § 2.8.bis
- 1 test unitaire (`TimestampableBlamableSubscriberTest`) 4 cas

## Décision EXCLUDED (cf. réponse review)
Les 4 entités préexistantes (`User`, `Role`, `Permission`, `Site`) sont **whitelistées** dans `EXCLUDED` avec justification par entrée, plutôt que rétrofitées dans ce ticket. Le rétrofit de `User` et `Site` est documenté en **HP-9 / HP-10** (récursion Blamable + migration → décision archi scopée). Doc mise à jour : spec § 2.8.bis, § 9, et `.claude/rules/backend.md`.

## Tests
- PHPUnit : 5 nouveaux tests, 0 échec, 0 risky (248 tests / 874 assertions au total)
- php-cs-fixer : OK

## Reviewer suggéré
- Tristan

---------

Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #13
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-05-28 09:37:18 +00:00

3.8 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

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}

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

Timestampable + Blamable (obligatoire pour entites metier)

Toute nouvelle entite metier sous src/Module/*/Domain/Entity/ doit porter les 4 colonnes created_at / updated_at / created_by / updated_by, remplies automatiquement. Trois lignes a ajouter a l'entite :

use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;

class MyEntity implements TimestampableInterface, BlamableInterface
{
    use TimestampableBlamableTrait; // porte les 4 props + getters/setters
    // ... reste metier
}
  • Le TimestampableBlamableSubscriber (Shared/Infrastructure/Doctrine/) remplit les colonnes au prePersist / preUpdate. Hors contexte HTTP (CLI, cron, migration), le blame reste null (libelle « Systeme » cote front).
  • La migration de l'entite doit creer les 4 colonnes (created_at / updated_at NOT NULL, created_by / updated_by nullable ON DELETE SET NULL).
  • Garde-fou CI : tests/Architecture/EntitiesAreTimestampableBlamableTest echoue si une entite oublie le pattern. Un referentiel statique justifie (ex: CategoryType) doit etre explicitement whiteliste dans la constante EXCLUDED avec un commentaire.
  • Spec complete : @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)