a948eed9b6
Auto Tag Develop / tag (push) Successful in 7s
Ticket Lesstime : ERP-67 — `[Convention SQL / Backend / L]` ## Objectif Documenter toutes les colonnes BDD via `COMMENT ON COLUMN` (visible dans DBeaver / DataGrip / pgAdmin sans lire le code Doctrine) et verrouiller la convention par un garde-fou de test architecture. ## Changements ### Convention (CLAUDE.md + rules) - `CLAUDE.md` regle ABSOLUE n°12 : toute migration creant ou modifiant une colonne doit poser un `COMMENT ON COLUMN` (FR, ≤ 200 caracteres). - `.claude/rules/backend.md` § Migrations Doctrine : exemples + helper standardise pour les 4 colonnes du `TimestampableBlamableTrait`. ### Garde-fou architecture - `tests/Architecture/ColumnsHaveSqlCommentTest` : echoue si une colonne `public` n'a pas de `col_description` (hors `doctrine_migration_versions` et `fake_site_aware_entity` fixture de test). - Whitelist metier `EXCLUDED_TABLES` volontairement vide. ### Retrofit des tables existantes - Migration `Version20260528120000` : 64 `COMMENT ON TABLE/COLUMN` sur les 11 tables metier (audit_log, category, category_type, permission, role, role_permission, site, user, user_permission, user_role, user_site). - Source unique de verite : `src/Shared/Infrastructure/Database/ColumnCommentsCatalog.php`. - Commande `app:apply-column-comments` (Module/Core/Infrastructure/Console) : 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 `.gitea/workflows/pull-request.yml`. ## Validation - `make db-reset` puis `make test` : 312 tests verts, 0 regression. - `make php-cs-fixer-allow-risky` : 0 fix. - Couverture : 53/53 colonnes documentees sur `starseed` et `starseed_test`. ## Test plan - [ ] `make db-reset` passe sans erreur. - [ ] `make test` passe ; `ColumnsHaveSqlCommentTest` vert sur DB de test. - [ ] Verifier dans DBeaver / pgAdmin que les commentaires apparaissent sur les colonnes de `category`, `user`, `audit_log`. - [ ] Verifier que le workflow CI Gitea (`pull-request.yml`) passe. ## A noter pour la suite La convention `options: ['comment' => '...']` sur chaque `#[ORM\Column]` reste recommandee pour les nouvelles entites — Doctrine genere alors automatiquement le `COMMENT ON COLUMN` dans la migration et `schema:update` le preserve sans avoir a rejouer le catalogue. A discuter si on veut en faire une regle forte. --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Co-authored-by: Matthieu <mtholot19@gmail.com> Reviewed-on: #24 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
127 lines
6.6 KiB
Markdown
127 lines
6.6 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
|
|
|
|
## 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 :
|
|
|
|
```php
|
|
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)
|
|
|
|
## 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 :
|
|
|
|
```php
|
|
// 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 :
|
|
|
|
```php
|
|
// 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.
|