Conventions & règles dures
Règles non négociables du projet. Toute PR qui les enfreint est rejetée. Ces conventions sont le reflet direct des priorités : intégrité financière › souveraineté › sobriété › maintenabilité › performance.
Doctrine — attributs PHP 8 uniquement
Le projet est en mapping type: attribute. Les docblock annotations
ne sont pas lues par Doctrine. Une annotation oubliée en docblock produit une colonne
manquante ou un slug NULL en silence.
// ❌ INTERDIT
/** @ORM\Column(type="string") @Gedmo\Slug(fields={"name"}) */
private $slug;
// ✅ OBLIGATOIRE
#[ORM\Column(length: 100)]
#[Gedmo\Slug(fields: ['name'])]
private ?string $slug = null;
Inclut aussi : #[ORM\PostPersist], #[ORM\HasLifecycleCallbacks],
#[ORM\MappedSuperclass], #[Assert\NotBlank]…
Cryptographie — libsodium uniquement
Toute signature ou vérification cryptographique passe par sodium_* (Ed25519,
Curve25519). Aucun appel à openssl_* n'est toléré pour les flux
financiers. Cohérent avec la roadmap Reticulum/LoRa (offline).
Atomicité financière
Toute modification de solde doit s'inscrire dans une transaction unique avec lock pessimiste
et persistance d'une Operation.
$em->wrapInTransaction(function () use ($accountId, $montant) {
$account = $em->find(Account::class, $accountId, LockMode::PESSIMISTIC_WRITE);
$flux = new Transaction(...);
$op = new OperationDebit($account, $flux, $montant);
$em->persist($flux);
$em->persist($op);
$account->applyOperation($op);
});
- Pas de cache du solde : il est recalculé depuis les opérations.
- Pas de calcul monétaire en
floatdirect :bcmath(oubrick/math) pour toute opération sensible. - Jamais de modification de
flux.hashouoperation.hashaprès création (chaînage cryptographique). - Jamais de
$em->flush()dans un EventSubscriber Doctrine lifecycle.
Double-flush pour références dépendant de l'UUID
Quand une référence métier dépend de l'UUID (UMR SEPA, slug court…), il faut un flush intermédiaire pour que Doctrine génère l'UUID.
$mandate = new SepaMandate();
$em->persist($mandate);
$em->flush(); // UUID disponible
$mandate->setMandateReference($this->generateUmr($mandate));
$em->flush();
Repository pattern
- Tout DQL / QueryBuilder vit dans
src/Repository/. - Méthodes typées
find*(findActiveByAdherent,findMonthlyPurchasesForFund…). - Aucun
$em->createQuery(...)dans un Service ou un Controller.
Sonata Admin 4.x — breaking changes
Sonata 4 a supprimé plusieurs raccourcis présents en 3.x. Sans ces ajustements, les Admins cassent silencieusement.
// ❌ INTERDIT (n'existe plus)
$this->trans('label.foo');
// ✅ OBLIGATOIRE
$this->getTranslator()->trans('label.foo', [], $this->getTranslationDomain());
DatagridMapperest final : pas mockable — préférer un test d'intégration.ProxyQueryInterfacen'expose plusgetRootAlias()/andWhere()en direct (accès via__call).- Mocks d'admin :
getMockBuilder(X::class)->disableOriginalConstructor()->onlyMethods([…]).
Symfony 7.x — tests fonctionnels
createClient()une seule fois par classe de test.AbstractFunctionalTestCase::setUp()l'appelle déjà..env.testdoit contenirSECURE_SCHEME=http(référencé parrequires_channeldanssecurity.yaml).- Pas de
vendor/bin/phpunitsans filtre (bloqué parsettings.json).
Frontend — règles dures
- Aucun JS ni CSS inline dans un
.html.twig. Créerassets/js|css/ma-feature.{js,css}, déclarer une entry danswebpack.config.js, inclure via{{ encore_entry_script_tags('ma-feature') }}, passer les données pardata-*. - Vanilla JS first : à chaque édition d'un fichier JS, migrer vers ES2020+ natif à comportement identique. Exceptions tolérées : peer deps (Select2, CKEditor).
console.log('[Module] …')partout : Terser strip en prod (drop_console: true).console.warn/erroraussi : pas pour du debug persistant.
Commandes Symfony — règles métier
- Toute commande modifiant la DB (INSERT/UPDATE/DELETE) doit accepter un
flag
--dry-run. - Toute commande financière doit logger via Monolog (channel dédié) en plus de la sortie console.
- Sortie console :
SymfonyStyle, pasecho.
Pipeline de validation pre-commit
flowchart LR
A["git commit"] --> B["GrumPHP"]
B --> C["PHP-CS-Fixer"]
B --> D["PHPStan"]
B --> E["lint:yaml + lint:twig"]
B --> F["PHPUnit ciblé"]
C & D & E & F --> G{"OK ?"}
G -- non --> H["commit refusé"]
G -- oui --> I["commit accepté"]
Pipeline GrumPHP : tout doit passer avant commit.
CHANGELOG — obligatoire avant chaque commit
| Fichier | Contenu | Public |
|---|---|---|
CHANGELOG.md | Technique : classes, migrations, breaking changes | Développeur |
CHANGELOG_USER.md | Fonctionnel : ce que voient utilisateurs/admins | Adhérent, trésorier |
Style PHP 8.4 — résumé
declare(strict_types=1);en tête de tout fichier.- Types de retour obligatoires (
voidinclus). - Constructor property promotion +
readonlyquand pertinent. - Enums PHP 8.1+ pour les statuts — 6 enums livrés dans
src/Enum/(pattern hybrideenumType, API string conservée, cases PascalCase) ; lesconst STATUS_*restent des littéraux synchronisés (PHP ^8.2 :Enum::Case->valueinterdit en expression constante). - Nommage : classes PascalCase suffixées (
Service,Repository,Controller,Admin) · méthodes camelCase ·find*pour repos ·is/has/canpour booléens · constantes UPPER_SNAKE_CASE. - PHPStan niveau cible 9 — pas de nouvelles suppressions sur le code produit.