Conventions Techniques
Ce document decrit les conventions de code et les regles techniques a respecter lors du developpement sur PCH-SIG.
UUID - Ramsey UUID
Toutes les entites utilisent Ramsey\Uuid\UuidInterface pour les identifiants.
Symfony\Component\Uid\Uuid
Implementation correcte
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
#[ORM\Entity]
class MonEntite
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
private UuidInterface $id;
public function __construct()
{
$this->id = Uuid::uuid4(); // PAS Uuid::v4()
}
public function getId(): UuidInterface // PAS Uuid
{
return $this->id;
}
}
HashRouter - URLs Frontend
Le frontend utilise HashRouter de React Router. Toutes les URLs generees par le backend doivent inclure /#/.
URLs correctes
// CORRECT
$url = $this->frontendUrl . '/#/auth/magic-link/' . $token;
$url = $this->frontendUrl . '/#/reset-password/' . $token;
// INCORRECT - ne fonctionne pas avec HashRouter
$url = $this->frontendUrl . '/auth/magic-link/' . $token;
RefreshToken
L'entite RefreshToken utilise des noms de methodes specifiques.
| Methode | Description | Alternative incorrecte |
|---|---|---|
setUsername(string) | Email de l'utilisateur | setUser() |
setRefreshToken(string) | Token aleatoire | setToken() |
getRefreshToken() | Recuperer le token | getToken() |
setValid(DateTimeImmutable) | Date d'expiration | setExpiresAt() |
Exemple
$refreshToken = new RefreshToken();
$refreshToken->setUsername($user->getEmail());
$refreshToken->setRefreshToken(bin2hex(random_bytes(32)));
$refreshToken->setValid(new \DateTimeImmutable('+7 days'));
User - Methodes de Securite
| Methode | Description |
|---|---|
resetFailedAttempts() | Reset tentatives echouees |
getFailedLoginAttempts() | Nombre de tentatives echouees |
setFailedLoginAttempts(int) | Definir le compteur |
resetFailedLoginAttempts() n'existe pas. Utiliser resetFailedAttempts().
API URL en Developpement
Les appels fetch() natifs doivent utiliser REACT_APP_API_URL.
// CORRECT
const apiUrl = process.env.REACT_APP_API_URL || '/api';
const response = await fetch(`${apiUrl}/endpoint`);
// INCORRECT - pointe sur port 3000 en dev
const response = await fetch('/api/endpoint');
Configuration
# .env.development.local
REACT_APP_API_URL=http://localhost:8000/api
# .env.production
REACT_APP_API_URL=/api
React Query + Axios
L'API retourne { success: true, data: { ... } }. Avec Axios et React Query :
// L'API retourne: { success: true, data: { dashboard: {...} } }
// Axios extrait: response.data = { success: true, data: { dashboard: {...} } }
// React Query: { data: queryData } = useQuery(...)
const { data: queryData } = useQuery({ queryFn: () => settingsApi.get() });
// CORRECT - 2 niveaux de .data
queryData?.data?.dashboard
// INCORRECT - 3 niveaux = undefined
queryData?.data?.data?.dashboard // BUG!
Regle: queryData.data = l'objet retourne par l'API dans le champ data.
Entites Principales
Menage
Fichier: backend/src/Entity/Menage.php
Champs numeriques (DECIMAL)
Ces champs acceptent ?string, pas float :
latitudelongitudealtitudeprecisionGps
Champs entiers
| Champ | Type | Contrainte |
|---|---|---|
tailleMenage | int | @Assert\Positive (> 0) |
nbEnfants04 | int | Default 0 |
nbEnfants514 | int | Default 0 |
nbAdultes1564 | int | Default 0 |
nbPersonnes65Plus | int | Default 0 |
nbHandicapes | int | Default 0 |
nbActifsOccupes | int | Default 0 |
Relations
| Relation | Type | Entite cible |
|---|---|---|
chefMenage | OneToOne | ChefMenage |
logement | OneToOne | Logement |
region | ManyToOne | Region |
secteur | ManyToOne | Secteur |
commune | ManyToOne | Commune |
localite | ManyToOne | Localite |
campagne | ManyToOne | Campagne |
enqueteur | ManyToOne | Enqueteur |
ChefMenage
Fichier: backend/src/Entity/ChefMenage.php
| Champ correct | Alternative incorrecte |
|---|---|
niveauEducation | niveauEtude |
occupationPrincipale | activitePrincipale |
statutMatrimonial | situationMatrimoniale |
Logement
Fichier: backend/src/Entity/Logement.php
| Champ correct | Alternative incorrecte |
|---|---|
nbPieces | nombrePieces |
materiauMur | natureMurs |
materiauToit | natureToit |
materiauSol | natureSol |
typeToilette | typeToilettes |
combustibleCuisine | sourceEnergieCuisson |
evacuationDechets | evacuationOrdures |
Gestion des Chaines Vides
Lors de la mise a jour des entites, convertir les chaines vides :
| Type de champ | Chaine vide '' devient |
|---|---|
| DECIMAL (GPS) | null |
| INT nullable | null |
| INT non-nullable | 0 (sauf tailleMenage > 0) |
Exemple
// GPS coordinates
$val = $data['latitude'];
$menage->setLatitude($val !== '' && $val !== null ? (string) $val : null);
// Nullable int
$chefMenage->setAge($data['age'] !== '' && $data['age'] !== null ? (int) $data['age'] : null);
// Non-nullable int with default 0
$menage->setNbEnfants04($val !== '' && $val !== null ? (int) $val : 0);
Routes API Custom
Menages
| Methode | Route | Description |
|---|---|---|
| POST | /api/menages/{id}/update | Mise a jour (evite conflit API Platform) |
| GET | /api/menages/{id}/membres | Liste des membres |
Cycles
| Methode | Route | Description |
|---|---|---|
| GET | /api/cycles/{id}/documents | Liste des documents |
| POST | /api/cycles/{id}/documents | Upload document |
| DELETE | /api/cycles/{id}/documents/{docId} | Supprimer document |
Campagnes
| Methode | Route | Description |
|---|---|---|
| GET | /api/campagnes | Liste paginee |
| GET | /api/campagnes/dropdown | Liste pour select |
| GET | /api/campagnes/{id}/stats | Statistiques globales |
| GET | /api/campagnes/{id}/stats/enqueteurs | Stats par enqueteur |
| GET | /api/campagnes/{id}/stats/zones | Stats par zone |
Permissions
Format : module.action
Exemples
registre.menages_view
registre.menages_edit
registre.menages_validate
transferts.cycles_view
transferts.cycles_create
transferts.cycles_validate
rapports.export
campagnes.view
campagnes.create
campagnes.stats
enqueteurs.view
enqueteurs.create
Verification cote backend
// Dans un controleur
#[IsGranted('ROLE_ADMIN')]
public function create(): Response { ... }
// Ou via le voter
$this->denyAccessUnlessGranted('campagnes.create');
Verification cote frontend
import { usePermissions } from '../hooks/usePermissions';
const { can } = usePermissions();
{can('campagnes.create') && (
<Button onClick={handleCreate}>Nouvelle campagne</Button>
)}
Header UI - MainLayout
Le header global (profil, notifications, recherche, statut serveur) est gere par MainLayout.tsx.
Les pages individuelles (Dashboard, etc.) ne doivent PAS dupliquer ces elements.
Service Worker
Fichier: frontend/public/service-worker.js
Pour forcer une mise a jour du cache apres deploiement :
-
Incrementer la version :
const CACHE_NAME = 'pch-sig-cache-v5'; // Incrementer -
Informer les utilisateurs de faire
Ctrl+Shift+R
Hierarchie Geographique
Region
└── Secteur
└── Commune
└── Localite
Le Departement existe mais n'est plus utilise dans le formulaire Menage.
Le Secteur depend de la Region, et la Commune depend du Secteur.