docs(architecture): add global architecture documentation for Arcadia API

docs(architecture): add request lifecycle documentation for Arcadia API

docs(architecture): add execution, transactions, and domain events documentation for Arcadia API

docs(architecture): add guide for creating a new use case in Arcadia API

docs(exceptions): summarize exception handling and HTTP status codes in Arcadia API

docs(tests): outline testing strategy and conventions for Arcadia API

style(openapi): correct description formatting in OpenAPI documentation

chore: remove outdated exceptions documentation

test: remove obsolete tests documentation
This commit is contained in:
Jérémy Munsch 2026-08-21 10:41:07 +02:00
parent 20ae9a2307
commit 0d2b0124ee
11 changed files with 943 additions and 388 deletions

View file

@ -1,41 +1,40 @@
# Arcadia API
Arcadia API est une application construite en respectant les principes de la **Clean Architecture** et du **Domain-Driven Design (DDD)**.
## Sommaire
- [Arcadia API](#arcadia-api)
- [Sommaire](#sommaire)
- [Architecture](#architecture)
- [Principes d'implémentation](#principes-dimplémentation)
- [Gestion des Exceptions](#gestion-des-exceptions)
- [Tests \& Assurance Qualité](#tests--assurance-qualité)
**Arcadia API** est une API de référence conçue selon les principes de la **Clean Architecture**, du **Domain-Driven Design (DDD)** et de l'**Architecture Hexagonale (Ports & Adapters)**.
---
## Architecture
## 📚 Documentation pour les Développeurs
![Arcadia API Architecture](resources/architecture.jpg)
L'ensemble de la documentation technique et des guides pratiques est organisé dans le dossier [`documentation/`](documentation/) :
L'application est découpée en couches strictes respectant la règle de dépendance vers l'intérieur :
### 🏛️ Architecture & Fonctionnement
- **[01 - Architecture Globale](documentation/Architecture/01-architecture-globale.md)** : Vue d'ensemble des couches (`Domain`, `Application`, `Presentation`, `Infra`, `Shared`), responsabilités et règles de dépendances strictes.
- **[02 - Cycle de Vie d'une Requête (Bullet Tracer)](documentation/Architecture/02-cycle-de-vie-requete.md)** : Traçage pas-à-pas d'une requête HTTP depuis la route jusqu'à la sérialisation JSON.
- **[03 - Exécution, Transactions & Événements](documentation/Architecture/03-execution-et-transactions.md)** : `UsecaseBuilder`, `LaravelExecutor`, exécution transactionnelle et dispatch sécurisé post-commit (`TransactionalDomainEventDispatcher`).
- **[04 - Guide Nouveau Cas d'Usage](documentation/Architecture/04-guide-nouveau-usecase.md)** : Checklist pas-à-pas pour créer une nouvelle fonctionnalité de bout en bout.
1. **Domain** : Le coeur métier de l'application utilisant un language commun. Ne dépend de rien d'autre (**Shared** autorisé). Contient les *Entités*, *Value Objects*, et les interfaces (*Ports*) pour interagir avec le monde extérieur.
2. **Application** : Les *Use Cases*. Orchestre la logique métier en utilisant le Domaine. Ne dépend d'aucune infrastructure ou framework (sauf `Shared`).
3. **Presentation** : Les points d'entrée de l'application (*API Controllers*, *Presenters*). Formate les données pour l'extérieur et gère les requêtes HTTP ou une interface cli.
4. **Infra** : L'implémentation technique (Eloquent, base de données, réseau, etc.). Implémente les interfaces définies par le Domaine.
5. **Shared** : Le socle technique commun (interfaces DDD de base, *Value Objects* génériques comme `Pagination`).
### 📐 Conventions & Standards
- **[Conventions de Tests](documentation/Conventions/tests.md)** : Principes F.I.R.S.T., structure GWT, pyramide de tests, **Test Data Builders** et **Object Mothers**.
- **[Gestion des Exceptions](documentation/Conventions/exceptions.md)** : Matrice de correspondance entre exceptions applicatives/domaine et codes HTTP RFC 7807 (400, 404, 409, 422, 429, 500).
## Principes d'implémentation
---
- **API Read-only** : Pour l'instant, l'API ne fait qu'exposer des données. Nous utilisons donc le pattern `Directory` (lecture seule) plutôt que `Repository` (lecture/écriture) dans nos Ports de Domaine.
- **Pattern Presenter** : Les Use Cases (dans `Application`) retournent un `Output` agnostique. La couche `Presentation` utilise ensuite des `Presenters` spécifiques (ex: API) pour transformer cet `Output` en une vue exploitable par l'interface finale (JSON).
- **Validation** : La validation stricte du format (ex: HTTP) est gérée dans la couche `Presentation` (via les `FormRequest`). La validation des règles métier est garantie au coeur du `Domain` par les `Value Objects`, qui lèvent des exceptions de type `InvalidArgumentException` traduites en HTTP 422 par le gestionnaire d'erreurs global.
## 🚀 Commandes Utiles
## Gestion des Exceptions
```bash
# Lancer la suite de tests Pest
composer test
Consulter le document [documentation/Exceptions.md](documentation/Exceptions.md) pour la matrice complète de correspondance entre le type d'exception (`DomainException`, `ValidationException`, `NotFoundException`, `ConflictException`) et les codes HTTP RFC 7807 (`400`, `404`, `409`, `422`, `429`, `500`).
# Lancer l'analyse statique PHPStan (Niveau 10)
vendor/bin/phpstan analyse
## Tests & Assurance Qualité
# Vérifier le formattage du code PSR-12 (Pint)
vendor/bin/pint --test
Consulter le document [documentation/Tests.md](documentation/Tests.md) pour la stratégie de test en pyramide (Unit, Integration, Feature, Arch) et les commandes d'exécution avec couverture de code.
# Verifier les conventions et regles de lint (PHP Code Sniffer)
vendor/bin/phpcs
# Corriger automatiquement les conventiosn (PHP Code Beautifier)
vendor/bin/phpcbf
```

View file

@ -7,273 +7,273 @@ Voici une classification des champs en catégories sémantiques cohérentes pour
## 🏷️ Classification des champs nutritionnels
### 1. **Identification / Métadonnées**
| Champ PHP | Constante | Description |
|---|---|---|
| `nom` | `NOM` | Nom du produit/fiche |
| `numero` | `NUMERO` | Numéro de la fiche |
| `source` | `SOURCE` | Source des données |
| Champ PHP | Constante | Description |
| --------- | --------- | -------------------- |
| `nom` | `NOM` | Nom du produit/fiche |
| `numero` | `NUMERO` | Numéro de la fiche |
| `source` | `SOURCE` | Source des données |
---
### 2. **Valeurs énergétiques — par 100g/100ml (référence `_R`)**
| Champ PHP | Constante | Description |
|---|---|---|
| `tailleReference` | `TAILLE_REF` | Taille de la portion de référence |
| `valEnergKjReference` | `VAL_ENERG_KJ_R` | Énergie en kJ / 100g |
| `valEnergKcalReference` | `VAL_ENERG_KCAL_R` | Énergie en kcal / 100g |
### 2. **Valeurs énergétiques - par 100g/100ml (référence `_R`)**
| Champ PHP | Constante | Description |
| ----------------------- | ------------------ | --------------------------------- |
| `tailleReference` | `TAILLE_REF` | Taille de la portion de référence |
| `valEnergKjReference` | `VAL_ENERG_KJ_R` | Énergie en kJ / 100g |
| `valEnergKcalReference` | `VAL_ENERG_KCAL_R` | Énergie en kcal / 100g |
---
### 3. **Macronutriments — par 100g/100ml (`_R`) + précisions (`_PREC`)**
| Champ PHP | Constante | Description |
|---|---|---|
| `matGrassReference` / `matGrassReferencePrec` | `MAT_GRASS_R` | Matières grasses / 100g |
| `acideGrasSatureReference` / `acideGrasSatureReferencePrec` | `AGS_R` | Acides gras saturés / 100g |
| `glucidesReference` / `glucidesReferencePrec` | `GLUCIDES_R` | Glucides / 100g |
| `sucreReference` / `sucreReferencePrec` | `SUCRE_R` | Sucres / 100g |
| `fibAlimReference` / `fibAlimReferencePrec` | `FIB_ALIM_R` | Fibres alimentaires / 100g |
| `proteineReference` / `proteineReferencePrec` | `PROTEINE_R` | Protéines / 100g |
| `selReference` / `selReferencePrec` | `SEL_R` | Sel / 100g |
| `sodiumReference` | `SODIUM_R` | Sodium / 100g |
### 3. **Macronutriments - par 100g/100ml (`_R`) + précisions (`_PREC`)**
| Champ PHP | Constante | Description |
| ----------------------------------------------------------- | ------------- | -------------------------- |
| `matGrassReference` / `matGrassReferencePrec` | `MAT_GRASS_R` | Matières grasses / 100g |
| `acideGrasSatureReference` / `acideGrasSatureReferencePrec` | `AGS_R` | Acides gras saturés / 100g |
| `glucidesReference` / `glucidesReferencePrec` | `GLUCIDES_R` | Glucides / 100g |
| `sucreReference` / `sucreReferencePrec` | `SUCRE_R` | Sucres / 100g |
| `fibAlimReference` / `fibAlimReferencePrec` | `FIB_ALIM_R` | Fibres alimentaires / 100g |
| `proteineReference` / `proteineReferencePrec` | `PROTEINE_R` | Protéines / 100g |
| `selReference` / `selReferencePrec` | `SEL_R` | Sel / 100g |
| `sodiumReference` | `SODIUM_R` | Sodium / 100g |
---
### 4. **Fruits, légumes, noix — par 100g/100ml (`_R`)**
| Champ PHP | Constante | Description |
|---|---|---|
| `fruitsReference` | `FRUITS_R` | Teneur en fruits / 100g |
### 4. **Fruits, légumes, noix - par 100g/100ml (`_R`)**
| Champ PHP | Constante | Description |
| -------------------------- | --------------------- | ------------------------------- |
| `fruitsReference` | `FRUITS_R` | Teneur en fruits / 100g |
| `fruitsCoqueSechReference` | `FRUITS_COQUE_SECH_R` | Fruits à coque et séchés / 100g |
| `pourcentFruits` | `POURCENT_FRUITS` | % fruits (global) |
| `pourcentFruits` | `POURCENT_FRUITS` | % fruits (global) |
---
### 5. **Valeurs nutritionnelles — par portion (`_P`) + précisions (`_PREC`)**
| Champ PHP | Constante | Description |
|---|---|---|
| `taillePortion` | `TAILLE_PORT` | Taille de la portion |
| `valEnergKjPortion` | `VAL_ENERG_KJ_P` | Énergie kJ / portion |
| `valEnergKcalPortion` | `VAL_ENERG_KCAL_P` | Énergie kcal / portion |
| `matGrassPortion` / `matGrassPortionPrec` | `MAT_GRASS_P` | Matières grasses / portion |
| `acideGrasSaturePortion` / `acideGrasSaturePortionPrec` | `AGS_P` | Acides gras saturés / portion |
| `glucidesPortion` / `glucidesPortionPrec` | `GLUCIDES_P` | Glucides / portion |
| `sucrePortion` / `sucrePortionPrec` | `SUCRE_P` | Sucres / portion |
| `fibAlimPortion` / `fibAlimPortionPrec` | `FIB_ALIM_P` | Fibres / portion |
| `proteinePortion` / `proteinePortionPrec` | `PROTEINE_P` | Protéines / portion |
| `sodiumPortion` | `SODIUM_P` | Sodium / portion |
| `selPortion` / `selPortionPrec` | `SEL_P` | Sel / portion |
| `fruitsPortion` | `FRUITS_P` | Fruits / portion |
| `fruitsCoqueSechPortion` | `FRUITS_COQUE_SECH_P` | Fruits à coque et séchés / portion |
### 5. **Valeurs nutritionnelles - par portion (`_P`) + précisions (`_PREC`)**
| Champ PHP | Constante | Description |
| ------------------------------------------------------- | --------------------- | ---------------------------------- |
| `taillePortion` | `TAILLE_PORT` | Taille de la portion |
| `valEnergKjPortion` | `VAL_ENERG_KJ_P` | Énergie kJ / portion |
| `valEnergKcalPortion` | `VAL_ENERG_KCAL_P` | Énergie kcal / portion |
| `matGrassPortion` / `matGrassPortionPrec` | `MAT_GRASS_P` | Matières grasses / portion |
| `acideGrasSaturePortion` / `acideGrasSaturePortionPrec` | `AGS_P` | Acides gras saturés / portion |
| `glucidesPortion` / `glucidesPortionPrec` | `GLUCIDES_P` | Glucides / portion |
| `sucrePortion` / `sucrePortionPrec` | `SUCRE_P` | Sucres / portion |
| `fibAlimPortion` / `fibAlimPortionPrec` | `FIB_ALIM_P` | Fibres / portion |
| `proteinePortion` / `proteinePortionPrec` | `PROTEINE_P` | Protéines / portion |
| `sodiumPortion` | `SODIUM_P` | Sodium / portion |
| `selPortion` / `selPortionPrec` | `SEL_P` | Sel / portion |
| `fruitsPortion` | `FRUITS_P` | Fruits / portion |
| `fruitsCoqueSechPortion` | `FRUITS_COQUE_SECH_P` | Fruits à coque et séchés / portion |
---
### 6. **Pourcentages des apports de référence (`POURCENT_APPORT_REF_*`)**
| Champ PHP | Constante | Description |
|---|---|---|
| `pourcentApportRefE14P` | `POURCENT_APPORT_REF_E14_P` | % AR énergie E14 / portion |
| `pourcentApportRefKjoP` | `POURCENT_APPORT_REF_KJO_P` | % AR énergie kJ / portion |
| `pourcentApportRefFatP` | `POURCENT_APPORT_REF_FAT_P` | % AR matières grasses / portion |
| Champ PHP | Constante | Description |
| -------------------------- | ------------------------------ | -------------------------------- |
| `pourcentApportRefE14P` | `POURCENT_APPORT_REF_E14_P` | % AR énergie E14 / portion |
| `pourcentApportRefKjoP` | `POURCENT_APPORT_REF_KJO_P` | % AR énergie kJ / portion |
| `pourcentApportRefFatP` | `POURCENT_APPORT_REF_FAT_P` | % AR matières grasses / portion |
| `pourcentApportRefFatsatP` | `POURCENT_APPORT_REF_FATSAT_P` | % AR graisses saturées / portion |
| `pourcentApportRefChovalP` | `POURCENT_APPORT_REF_CHOVAL_P` | % AR glucides / portion |
| `pourcentApportRefSugarP` | `POURCENT_APPORT_REF_SUGAR_P` | % AR sucres / portion |
| `pourcentApportRefProP` | `POURCENT_APPORT_REF_PRO_P` | % AR protéines / portion |
| `pourcentApportRefSalteqP` | `POURCENT_APPORT_REF_SALTEQ_P` | % AR sel / portion |
| `pourcentApportRefChovalP` | `POURCENT_APPORT_REF_CHOVAL_P` | % AR glucides / portion |
| `pourcentApportRefSugarP` | `POURCENT_APPORT_REF_SUGAR_P` | % AR sucres / portion |
| `pourcentApportRefProP` | `POURCENT_APPORT_REF_PRO_P` | % AR protéines / portion |
| `pourcentApportRefSalteqP` | `POURCENT_APPORT_REF_SALTEQ_P` | % AR sel / portion |
---
### 7. **Nutri-Score**
| Champ PHP | Constante | Description |
|---|---|---|
| `composanteN` | `COMPOSANTE_N` | Composante négative (énergie, graisses, sucres, sel) |
| `composanteN2022` | `COMPOSANTE_N_2022` | Composante négative — algorithme 2022 |
| `composanteP` | `COMPOSANTE_P` | Composante positive (fibres, protéines, fruits) |
| `composanteP2022` | `COMPOSANTE_P_2022` | Composante positive — algorithme 2022 |
| `scoreNutri` | `SCORE_NUTRI` | Score Nutri-Score brut |
| `scoreNutri2022` | `SCORE_NUTRI_2022` | Score Nutri-Score 2022 |
| `classement` | `CLASSEMENT` | Lettre Nutri-Score (A–E) |
| `nutriScore2022` | `NUTRI_SCORE_2022` | Nutri-Score lettre — algorithme 2022 |
| `nutriAArt` | `NUTRI_A_ART` | Flag article Nutri-Score A |
| Champ PHP | Constante | Description |
| ----------------- | ------------------- | ---------------------------------------------------- |
| `composanteN` | `COMPOSANTE_N` | Composante négative (énergie, graisses, sucres, sel) |
| `composanteN2022` | `COMPOSANTE_N_2022` | Composante négative - algorithme 2022 |
| `composanteP` | `COMPOSANTE_P` | Composante positive (fibres, protéines, fruits) |
| `composanteP2022` | `COMPOSANTE_P_2022` | Composante positive - algorithme 2022 |
| `scoreNutri` | `SCORE_NUTRI` | Score Nutri-Score brut |
| `scoreNutri2022` | `SCORE_NUTRI_2022` | Score Nutri-Score 2022 |
| `classement` | `CLASSEMENT` | Lettre Nutri-Score (A–E) |
| `nutriScore2022` | `NUTRI_SCORE_2022` | Nutri-Score lettre - algorithme 2022 |
| `nutriAArt` | `NUTRI_A_ART` | Flag article Nutri-Score A |
---
### 8. **Points de calcul du Nutri-Score**
| Champ PHP | Constante | Description |
|---|---|---|
| `pointsEnergie` | `POINTS_ENERGIE` | Points énergie |
| `pointsAcideGrasSature` | `POINTS_AGS` | Points acides gras saturés |
| `pointsSucres` | `POINTS_SUCRES` | Points sucres |
| `pointsSel` | `POINTS_SEL` | Points sel |
| `pointsFibres` | `POINTS_FIBRES` | Points fibres |
| `pointsProteines` | `POINTS_PROTEINES` | Points protéines |
| Champ PHP | Constante | Description |
| ------------------------------ | ------------------ | -------------------------------- |
| `pointsEnergie` | `POINTS_ENERGIE` | Points énergie |
| `pointsAcideGrasSature` | `POINTS_AGS` | Points acides gras saturés |
| `pointsSucres` | `POINTS_SUCRES` | Points sucres |
| `pointsSel` | `POINTS_SEL` | Points sel |
| `pointsFibres` | `POINTS_FIBRES` | Points fibres |
| `pointsProteines` | `POINTS_PROTEINES` | Points protéines |
| `pointsFruitsLegumesNoixFrais` | `POINTS_FNL_FRAIS` | Points fruits/noix/légumes frais |
---
### 9. **FNL (Fruits, Noix, Légumes)**
| Champ PHP | Constante | Description |
|---|---|---|
| `fruitLegumeNoixFrais` | `FNL_FRAIS` | Teneur FNL frais / 100g |
| `fruitLegumeNoixFraisPortion` | `FNL_FRAIS_PORTION` | Teneur FNL frais / portion |
| `pourcentFruitsLegumesNoixFrais` | `POURCENT_FNL_FRAIS` | % FNL frais |
| Champ PHP | Constante | Description |
| -------------------------------- | -------------------- | -------------------------- |
| `fruitLegumeNoixFrais` | `FNL_FRAIS` | Teneur FNL frais / 100g |
| `fruitLegumeNoixFraisPortion` | `FNL_FRAIS_PORTION` | Teneur FNL frais / portion |
| `pourcentFruitsLegumesNoixFrais` | `POURCENT_FNL_FRAIS` | % FNL frais |
---
### 10. **Ratio / Indicateurs complémentaires**
| Champ PHP | Constante | Description |
|---|---|---|
| `pSurL` | `P_SUR_L` | Ratio P/L (protéines sur lipides) |
| Champ PHP | Constante | Description |
| ------------- | -------------- | ------------------------------------------- |
| `pSurL` | `P_SUR_L` | Ratio P/L (protéines sur lipides) |
| `viandeRouge` | `VIANDE_ROUGE` | Flag viande rouge (impact Nutri-Score 2022) |
---
### 11. **Micronutriments détaillés (`NUT_*`)**
Cette famille regroupe les vitamines, minéraux, acides aminés et acides gras détaillés — typiquement issus de bases comme **Ciqual** ou **FoodData Central**.
Cette famille regroupe les vitamines, minéraux, acides aminés et acides gras détaillés - typiquement issus de bases comme **Ciqual** ou **FoodData Central**.
#### 11a. Minéraux & Oligo-éléments
| Champ PHP | Constante |
|---|---|
| `nutCa` | `NUT_CA` — Calcium |
| `nutMg` | `NUT_MG` — Magnésium |
| `nutK` | `NUT_K` — Potassium |
| `nutP` | `NUT_P` — Phosphore |
| `nutCld` | `NUT_CLD` — Chlorure |
| `nutFe` | `NUT_FE` — Fer |
| `nutZn` | `NUT_ZN` — Zinc |
| `nutCu` | `NUT_CU` — Cuivre |
| `nutMn` | `NUT_MN` — Manganèse |
| `nutSe` | `NUT_SE` — Sélénium |
| `nutId` | `NUT_ID` — Iode |
| `nutCr` | `NUT_CR` — Chrome |
| `nutMo` | `NUT_MO` — Molybdène |
| `nutS6` | `NUT_S6` — Soufre |
| `nutNitra` | `NUT_NITRA` — Nitrates |
| Champ PHP | Constante |
| ---------- | ---------------------- |
| `nutCa` | `NUT_CA` - Calcium |
| `nutMg` | `NUT_MG` - Magnésium |
| `nutK` | `NUT_K` - Potassium |
| `nutP` | `NUT_P` - Phosphore |
| `nutCld` | `NUT_CLD` - Chlorure |
| `nutFe` | `NUT_FE` - Fer |
| `nutZn` | `NUT_ZN` - Zinc |
| `nutCu` | `NUT_CU` - Cuivre |
| `nutMn` | `NUT_MN` - Manganèse |
| `nutSe` | `NUT_SE` - Sélénium |
| `nutId` | `NUT_ID` - Iode |
| `nutCr` | `NUT_CR` - Chrome |
| `nutMo` | `NUT_MO` - Molybdène |
| `nutS6` | `NUT_S6` - Soufre |
| `nutNitra` | `NUT_NITRA` - Nitrates |
#### 11b. Vitamines
| Champ PHP | Constante |
|---|---|
| `nutVita` | `NUT_VITA` — Vitamine A |
| `nutVitb6` | `NUT_VITB6` — Vitamine B6 |
| `nutVitb12` | `NUT_VITB12` — Vitamine B12 |
| `nutVitc` | `NUT_VITC` — Vitamine C |
| `nutVitd` | `NUT_VITD` — Vitamine D |
| `nutVite` | `NUT_VITE` — Vitamine E |
| `nutVitk` | `NUT_VITK` — Vitamine K |
| `nutChocal` | `NUT_CHOCAL` — Cholécalciférol (vit. D3) |
| `nutNia` | `NUT_NIA` — Niacine (B3) |
| `nutRibf` | `NUT_RIBF` — Riboflavine (B2) |
| `nutThia` | `NUT_THIA` — Thiamine (B1) |
| `nutPantac` | `NUT_PANTAC` — Acide pantothénique (B5) |
| `nutBiot` | `NUT_BIOT` — Biotine (B8) |
| `nutFoldfe` | `NUT_FOLDFE` — Folates équivalents (B9) |
| Champ PHP | Constante |
| ----------- | ---------------------------------------- |
| `nutVita` | `NUT_VITA` - Vitamine A |
| `nutVitb6` | `NUT_VITB6` - Vitamine B6 |
| `nutVitb12` | `NUT_VITB12` - Vitamine B12 |
| `nutVitc` | `NUT_VITC` - Vitamine C |
| `nutVitd` | `NUT_VITD` - Vitamine D |
| `nutVite` | `NUT_VITE` - Vitamine E |
| `nutVitk` | `NUT_VITK` - Vitamine K |
| `nutChocal` | `NUT_CHOCAL` - Cholécalciférol (vit. D3) |
| `nutNia` | `NUT_NIA` - Niacine (B3) |
| `nutRibf` | `NUT_RIBF` - Riboflavine (B2) |
| `nutThia` | `NUT_THIA` - Thiamine (B1) |
| `nutPantac` | `NUT_PANTAC` - Acide pantothénique (B5) |
| `nutBiot` | `NUT_BIOT` - Biotine (B8) |
| `nutFoldfe` | `NUT_FOLDFE` - Folates équivalents (B9) |
#### 11c. Glucides détaillés
| Champ PHP | Constante |
|---|---|
| `nutStarch` | `NUT_STARCH` — Amidon |
| `nutMaltdex` | `NUT_MALTDEX` — Maltodextrines |
| `nutSucs` | `NUT_SUCS` — Saccharose |
| `nutGlus` | `NUT_GLUS` — Glucose |
| `nutLacs` | `NUT_LACS` — Lactose |
| `nutMals` | `NUT_MALS` — Maltose |
| `nutGalfb` | `NUT_GALFB` — Galactose |
| `nutGHc` | `NUT_G_HC` — Glucides hydrates de carbone |
| `nutFrufb` | `NUT_FRUFB` — Fructose lié (fibre) |
| `nutFrus` | `NUT_FRUS` — Fructose libre |
| `nutOlsac` | `NUT_OLSAC` — Oligosaccharides |
| `nutPolyl` | `NUT_POLYL` — Polyols |
| `nutFibsol` | `NUT_FIBSOL` — Fibres solubles |
| `nutInotl` | `NUT_INOTL` — Inositol |
| Champ PHP | Constante |
| ------------ | ----------------------------------------- |
| `nutStarch` | `NUT_STARCH` - Amidon |
| `nutMaltdex` | `NUT_MALTDEX` - Maltodextrines |
| `nutSucs` | `NUT_SUCS` - Saccharose |
| `nutGlus` | `NUT_GLUS` - Glucose |
| `nutLacs` | `NUT_LACS` - Lactose |
| `nutMals` | `NUT_MALS` - Maltose |
| `nutGalfb` | `NUT_GALFB` - Galactose |
| `nutGHc` | `NUT_G_HC` - Glucides hydrates de carbone |
| `nutFrufb` | `NUT_FRUFB` - Fructose lié (fibre) |
| `nutFrus` | `NUT_FRUS` - Fructose libre |
| `nutOlsac` | `NUT_OLSAC` - Oligosaccharides |
| `nutPolyl` | `NUT_POLYL` - Polyols |
| `nutFibsol` | `NUT_FIBSOL` - Fibres solubles |
| `nutInotl` | `NUT_INOTL` - Inositol |
#### 11d. Lipides / Acides gras détaillés
> Nomenclature lipidique standard (IUPAC / Ciqual) — `FXXdY` = chaîne carbonée XX, Y insaturations
> Nomenclature lipidique standard (IUPAC / Ciqual) - `FXXdY` = chaîne carbonée XX, Y insaturations
| Champ PHP | Constante | Nom usuel |
|---|---|---|
| `nutAgt` | `NUT_AGT` | Acides gras trans |
| `nutChol` | `NUT_CHOL` | Cholestérol |
| `nutFd` | `NUT_FD` | Lipides totaux |
| `nutFpalm` | `NUT_FPALM` | Palmitoleïque (C16:1) |
| `nutF16d0` | `NUT_F16D0` | Acide palmitique (C16:0) |
| `nutFsteariq` | `NUT_FSTEARIQ` | Acide stéarique (C18:0) |
| `nutFoleiq` | `NUT_FOLEIQ` | Acide oléique (C18:1, ω-9) |
| `nutF18d` | `NUT_F18D` | C18 insaturé (générique) |
| `nutF18d2cn6` | `NUT_F18D2CN6` | Acide linoléique (C18:2, ω-6) |
| `nutF18d3n3` | `NUT_F18D3N3` | Acide α-linolénique (C18:3, ω-3) |
| `nutF20d4` | `NUT_F20D4` | Acide arachidonique (C20:4) |
| `nutF20d5n3` | `NUT_F20D5N3` | EPA (C20:5, ω-3) |
| `nutF22d6n3` | `NUT_F22D6N3` | DHA (C22:6, ω-3) |
| `nutF11eico` | `NUT_F11EICO` | Acide gondoïque (C20:1) |
| `nutFarachidiq` | `NUT_FARACHIDIQ` | Acide arachidique (C20:0) |
| `nutFeruciq` | `NUT_FERUCIQ` | Acide érucique (C22:1) |
| `nutFlauriq` | `NUT_FLAURIQ` | Acide laurique (C12:0) |
| `nutFmyristiq` | `NUT_FMYRISTIQ` | Acide myristique (C14:0) |
| `nutFbutyriq` | `NUT_FBUTYRIQ` | Acide butyrique (C4:0) |
| `nutFcaproiq` | `NUT_FCAPROIQ` | Acide caproïque (C6:0) |
| `nutFcapryliq` | `NUT_FCAPRYLIQ` | Acide caprylique (C8:0) |
| `nutFcapriq` | `NUT_FCAPRIQ` | Acide caprique (C10:0) |
| `nutFapun3` | `NUT_FAPUN3` | AG polyinsaturés ω-3 |
| `nutFapun6` | `NUT_FAPUN6` | AG polyinsaturés ω-6 |
| `nutFapun9` | `NUT_FAPUN9` | AG polyinsaturés ω-9 |
| `nutFapucis` | `NUT_FAPUCIS` | AG polyinsaturés cis |
| `nutFapulc` | `NUT_FAPULC` | AG polyinsaturés longue chaîne |
| `nutFamscis` | `NUT_FAMSCIS` | AG monoinsaturés cis |
| `nutFacetiq` | `NUT_FACETIQ` | Acide acétique (C2:0) |
| `nutFpropioniq` | `NUT_FPROPIONIQ` | Acide propionique (C3:0) |
| `nutFvaleriq` | `NUT_FVALERIQ` | Acide valérique (C5:0) |
| `nutFcarboceriq` | `NUT_FCARBOCERIQ` | Acide carbocérique (C26:0) |
| `nutFbeheniq` | `NUT_FBEHENIQ` | Acide béhénique (C22:0) |
| `nutFlignoceriq` | `NUT_FLIGNOCERIQ` | Acide lignocérique (C24:0) |
| `nutFnervoniq` | `NUT_FNERVONIQ` | Acide nervonique (C24:1) |
| `nutFlaceroiq` | `NUT_FLACEROIQ` | Acide lacéroïque (C32:0) |
| `nutFmelissiq` | `NUT_FMELISSIQ` | Acide mélissique (C30:0) |
| `nutFmontaniq` | `NUT_FMONTANIQ` | Acide montanique (C28:0) |
| `nutFcerotiq` | `NUT_FCEROTIQ` | Acide cérotique (C26:0 alt.) |
| `nutFpsylliq` | `NUT_FPSYLLIQ` | Acide psyllique |
| `nutFgeddiq` | `NUT_FGEDDIQ` | Acide geddique (C20:1) |
| `nutFmargariq` | `NUT_FMARGARIQ` | Acide margarique (C17:0) |
| `nutFenatiq` | `NUT_FENATIQ` | Acide énanthique (C7:0) |
| `nutFformiq` | `NUT_FFORMIQ` | Acide formique (C1:0) |
| `nutFhyeniq` | `NUT_FHYENIQ` | Acide hyénique |
| `nutFrufb` | `NUT_FRUFB` | *(déjà classé glucides)* |
| `nutFrus` | `NUT_FRUS` | *(déjà classé glucides)* |
| `nutFpelargoniq` | `NUT_FPELARGONIQ` | Acide pélargonique (C9:0) |
| `nutFundecyliq` | `NUT_FUNDECYLIQ` | Acide undécylique (C11:0) |
| `nutFpentadecyliq` | `NUT_FPENTADECYLIQ` | Acide pentadécylique (C15:0) |
| `nutFtridecyliq` | `NUT_FTRIDECYLIQ` | Acide tridécylique (C13:0) |
| `nutFtricosiliq` | `NUT_FTRICOSILIQ` | Acide tétracosanoïque (C23:0) |
| `nutFnonacosyliq` | `NUT_FNONACOSYLIQ` | Acide nonacosylique (C29:0) |
| `nutFnonadylique` | `NUT_FNONADYLIQUE` | Acide nonadylique |
| `nutFheneicosyliq` | `NUT_FHENEICOSYLIQ` | Acide héneicosylique (C21:0) |
| `nutFhentriacontyliq` | `NUT_FHENTRIACONTYLIQ` | C31:0 |
| `nutFheptatriacontyliq` | `NUT_FHEPTATRIACONTYLIQ` | C37:0 |
| `nutFhextriacontyliq` | `NUT_FHEXTRIACONTYLIQ` | C36:0 |
| `nutFoctatriacontyliq` | `NUT_FOCTATRIACONTYLIQ` | C38:0 |
| `nutF19d` | `NUT_F19D` | C19 insaturé |
| `nutFoldfe` | `NUT_FOLDFE` | *(déjà classé vitamines — folates)* |
| Champ PHP | Constante | Nom usuel |
| ----------------------- | ------------------------ | ----------------------------------- |
| `nutAgt` | `NUT_AGT` | Acides gras trans |
| `nutChol` | `NUT_CHOL` | Cholestérol |
| `nutFd` | `NUT_FD` | Lipides totaux |
| `nutFpalm` | `NUT_FPALM` | Palmitoleïque (C16:1) |
| `nutF16d0` | `NUT_F16D0` | Acide palmitique (C16:0) |
| `nutFsteariq` | `NUT_FSTEARIQ` | Acide stéarique (C18:0) |
| `nutFoleiq` | `NUT_FOLEIQ` | Acide oléique (C18:1, ω-9) |
| `nutF18d` | `NUT_F18D` | C18 insaturé (générique) |
| `nutF18d2cn6` | `NUT_F18D2CN6` | Acide linoléique (C18:2, ω-6) |
| `nutF18d3n3` | `NUT_F18D3N3` | Acide α-linolénique (C18:3, ω-3) |
| `nutF20d4` | `NUT_F20D4` | Acide arachidonique (C20:4) |
| `nutF20d5n3` | `NUT_F20D5N3` | EPA (C20:5, ω-3) |
| `nutF22d6n3` | `NUT_F22D6N3` | DHA (C22:6, ω-3) |
| `nutF11eico` | `NUT_F11EICO` | Acide gondoïque (C20:1) |
| `nutFarachidiq` | `NUT_FARACHIDIQ` | Acide arachidique (C20:0) |
| `nutFeruciq` | `NUT_FERUCIQ` | Acide érucique (C22:1) |
| `nutFlauriq` | `NUT_FLAURIQ` | Acide laurique (C12:0) |
| `nutFmyristiq` | `NUT_FMYRISTIQ` | Acide myristique (C14:0) |
| `nutFbutyriq` | `NUT_FBUTYRIQ` | Acide butyrique (C4:0) |
| `nutFcaproiq` | `NUT_FCAPROIQ` | Acide caproïque (C6:0) |
| `nutFcapryliq` | `NUT_FCAPRYLIQ` | Acide caprylique (C8:0) |
| `nutFcapriq` | `NUT_FCAPRIQ` | Acide caprique (C10:0) |
| `nutFapun3` | `NUT_FAPUN3` | AG polyinsaturés ω-3 |
| `nutFapun6` | `NUT_FAPUN6` | AG polyinsaturés ω-6 |
| `nutFapun9` | `NUT_FAPUN9` | AG polyinsaturés ω-9 |
| `nutFapucis` | `NUT_FAPUCIS` | AG polyinsaturés cis |
| `nutFapulc` | `NUT_FAPULC` | AG polyinsaturés longue chaîne |
| `nutFamscis` | `NUT_FAMSCIS` | AG monoinsaturés cis |
| `nutFacetiq` | `NUT_FACETIQ` | Acide acétique (C2:0) |
| `nutFpropioniq` | `NUT_FPROPIONIQ` | Acide propionique (C3:0) |
| `nutFvaleriq` | `NUT_FVALERIQ` | Acide valérique (C5:0) |
| `nutFcarboceriq` | `NUT_FCARBOCERIQ` | Acide carbocérique (C26:0) |
| `nutFbeheniq` | `NUT_FBEHENIQ` | Acide béhénique (C22:0) |
| `nutFlignoceriq` | `NUT_FLIGNOCERIQ` | Acide lignocérique (C24:0) |
| `nutFnervoniq` | `NUT_FNERVONIQ` | Acide nervonique (C24:1) |
| `nutFlaceroiq` | `NUT_FLACEROIQ` | Acide lacéroïque (C32:0) |
| `nutFmelissiq` | `NUT_FMELISSIQ` | Acide mélissique (C30:0) |
| `nutFmontaniq` | `NUT_FMONTANIQ` | Acide montanique (C28:0) |
| `nutFcerotiq` | `NUT_FCEROTIQ` | Acide cérotique (C26:0 alt.) |
| `nutFpsylliq` | `NUT_FPSYLLIQ` | Acide psyllique |
| `nutFgeddiq` | `NUT_FGEDDIQ` | Acide geddique (C20:1) |
| `nutFmargariq` | `NUT_FMARGARIQ` | Acide margarique (C17:0) |
| `nutFenatiq` | `NUT_FENATIQ` | Acide énanthique (C7:0) |
| `nutFformiq` | `NUT_FFORMIQ` | Acide formique (C1:0) |
| `nutFhyeniq` | `NUT_FHYENIQ` | Acide hyénique |
| `nutFrufb` | `NUT_FRUFB` | *(déjà classé glucides)* |
| `nutFrus` | `NUT_FRUS` | *(déjà classé glucides)* |
| `nutFpelargoniq` | `NUT_FPELARGONIQ` | Acide pélargonique (C9:0) |
| `nutFundecyliq` | `NUT_FUNDECYLIQ` | Acide undécylique (C11:0) |
| `nutFpentadecyliq` | `NUT_FPENTADECYLIQ` | Acide pentadécylique (C15:0) |
| `nutFtridecyliq` | `NUT_FTRIDECYLIQ` | Acide tridécylique (C13:0) |
| `nutFtricosiliq` | `NUT_FTRICOSILIQ` | Acide tétracosanoïque (C23:0) |
| `nutFnonacosyliq` | `NUT_FNONACOSYLIQ` | Acide nonacosylique (C29:0) |
| `nutFnonadylique` | `NUT_FNONADYLIQUE` | Acide nonadylique |
| `nutFheneicosyliq` | `NUT_FHENEICOSYLIQ` | Acide héneicosylique (C21:0) |
| `nutFhentriacontyliq` | `NUT_FHENTRIACONTYLIQ` | C31:0 |
| `nutFheptatriacontyliq` | `NUT_FHEPTATRIACONTYLIQ` | C37:0 |
| `nutFhextriacontyliq` | `NUT_FHEXTRIACONTYLIQ` | C36:0 |
| `nutFoctatriacontyliq` | `NUT_FOCTATRIACONTYLIQ` | C38:0 |
| `nutF19d` | `NUT_F19D` | C19 insaturé |
| `nutFoldfe` | `NUT_FOLDFE` | *(déjà classé vitamines - folates)* |
#### 11e. Acides aminés & protéines spécifiques
| Champ PHP | Constante | Description |
|---|---|---|
| `nutAae` | `NUT_AAE` | Acides aminés essentiels (total) |
| `nutCasn` | `NUT_CASN` | Caséines |
| `nutWhey` | `NUT_WHEY` | Protéines de lactosérum |
| Champ PHP | Constante | Description |
| --------- | ---------- | -------------------------------- |
| `nutAae` | `NUT_AAE` | Acides aminés essentiels (total) |
| `nutCasn` | `NUT_CASN` | Caséines |
| `nutWhey` | `NUT_WHEY` | Protéines de lactosérum |
#### 11f. Autres composés bioactifs / spéciaux
| Champ PHP | Constante | Description |
|---|---|---|
| `nutCholn` | `NUT_CHOLN` | Choline |
| `nutCarnitine` | `NUT_CARNITINE` | Carnitine |
| `nutTau` | `NUT_TAU` | Taurine |
| Champ PHP | Constante | Description |
| --------------- | ---------------- | ----------- |
| `nutCholn` | `NUT_CHOLN` | Choline |
| `nutCarnitine` | `NUT_CARNITINE` | Carnitine |
| `nutTau` | `NUT_TAU` | Taurine |
| `nutNucleotide` | `NUT_NUCLEOTIDE` | Nucléotides |
---
## ⚠️ Points d'attention
1. **Doublon détecté** : `'acideGrasSaturePortion' => 'AGS_P'` est déclaré **deux fois** — l'une des deux entrées sera silencieusement ignorée par PHP.
1. **Doublon détecté** : `'acideGrasSaturePortion' => 'AGS_P'` est déclaré **deux fois** - l'une des deux entrées sera silencieusement ignorée par PHP.
2. **Champs `_PREC`** : Les suffixes `_PREC` semblent indiquer la **précision de mesure** (ex. `<`, `~`, tracé) — à documenter explicitement.
2. **Champs `_PREC`** : Les suffixes `_PREC` semblent indiquer la **précision de mesure** (ex. `<`, `~`, tracé) - à documenter explicitement.
3. **Champs MISSING** : Nombreux champs présents dans le référentiel INCO mais absents du mapping — notamment toute la série `POURCENT_APPORT_REF_*_R` (apports de référence pour 100g) et certains AG comme `FATSAT_R`, `CHOVAL_R`, `SUGAR_R`.
3. **Champs MISSING** : Nombreux champs présents dans le référentiel INCO mais absents du mapping - notamment toute la série `POURCENT_APPORT_REF_*_R` (apports de référence pour 100g) et certains AG comme `FATSAT_R`, `CHOVAL_R`, `SUGAR_R`.
4. **`classement` / `nutriscore`** : Vous notez vous-même que `CLASSEMENT` ne devrait pas être dans `INCO_COMPO` — à externaliser dans un objet Nutri-Score dédié.
4. **`classement` / `nutriscore`** : Vous notez vous-même que `CLASSEMENT` ne devrait pas être dans `INCO_COMPO` - à externaliser dans un objet Nutri-Score dédié.

View file

@ -0,0 +1,95 @@
# Architecture Globale - Arcadia API
Ce document présente l'architecture logicielle d'**Arcadia API** à destination des développeurs.
---
## 🏛️ Les 3 Piliers Architecturaux
Arcadia API combine 3 paradigmes éprouvés pour créer une base de code hautement maintenable, testable et découplée du framework :
1. **Clean Architecture (Robert C. Martin)** : Séparation en couches concentriques avec **règle de dépendance stricte vers l'intérieur**.
2. **Domain-Driven Design (Eric Evans)** : Modèle métier riche et expressif (Entités, Value Objects, Agrégats, Événements de Domaine), indépendant de tout détail technique.
3. **Architecture Hexagonale (Alistair Cockburn)** : Ports (interfaces dans le Domaine) et Adaptateurs (implémentations techniques dans l'Infrastructure).
```
┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION │
│ (API Controllers, FormRequests, Presenters, Views, DTOs) │
├──────────────────────────────┬──────────────────────────────┤
│ APPLICATION │ INFRA │
│ (Use Cases, Input, Output) │ (Eloquent, DB, Cache, Logs) │
├──────────────────────────────┴──────────────────────────────┤
│ DOMAIN │
│ (Entities, Value Objects, Ports/Interfaces, Events) │
├─────────────────────────────────────────────────────────────┤
│ SHARED │
│ (Socle DDD générique, UsecaseBuilder, Pagination) │
└─────────────────────────────────────────────────────────────┘
```
---
## 🎯 Rôle et Responsabilité de Chaque Couche
### 1. `Domain\` - Le Coeur Métier Pur
- **Dépendance** : Ne dépend de **RIEN** (sauf `Shared\`). **Aucun import Laravel / Eloquent / HTTP n'est toléré**.
- **Contenu** :
- **`Models/Entities/`** : Entités riches, `final readonly`, immutables, implémentant `Shared\DDD\Model\Entity`.
- **`Models/ValueObjects/`** : Objets-valeurs typés et auto-validants (ex: `ArcadiaEmployeeId`, `NutriScore`, `BrandCode`).
- **`Ports/`** : Interfaces des opérations externes :
- `Directories/` : Ports de lecture seule (pattern `Directory<TId, TEntity>`).
- `Repositories/` : Ports de persistance / écriture (`Repository<TId, TEntity>`).
- `Services/` : Interfaces pour services externes métier.
- **`Events/`** : Événements métier purs (ex: `BrandCreated`).
- **`Services/`** : Logique métier pure impliquant plusieurs entités.
---
### 2. `Application\` - L'Orchestration des Cas d'Usage
- **Dépendance** : Dépend uniquement de `Domain\` et `Shared\`. Pas de Laravel ni d'HTTP.
- **Contenu** : Cas d'usage métier découpés par contexte (`Application/Arcadia/Usecase/{Context}/{Action}/`).
- Chaque cas d'usage se compose d'un triplet obligatoire :
- `Input.php` : DTO d'entrée typé (`Shared\DDD\Usecase\Input`).
- `Output.php` : DTO de sortie agnostique (`Shared\DDD\Usecase\Output`).
- `{ActionName}.php` : Classe orchestratrice (`Shared\DDD\Usecase\Usecase<Input, Output>`).
---
### 3. `Presentation\` - Les Points d'Entrée Utilisateur / API
- **Dépendance** : Dépend de `Application\`, `Domain\` et `Shared\`. Peut utiliser les composants HTTP de Laravel.
- **Contenu** :
- `Controllers/` : Single-action invokables (`__invoke`). Reçoivent la `FormRequest`, construisent l'`Input`, appellent `UsecaseBuilder` et retournent une `JsonResponse`.
- `Requests/` : `FormRequest` pour la validation syntaxique HTTP.
- `Presenters/` & `Views/` : Transformateurs de l'`Output` Use Case en DTO de vue spécifique (`Presenter<Output, View>`).
- `Responses/` & `Dto/` : Sérialisation finale en JSON (conformité OpenAPI).
---
### 4. `Infra\` - L'Infrastructure et les Adaptateurs Techniques
- **Dépendance** : Dépend de `Domain\`, `Application\` et `Shared\`.
- **Règle d'or** : **Aucune autre couche n'importe `Infra\`**. C'est le conteneur d'injection de dépendances (Service Provider) qui lie les Ports du Domaine aux Adaptateurs d'Infra.
- **Contenu** :
- `Adapters/` : Implémentations Eloquent des Ports (`EloquentEmployeeDirectory`, `EloquentBrandRepository`).
- `Database/Models/` : Modèles Eloquent anémiques (mapping BDD uniquement).
- `Http/Middleware/` : Middlewares techniques (HMAC, Auth).
- `Providers/` : Configuration des bindings Laravel (`DomainServiceProvider`, `SecurityServiceProvider`).
- `Usecase/LaravelExecutor.php` : Exécuteur transactionnel et logging PSR-3.
---
### 5. `Shared\` - Le Socle DDD Transverse
- **Dépendance** : Zéro dépendance externe.
- **Contenu** : Interfaces de base (`Entity`, `EntityId`, `Usecase`, `Input`, `Output`, `Presenter`, `DomainEventDispatcher`, `TransactionalDomainEventDispatcher`, `UsecaseBuilder`, `Paginated`, `Pagination`).
---
## 🚫 Règles de Dépendance (Vérifiées par Pest Arch)
| Couche | Peut importer... | Ne peut JAMAIS importer... |
| :----------------- | :-------------------------------- | :---------------------------------------------------------------- |
| **`Domain`** | `Shared` | `Application`, `Presentation`, `Infra`, `Illuminate\*`, `Laravel` |
| **`Application`** | `Domain`, `Shared` | `Presentation`, `Infra`, `Illuminate\*`, `Laravel` |
| **`Presentation`** | `Application`, `Domain`, `Shared` | `Infra` *(sauf bindings conteneur)* |
| **`Infra`** | `Domain`, `Application`, `Shared` | `Presentation` |
| **`Shared`** | *Rien* | `Domain`, `Application`, `Presentation`, `Infra`, `Illuminate\*` |

View file

@ -0,0 +1,164 @@
# Cycle de Vie d'une Requête (Bullet Tracer) - Arcadia API
Ce document détaille le cheminement complet d'une requête HTTP à travers toutes les couches de l'application, étape par étape.
---
## 🗺️ Vue d'Ensemble du Flux
```mermaid
flowchart TD
Client(["Client HTTP"]) -->|"GET /api/v1/employees/12345678"| Route["routes/apiv1.php"]
Route -->|"Vérifie HMAC & Nonce"| HMAC["VerifyHmacSignature (Middleware)"]
HMAC -->|"Requête valide"| Request["EmployeeShowRequest (Validation HTTP)"]
Request --> Controller["EmployeeShowController (__invoke)"]
subgraph Presentation Couche
Controller -->|"Instancie"| Input["Input(ArcadiaEmployeeId)"]
Controller -->|"Lance"| Builder["UsecaseBuilder::using(executor)"]
end
subgraph Infra Execution
Builder -->|"Englobe transaction & log"| Executor["LaravelExecutor"]
end
subgraph Application & Domain
Executor --> Usecase["FindEmployee::execute(input)"]
Usecase --> DirectoryPort["EmployeeDirectory (Port Domain)"]
DirectoryPort -.->|"Implémenté par"| EloquentDir["EloquentEmployeeDirectory (Adapter Infra)"]
EloquentDir --> DB[("Base de Données")]
EloquentDir -->|"Hydrate"| Entity["Employee (Entité Domain)"]
Usecase -->|"Retourne"| Output["Output(Employee)"]
end
subgraph Presentation Formatting
Executor -->|"Applique"| Presenter["EmployeeShowPresenter::present(output)"]
Presenter --> View["EmployeeShowView"]
Controller --> Response["EmployeeShowResponse($view)"]
Response --> DTO["EmployeeDto::fromView($view)"]
end
Response -->|"HTTP 200 JSON"| Client
```
---
## 🔍 Les 7 Étapes Détaillées
### Étape 1 : Routage (`routes/apiv1.php`)
La requête arrive sur l'endpoint configuré dans `routes/apiv1.php` avec le préfixe `/api/v1/` :
```php
Route::get('/employees/{identifier}', EmployeeShowController::class)
->name('employees.show');
```
---
### Étape 2 : Sécurité & Signature HMAC (`VerifyHmacSignature`)
Le middleware de sécurité intercepte la requête :
1. Valide la présence des 4 en-têtes : `X-Client-Identifier`, `X-Timestamp`, `X-Nonce`, `X-Signature`.
2. Vérifie que le timestamp est dans la fenêtre de tolérance (+/-5 minutes).
3. Vérifie l'unicité du nonce (`CachedNonceValidator`) pour bloquer toute attaque par rejeu.
4. Recalcule la signature HMAC-SHA256 (`METHOD|URI|TIMESTAMP|NONCE|BODY_HASH`) et la compare.
5. Injecte les `ApiClientUserDetails` dans les attributs de la requête.
---
### Étape 3 : Validation HTTP & Contrôleur (`Presentation\Api\V1\`)
Le contrôleur est **invokable** et à action unique (Single Action Controller) :
1. La `FormRequest` (`EmployeeShowRequest`) valide les types et formats HTTP (ex: regex de l'identifiant).
2. Le contrôleur extrait les paramètres et instancie l'`Input` du Use Case :
```php
final readonly class EmployeeShowController
{
public function __construct(
private FindEmployee $usecase,
private LaravelExecutor $executor,
) {}
public function __invoke(EmployeeShowRequest $request, string $identifier): JsonResponse
{
$input = new Input(new ArcadiaEmployeeId($identifier));
/** @var EmployeeShowView $view */
$view = UsecaseBuilder::using($this->executor)
->do($this->usecase)
->from($input)
->with(new EmployeeShowPresenter())
->run();
return (new EmployeeShowResponse($view))->present();
}
}
```
---
### Étape 4 : Exécution Orchestrée (`LaravelExecutor`)
Le `UsecaseBuilder` délègue l'exécution au `LaravelExecutor` :
1. Enregistre un log PSR-3 `debug` avec le contexte complet (Use Case, Input, Utilisateur connecté).
2. Si le Use Case implémente `Transactional`, il est enveloppé dans `$database->transaction(...)`.
3. Exécute la méthode `$usecase->execute($input)`.
4. Après succès, libère automatiquement les événements de domaine (`$eventDispatcher->release()`).
5. En cas d'exception, logue l'erreur, purge les événements non commités (`$eventDispatcher->clear()`) et propage l'exception vers le handler global d'API.
---
### Étape 5 : Logique Métier & Ports (`Application\` & `Domain\`)
Le Use Case orchestre l'accès aux données via les interfaces de ports du Domaine :
```php
final readonly class FindEmployee implements Usecase
{
public function __construct(
private EmployeeDirectory $directory // Port Domain (Interface)
) {}
public function execute(BaseInput $input): Output
{
assert($input instanceof Input);
$employee = $this->directory->find($input->id);
if ($employee === null) {
throw new EmployeeNotFoundException($input->id);
}
return new Output($employee);
}
}
```
- L'adaptateur d'Infrastructure (`EloquentEmployeeDirectory`) exécute la requête Eloquent/SQL et hydrate une entité pure du domaine (`Employee`) via son constructeur typé ou un Builder.
---
### Étape 6 : Présentation & Transformation (`Presenter` -> `View`)
L'`Output` agnostique retourné par le Use Case est confié au `Presenter` :
```php
final readonly class EmployeeShowPresenter implements Presenter
{
public function present(UsecaseOutput $output): EmployeeShowView
{
assert($output instanceof Output);
return EmployeeShowView::fromOutput($output);
}
}
```
- Le `EmployeeShowView` isole la structure d'affichage attendue par l'API sans polluer la couche Application.
---
### Étape 7 : Sérialisation DTO & Réponse JSON (`Response` -> `Dto`)
La classe Response formate la vue en JSON conforme à la spécification OpenAPI :
```php
final readonly class EmployeeShowResponse implements ApiResponse
{
public function __construct(private EmployeeShowView $view) {}
public function present(): JsonResponse
{
return response()->json([
'data' => (new EmployeeDto($this->view->employee))->toArray(),
], 200);
}
}
```
- Les DTOs (`Presentation\Api\V1\Dto\`) garantissent la conversion exacte des Value Objects en types scalaires primitifs (`string`, `int`, `bool`, `ISO-8601`).

View file

@ -0,0 +1,125 @@
# Exécution, Transactions & Événements de Domaine - Arcadia API
Ce document détaille le fonctionnement de l'exécuteur de cas d'usage (`LaravelExecutor`), de la gestion des transactions SQL et du cycle de vie des **Événements de Domaine (Domain Events)**.
---
## ⚡ 1. Le Fluent Builder (`UsecaseBuilder`)
Pour garantir un typage strict et une exécution homogène à travers toute l'application, les Use Cases sont exécutés via un builder fluent :
```php
$result = UsecaseBuilder::using($this->executor)
->do($this->usecase) // Le Use Case à exécuter
->from($input) // Le DTO Input entrant
->with(new CustomPresenter()) // (Optionnel) Le Presenter
->run(); // Exécute et retourne le View (ou l'Output)
```
Le builder garantit à la compilation (PHPStan Niveau 10) que les types d'`Input`, d'`Output` et de `View` concordent parfaitement.
---
## 🛡️ 2. Gestion des Transactions (`Transactional`)
Pour distinguer clairement les opérations de **lecture (Query)** des opérations d'**écriture (Command)** :
- **Cas d'Usage en Lecture (Query)** : N'implémente pas `Transactional`. Exécuté directement sans overhead de transaction de base de données.
- **Cas d'Usage en Écriture (Command)** : Implémente le marqueur `Shared\DDD\Usecase\Transactional`.
```php
namespace Application\Security\Usecases\CreateClient;
use Shared\DDD\Usecase\Transactional;
use Shared\DDD\Usecase\Usecase;
/** @implements Usecase<Input, Output> */
final readonly class CreateClient implements Transactional, Usecase
{
public function execute(BaseInput $input): Output
{
// Cette méthode sera automatiquement enveloppée dans
// DB::transaction(...) par le LaravelExecutor
}
}
```
---
## 📢 3. Événements de Domaine & Sécurité Post-Commit
Dans les architectures événementielles, un problème classique survient lorsqu'un événement est émis **pendant** une transaction SQL qui échoue ensuite (rollback) : les écouteurs ont déjà envoyé des notifications ou loggué des actions erronées.
Arcadia API résout ce problème via le pattern **`TransactionalDomainEventDispatcher`** :
```mermaid
sequenceDiagram
autonumber
participant UseCase as Cas d'Usage
participant Dispatcher as TransactionalDomainEventDispatcher (Buffer)
participant DB as Base de Données
participant Executor as LaravelExecutor
participant LaravelBus as Laravel Event Bus
Executor->>DB: DB::beginTransaction()
Executor->>UseCase: execute(input)
UseCase->>Dispatcher: dispatch(BrandCreated)
Note over Dispatcher: Événement stocké dans le buffer mémoire (non émis)
UseCase-->>Executor: Output
alt Succès de la Transaction
Executor->>DB: DB::commit()
Executor->>Dispatcher: release()
Dispatcher->>LaravelBus: dispatch(BrandCreated)
Note over LaravelBus: Les Listeners sont déclenchés après le commit garanti
else Exception / Rollback
Executor->>DB: DB::rollBack()
Executor->>Dispatcher: clear()
Note over Dispatcher: Le buffer est vidé, aucun événement fantôme n'est émis
end
```
---
## 💻 4. Comment Émettre un Événement dans le Domaine ?
Dans un service ou un Use Case de domaine, injectez simplement `Shared\DDD\Event\DomainEventDispatcher` :
```php
namespace Domain\Services;
use Domain\Events\BrandCreated;
use Shared\DDD\Event\DomainEventDispatcher;
final readonly class BrandResolver
{
public function __construct(
private BrandRepository $brands,
private DomainEventDispatcher $dispatcher,
) {}
public function resolve(string $brandName): Brand
{
// Logique de résolution / création...
$brand = Brand::create(...);
$this->brands->persist($brand);
// Déclencher l'événement métier
$this->dispatcher->dispatch(new BrandCreated($brand));
return $brand;
}
}
```
### Enregistrer un Écouteur (Listener)
Dans `Infra\Providers\EventServiceProvider.php` :
```php
use Domain\Events\BrandCreated;
use Infra\Listeners\LogBrandCreated;
Event::listen(
BrandCreated::class,
LogBrandCreated::class,
);
```

View file

@ -0,0 +1,144 @@
# Guide Pratique : Créer un Nouveau Cas d'Usage - Arcadia API
Ce guide pas-à-pas accompagne un développeur dans la création d'un nouveau cas d'usage complet (ex: `GET /api/v1/articles/{id}`).
---
## 📋 Checklist en 5 Étapes
1. **Domaine (`Domain\`)** : Définir l'Entité, les Value Objects et le Port (Interface).
2. **Infrastructure (`Infra\`)** : Implémenter l'adaptateur Eloquent et enregistrer le binding.
3. **Application (`Application\`)** : Créer le triplet `Input`, `Output` et `ActionName` (Use Case).
4. **Présentation (`Presentation\`)** : Créer `Presenter`, `View`, `Controller`, `Request` et `Response`.
5. **Tests (`tests/`)** : Rédiger les tests Unitaires, d'Intégration et de Feature (OpenAPI).
---
## 🛠️ Étape par Étape
### 1. Domaine : Le Port & l'Entité
Définissez l'interface dans `src/Domain/Ports/Directories/ArticleDirectory.php` :
```php
namespace Domain\Ports\Directories;
use Domain\Models\Entities\Article;
use Domain\Models\ValueObjects\ArticleId;
use Shared\DDD\Port\Directory;
/** @extends Directory<ArticleId, Article> */
interface ArticleDirectory extends Directory
{
public function find(ArticleId $id): ?Article;
}
```
---
### 2. Infrastructure : L'Adaptateur Eloquent
Implémentez l'interface dans `src/Infra/Adapters/Directories/EloquentArticleDirectory.php` :
```php
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Article;
use Domain\Models\ValueObjects\ArticleId;
use Domain\Ports\Directories\ArticleDirectory;
use Infra\Database\Models\Article as EloquentArticle;
final class EloquentArticleDirectory implements ArticleDirectory
{
#[\Override]
public function find(ArticleId $id): ?Article
{
$model = EloquentArticle::query()->find($id->value);
return $model ? $this->toDomain($model) : null;
}
private function toDomain(EloquentArticle $model): Article
{
return Article::hydrate(...);
}
}
```
Enregistrez le binding dans `src/Infra/Providers/DomainServiceProvider.php` :
```php
$this->app->bind(ArticleDirectory::class, EloquentArticleDirectory::class);
```
---
### 3. Application : Le Use Case
Dans `src/Application/Arcadia/Usecase/Articles/Find/` :
**`Input.php`** :
```php
namespace Application\Arcadia\Usecase\Articles\Find;
use Domain\Models\ValueObjects\ArticleId;
use Shared\DDD\Usecase\Input as BaseInput;
final readonly class Input implements BaseInput
{
public function __construct(public ArticleId $id) {}
}
```
**`Output.php`** :
```php
namespace Application\Arcadia\Usecase\Articles\Find;
use Domain\Models\Entities\Article;
use Shared\DDD\Usecase\Output as BaseOutput;
final readonly class Output implements BaseOutput
{
public function __construct(public Article $article) {}
}
```
**`FindArticle.php`** :
```php
namespace Application\Arcadia\Usecase\Articles\Find;
use Application\Arcadia\Exceptions\ArticleNotFoundException;
use Domain\Ports\Directories\ArticleDirectory;
use Shared\DDD\Usecase\Input as BaseInput;
use Shared\DDD\Usecase\Usecase;
/** @implements Usecase<Input, Output> */
final readonly class FindArticle implements Usecase
{
public function __construct(private ArticleDirectory $directory) {}
public function execute(BaseInput $input): Output
{
assert($input instanceof Input);
$article = $this->directory->find($input->id);
if ($article === null) {
throw new ArticleNotFoundException($input->id);
}
return new Output($article);
}
}
```
---
### 4. Présentation : Contrôleur & Presenter
1. **Presenter** (`ArticleShowPresenter`) : convertit `Output` en `ArticleShowView`.
2. **Controller** (`ArticleShowController`) : Single-action invokable exécutant `UsecaseBuilder`.
3. **Response** (`ArticleShowResponse`) : formate la vue en JSON avec `ArticleDto`.
4. **Route** dans `routes/apiv1.php` :
```php
Route::get('/articles/{identifier}', ArticleShowController::class)
->name('articles.show');
```
---
### 5. Tests
1. **Unit Test** (`tests/Unit/Application/Arcadia/Usecase/Articles/FindArticleTest.php`) : avec `InMemoryArticleDirectory` et `Object Mother`.
2. **Integration Test** (`tests/Integration/Infra/Adapters/Directories/EloquentArticleDirectoryTest.php`) : test de la requête réelle sur base SQLite/MySQL de test.
3. **Feature Test** (`tests/Feature/Api/V1/Articles/ArticleShowTest.php`) : test HTTP de bout en bout avec signature HMAC.
4. **OpenAPI Test** (`tests/Feature/OpenApi/V1/Articles/ArticleOpenApiTest.php`) : conformité stricte au schéma `openapi.yaml`.

View file

@ -0,0 +1,62 @@
# Gestion des Exceptions & Codes HTTP - Arcadia API
Ce document récapitule la stratégie de gestion des exceptions et la correspondance avec les codes de statut HTTP dans **Arcadia API**.
---
## 📑 Sommaire
- [1. Convention REST & RFC 7807](#1-convention-rest--rfc-7807)
- [2. Matrice de Mapping Exceptions -> Codes HTTP](#2-matrice-de-mapping-exceptions--codes-http)
- [3. Format de Réponse (Problem Details)](#3-format-de-réponse-problem-details)
- [4. Distinctions 400 Bad Request vs 422 Unprocessable Content](#4-distinctions-400-bad-request-vs-422-unprocessable-content)
---
## 1. Convention REST & RFC 7807
L'API exprime toutes les réponses d'erreur au format standardisé **RFC 7807 / RFC 9457 Problem Details** avec l'en-tête HTTP :
`Content-Type: application/problem+json`
---
## 2. Matrice de Mapping Exceptions -> Codes HTTP
| Type d'Exception | Couche Origine | Code HTTP | Titre RFC 7807 | Usage & Description |
| :----------------------------- | :----------------------- | :-------: | :---------------------- | :------------------------------------------------------------------------------------- |
| `ValidationException` | `Presentation` | **422** | `Unprocessable Entity` | Échec de validation HTTP (`FormRequest`), champs invalides ou manquants. |
| `DomainException` | `Domain` | **422** | `Unprocessable Entity` | Violation d'un invariant ou d'une règle métier au cœur du Domaine (ex: `ValueObject`). |
| `ConflictException` | `Domain` / `Application` | **409** | `Conflict` | Conflit d'état métier (ex: ressource déjà existante ou verrouillée). |
| `NotFoundException` | `Application` | **404** | `Resource Not Found` | Ressource introuvable par le Use Case ou le répertoire. |
| `HttpExceptionInterface` (403) | `Presentation` | **403** | `Forbidden` | Accès refusé par les politiques d'autorisation HTTP. |
| `ThrottleRequestsException` | `Infra` | **429** | `Too Many Requests` | Dépassement du quota de requêtes API (Rate Limiting). |
| `Throwable` / `Exception` | Transverse | **500** | `Internal Server Error` | Erreur système non captée (détails masqués hors mode debug). |
---
## 3. Format de Réponse (Problem Details)
Chaque réponse d'erreur retourne une structure JSON standardisée :
```json
{
"type": "http://localhost/errors/422",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Invalid employee number format",
"instance": "/api/v1/employees",
"invalidParams": [
{
"name": "domain_invariant",
"reason": "Invalid employee number format"
}
]
}
```
---
## 4. Distinctions 400 Bad Request vs 422 Unprocessable Content
- **HTTP 400 (Bad Request)** : Réservé aux erreurs de **syntaxe ou de structure HTTP** (ex: JSON illisible, guillemets mal fermés, en-têtes HMAC malformés). Le serveur ne peut pas parser le message.
- **HTTP 422 (Unprocessable Content)** : Convention **REST & Laravel**. La requête est du JSON syntaxiquement valide, mais son contenu viole les règles de validation HTTP (`FormRequest`) ou les invariants du domaine métier (`DomainException`).

View file

@ -0,0 +1,120 @@
# Conventions de Tests - Arcadia API
Ce document récapitule la stratégie, l'organisation et les conventions strictes applicables à la suite de tests dans **Arcadia API**.
---
## 🚀 1. Commandes
```bash
# Exécuter l'ensemble de la suite de tests (Pest 4)
composer test
# Exécuter les tests avec couverture de code (seuil minimal 85%)
composer test:coverage
# Exécuter uniquement les tests d'architecture
vendor/bin/pest tests/Architecture
```
---
## 🏔️ 2. Pyramide de Tests
```
.
/ \
/ \ 1. FEATURE TESTS (Contrat OpenAPI & E2E)
/ \ ----------------------------------------
/ Feature\ - Respect du contrat OpenAPI (openapi.yaml)
/----------\ - Codes statut HTTP (200, 404, 422, 429) & Auth HMAC
/ \ - Validation de bout en bout des réponses JSON
/ Integration \
/----------------\ 2. INTEGRATION TESTS (Infra & Laravel)
/ \ ----------------------------------------
/ \- Adaptateurs Eloquent (requêtes BDD réelles)
/ Unit \- Service Providers & Bindings Laravel
/ \- Exécuteur transactionnel & Dispatcher d'événements
/--------------------------\
/ \ 3. UNIT TESTS (Domaine & Use Cases)
/ \------------------------------------
/ \- Entités DDD, Value Objects, règles métier
/ \- Use Cases (avec InMemory Directories & Mocks)
/____________________________________\- Pure PHP (sans conteneur Laravel, instantané)
```
---
## 📁 3. Structure des Dossiers
L'arborescence `tests/` reproduit en miroir la structure de `src/` :
- `tests/Architecture/` : Tests de conformité d'architecture DDD, dépendances inter-couches et conventions (Pest Arch).
- `tests/Unit/` : Tests unitaires purs (sans conteneur Laravel, sans BDD, exécution instantanée).
- `tests/Integration/` : Tests des adaptateurs d'infrastructure (Eloquent, événements, base SQLite de test).
- `tests/Feature/` : Tests de bout en bout des endpoints API et validation OpenAPI (`openapi.yaml`).
- **`tests/Support/`** : Utilitaires de test partagés (**Test Data Builders & Object Mothers**).
---
## 💎 4. Conventions de Code
### Principes F.I.R.S.T.
Chaque test rédigé dans ce projet doit obligatoirement respecter les principes **F.I.R.S.T.** :
- **Fast** (Rapide) : Les tests unitaires doivent s'exécuter en quelques millisecondes.
- **Isolated / Independent** (Isolé) : Les tests ne doivent jamais dépendre les uns des autres ni de l'ordre d'exécution.
- **Repeatable** (Répétable) : Le test produit le même résultat sur toutes les machines et en CI.
- **Self-validating** (Auto-validant) : Le résultat est binaire (`OK` / `FAIL`), sans vérification manuelle.
- **Timely** (Opportun) : Les tests sont écrits au bon moment (en TDD ou simultanément avec le code).
### Déclaration `covers()` et Structure GWT
Tous les tests unitaires et d'intégration doivent obligatoirement déclarer la classe ciblée via `covers(NomDeLaClasse::class);` et structurer leur logique avec les blocs `// GIVEN`, `// WHEN`, `// THEN` :
```php
namespace Tests\Unit\Presentation\Api\V1\Presenters\Employees;
use Presentation\Api\V1\Presenters\Employees\EmployeeShowPresenter;
use Presentation\Api\V1\Presenters\Employees\EmployeeShowView;
use Tests\Support\Mothers\EmployeeMother;
covers(EmployeeShowPresenter::class, EmployeeShowView::class);
it('transforms Output into EmployeeShowView', function (): void {
// GIVEN
$employee = EmployeeMother::create('12345678', 'Dupont', 'Jean');
$output = new Output($employee);
$presenter = new EmployeeShowPresenter();
// WHEN
$view = $presenter->present($output);
// THEN
expect($view)->toBeInstanceOf(EmployeeShowView::class)
->and($view->employee)->toBe($employee);
});
```
---
## 🏗️ 5. Test Data Builders & Object Mothers (`tests/Support/`)
Pour éviter la fragilité des tests face aux modifications de constructeurs complexes du Domaine (ex: `IncoComposition` et ses 16 Value Objects), **utilisez toujours les Test Data Builders ou Object Mothers** :
### Object Mothers (`tests/Support/Mothers/`)
Fournissent des instances prêtes à l'emploi et nommées pour les scénarios standards :
```php
// Instancie un objet valide avec des valeurs par défaut réalistes
$composition = IncoCompositionMother::create();
$incoSheet = IncoSheetMother::pouletRoti();
$employee = EmployeeMother::create('12345678');
```
### Test Data Builders (`tests/Support/Builders/`)
Permettent de surcharger précisément les propriétés pertinentes pour le test sans se soucier du reste du graphe :
```php
$composition = IncoCompositionTestBuilder::create()
->withId('67890')
->withNutriScore('A')
->build();
```

View file

@ -1,60 +0,0 @@
# Gestion des Exceptions & Codes HTTP — Arcadia API
Ce document récapitule la stratégie de gestion des exceptions et la correspondance avec les codes de statut HTTP dans **Arcadia API**.
## Sommaire
- [1. Convention REST & RFC 7807](#1-convention-rest--rfc-7807)
- [2. Matrice de Mapping Exceptions ➔ Codes HTTP](#2-matrice-de-mapping-exceptions--codes-http)
- [3. Format de Réponse (Problem Details)](#3-format-de-réponse-problem-details)
- [4. Distinctions 400 Bad Request vs 422 Unprocessable Content](#4-distinctions-400-bad-request-vs-422-unprocessable-content)
---
## 1. Convention REST & RFC 7807
L'API exprime toutes les réponses d'erreur au format **RFC 7807 / RFC 9457 Problem Details** avec l'en-tête HTTP :
`Content-Type: application/problem+json`
---
## 2. Matrice de Mapping Exceptions ➔ Codes HTTP
| Type d'Exception | Couche Origine | Code HTTP | Titre RFC 7807 | Usage & Description |
| ------------------------------ | ------------------------ | --------- | ----------------------- | --------------------------------------------------------------------------------------- |
| `ValidationException` | `Presentation` | **422** | `Unprocessable Entity` | Échec de validation HTTP (`FormRequest`), champs invalides ou manquants. |
| `DomainException` | `Domain` | **422** | `Unprocessable Entity` | Violation d'un invariant ou d'une règle métier au coeur du Domaine (ex: `ValueObject`). |
| `ConflictException` | `Domain` / `Application` | **409** | `Conflict` | Conflit d'état métier (ex: compte déjà supprimé, ressource déjà existante/verrouillée). |
| `NotFoundException` | `Application` | **404** | `Resource Not Found` | Ressource introuvable par le Use Case / répertoire. |
| `HttpExceptionInterface` (403) | `Presentation` | **403** | `Forbidden` | Accès refusé par les politiques d'autorisation HTTP. |
| `ThrottleRequestsException` | `Infra` | **429** | `Too Many Requests` | Dépassement du quota de requêtes API (Rate Limiting). |
| `Throwable` / `Exception` | Transverse | **500** | `Internal Server Error` | Erreur système non captée (détails masqués hors mode debug). |
---
## 3. Format de Réponse (Problem Details)
Chaque réponse d'erreur retourne une structure JSON standardisée :
```json
{
"type": "http://localhost/errors/422",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Invalid employee number format",
"instance": "/api/v1/employees",
"invalidParams": [
{
"name": "domain_invariant",
"reason": "Invalid employee number format"
}
]
}
```
---
## 4. Distinctions 400 Bad Request vs 422 Unprocessable Content
- **HTTP 400 (Bad Request)** : Réservé aux erreurs de **syntaxe ou de structure HTTP** (ex: JSON illisible, guillemets mal fermés, en-têtes HMAC malformés). Le serveur ne peut pas parser le message.
- **HTTP 422 (Unprocessable Content)** : Convention **REST & Laravel**. La requête est du JSON syntaxiquement valide, mais son contenu viole les règles de validation HTTP (`FormRequest`) ou les invariants du domaine métier (`DomainException`).

View file

@ -337,7 +337,7 @@ components:
type: apiKey
in: header
name: X-Timestamp
description: Horodatage au format RFC3339 (tolérance anti-replay ±5 minutes).
description: Horodatage au format RFC3339 (tolérance anti-replay +/-5 minutes).
HmacNonce:
type: apiKey
in: header

View file

@ -1,94 +0,0 @@
# Tests — Arcadia API
Ce document récapitule la stratégie et l'organisation des tests dans **Arcadia API**.
## Sommaire
- [Tests — Arcadia API](#tests--arcadia-api)
- [Sommaire](#sommaire)
- [1. Commandes](#1-commandes)
- [2. Pyramide de Tests](#2-pyramide-de-tests)
- [3. Structure des Dossiers](#3-structure-des-dossiers)
- [4. Conventions de Code](#4-conventions-de-code)
- [Principes F.I.R.S.T.](#principes-first)
- [Pattern GWT (Given - When - Then)](#pattern-gwt-given---when---then)
---
## 1. Commandes
```bash
# Exécuter l'ensemble de la suite de tests
composer test
# Exécuter les tests avec couverture de code (seuil minimal 85%)
composer test:coverage
```
## 2. Pyramide de Tests
```
.
/ \
/ \ 1. FEATURE TESTS (Contrat OpenAPI & E2E)
/ \ ----------------------------------------
/ Feature\ - Respect du contrat OpenAPI (openapi.yaml)
/----------\ - Codes statut HTTP (200, 404, 422, 429) & Auth HMAC
/ \ - Validation de bout en bout des réponses JSON
/ Integration \
/----------------\ 2. INTEGRATION TESTS (Infra & Laravel)
/ \ ----------------------------------------
/ \- Adaptateurs Eloquent (requêtes BDD réelles)
/ Unit \- Service Providers & Bindings Laravel
/ \- Clients & adaptateurs de services externes
/--------------------------\
/ \ 3. UNIT TESTS (Domaine & Use Cases)
/ \------------------------------------
/ \- Entités DDD, Value Objects, règles métier
/ \- Use Cases (avec Mocks de Ports)
/____________________________________\- Pure PHP (sans instanciation de Laravel)
```
## 3. Structure des Dossiers
L'arborescence `tests/` reproduit en miroir la structure de `src/` :
- `tests/Architecture/` : Tests de règles d'architecture DDD, dépendances inter-couches et conventions (Pest Arch).
- `tests/Unit/` : Tests unitaires purs (sans conteneur Laravel, sans BDD, exécution instantanée).
- `tests/Integration/` : Tests des adaptateurs d'infrastructure (BDD, services).
- `tests/Feature/` : Tests de bout en bout et de conformité des endpoints API :
- `tests/Feature/Api/V1/{Context}/` : Tests fonctionnels des endpoints HTTP et de leurs query parameters.
- `tests/Feature/OpenApi/V1/{Context}/` : Tests de conformité au contrat OpenAPI (`openapi.yaml`).
## 4. Conventions de Code
### Principes F.I.R.S.T.
Chaque test rédigé dans ce projet doit obligatoirement respecter les principes **F.I.R.S.T.** :
- **Fast** (Rapide) : Les tests (en particulier unitaires) doivent s'exécuter en quelques millisecondes pour un feedback immédiat.
- **Isolated / Independent** (Isolé) : Les tests ne doivent jamais dépendre les uns des autres ni de l'ordre d'exécution. Chaque test initialise son propre état et le nettoie.
- **Repeatable** (Répétable) : Le test produit exactement le même résultat dans tous les environnements (machine locale, CI GitLab, conteneur Docker).
- **Self-validating** (Auto-validant) : Le test passe ou échoue de façon binaire (`OK` / `FAIL`), sans nécessiter d'analyse manuelle des logs.
- **Timely** (Opportun) : Les tests sont écrits au bon moment (en TDD ou simultanément avec le code de production).
### Declaration covers() et Pattern GWT
Tous les tests unitaires (`tests/Unit`) et d'intégration (`tests/Integration`) doivent obligatoirement déclarer la classe ciblée via `covers(NomDeLaClasse::class);`, utiliser des blocs de commentaires explicites `// GIVEN`, `// WHEN`, `// THEN` et appartenir à un namespace `Tests\...` (vérifié par test d'architecture) :
```php
namespace Tests\Unit\Domain\Models\ValueObjects;
use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId;
covers(ArcadiaEmployeeId::class);
it('validates an employee id', function (): void {
// GIVEN
$id = '12345678';
// WHEN
$entityId = new ArcadiaEmployeeId($id);
// THEN
expect($entityId->value)->toBe($id);
});
```