- Migration retrofit Version20260528120000 : pose COMMENT ON TABLE/COLUMN sur les 11 tables metier existantes (53 colonnes) via le catalogue partage ColumnCommentsCatalog (Shared/Infrastructure/Database). - Commande app:apply-column-comments : rejoue le catalogue apres doctrine:schema:update --force (sinon l'ORM drop les commentaires absents du mapping PHP). Branchee dans makefile test-db-setup et workflow CI Gitea. - Test architecture tests/Architecture/ColumnsHaveSqlCommentTest : echoue si une colonne public n'a pas de col_description (hors doctrine_migration_versions et fake_site_aware_entity). Whitelist metier vide. - Convention documentee dans CLAUDE.md (regle ABSOLUE n°12) et .claude/rules/backend.md (section Migrations Doctrine) avec exemples et helper standardise pour les colonnes Timestampable/Blamable.
6.6 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(viaconfig/routes/api_platform.yaml) - Le login
/login_checkest hors prefix/api(nginx reecritREQUEST_URIvers/login_check) - Exception : si tu dois creer un controller custom sous
/api/, mettrepriority: 1sur#[Route]pour eviter le conflit avec API Platform{id}
Repositories
- Interface :
*RepositoryInterfacedansDomain/Repository/ - Implementation Doctrine :
Doctrine*RepositorydansInfrastructure/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](deShared/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 auprePersist/preUpdate. Hors contexte HTTP (CLI, cron, migration), le blame restenull(libelle « Systeme » cote front). - La migration de l'entite doit creer les 4 colonnes (
created_at/updated_atNOT NULL,created_by/updated_bynullableON DELETE SET NULL). - Garde-fou CI :
tests/Architecture/EntitiesAreTimestampableBlamableTestechoue si une entite oublie le pattern. Un referentiel statique justifie (ex:CategoryType) doit etre explicitement whiteliste dans la constanteEXCLUDEDavec 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()— jamaisgetClientMimeType()(spoofable par le client)
PostgreSQL
- Noms de colonnes toujours en minuscules dans le SQL brut (commun a tous les projets MALIO)
Migrations Doctrine
Documentation SQL obligatoire (COMMENT ON COLUMN)
Toute migration qui cree ou modifie une colonne d'une table metier doit poser un COMMENT ON COLUMN decrivant le champ. La description est stockee dans pg_description et visible dans tous les outils d'admin BDD (DBeaver, DataGrip, pgAdmin), sans avoir a lire les annotations PHP.
Format de la description :
- En francais
- ≤ 200 caracteres
- Semantique du champ — contraintes / lien RG si pertinent
- Pour les colonnes d'identifiant ou FK, mentionner la cible
Exemples :
// Migration : creation d'une colonne avec son commentaire dans la meme migration
$this->addSql("ALTER TABLE client ADD COLUMN siren VARCHAR(9) DEFAULT NULL");
$this->addSql("COMMENT ON COLUMN client.siren IS 'SIREN (9 chiffres) — identifiant legal entreprise. Unique parmi non-archives (RG-1.15).'");
// Cas FK : preciser la cible
$this->addSql("COMMENT ON COLUMN client.legal_form_id IS 'Reference forme juridique (SARL, SAS, SA...) — FK -> legal_form.id, ON DELETE RESTRICT.'");
// Cas booleen : preciser le sens et la valeur par defaut
$this->addSql("COMMENT ON COLUMN user.is_admin IS 'Drapeau super-administrateur — bypass complet RBAC. Faux par defaut.'");
// Bonus : decrire la table elle-meme
$this->addSql("COMMENT ON TABLE client IS 'Repertoire clients (M1 Commercial) — entites archivables.'");
Helper Timestampable/Blamable
Les 4 colonnes created_at, updated_at, created_by, updated_by ajoutees par TimestampableBlamableTrait recoivent une description standardisee via le helper centralise pour eviter la duplication. Helper a creer ou appeler :
// Dans la migration, apres avoir ajoute les 4 colonnes :
$this->addStandardTimestampableBlamableComments($schema, 'client');
L'implementation du helper applique :
created_at: « Horodatage de creation de la ligne (UTC, rempli automatiquement par TimestampableBlamableSubscriber). »updated_at: « Horodatage de derniere modification de la ligne (UTC, rempli automatiquement par TimestampableBlamableSubscriber). »created_by: « ID de l'utilisateur ayant cree la ligne — null pour les creations hors HTTP (CLI, migration, fixture). FK -> user.id, ON DELETE SET NULL. »updated_by: « ID de l'utilisateur ayant modifie la ligne en dernier — null pour les modifications hors HTTP. FK -> user.id, ON DELETE SET NULL. »
Garde-fou architecture
tests/Architecture/ColumnsHaveSqlCommentTest parcourt information_schema.columns filtre sur le schema public et echoue si une seule colonne n'a pas de col_description. Seules les tables system (doctrine_migration_versions) et la whitelist EXCLUDED_TABLES explicite (commentaire de justification + ticket Lesstime ouvert pour le retrofit) sont tolerees.
Conclusion : si tu crees une colonne sans poser son COMMENT ON COLUMN, make test casse en CI.