Présentation
Projet académique du semestre 5 du BUT, réalisé en trinôme à l’IUT Montpellier-Sète. L’objectif : construire un annuaire de profils en Symfony, c’est-à-dire une application où chacun s’inscrit, publie une fiche et décide de sa visibilité — un terrain d’exercice idéal pour apprendre l’authentification, les rôles et la cohérence des données.
L’application est un monolithe Symfony classique : les pages sont rendues côté serveur avec Twig, la persistance passe par Doctrine et MySQL, et l’ensemble tourne dans un environnement Docker reproductible.
- Deux rôles :
ROLE_USERconsulte l’annuaire et gère son propre profil,ROLE_ADMINvoit aussi les profils masqués et peut supprimer n’importe quel compte - Cycle de vie complet du profil : inscription, connexion, consultation, édition, suppression
- Environ deux mois de développement, de septembre à octobre 2024
Mon rôle
Entité User et cohérence des données
J’ai pris en charge la modélisation du cœur du domaine — une entité unique, mais qui porte l’essentiel des règles de l’application.
- Entité
UserimplémentantUserInterfaceetPasswordAuthenticatedUserInterface, avec informations principales (login, e-mail, code de profil, visibilité, dates) et champs complémentaires optionnels (téléphone, pays, adresse) - Contraintes d’unicité en base sur le login, l’e-mail et le code de profil, doublées d’attributs
#[UniqueEntity]pour renvoyer un message clair plutôt qu’une erreur SQL - Assertions de validation directement sur l’entité : longueurs bornées pour le login et le code de profil, format de l’adresse e-mail
- Callback
#[ORM\PreUpdate]mettant à jour la date de dernière modification — avec une subtilité : si le seul champ modifié est la date de dernière connexion, la date de modification n’est pas touchée, sinon une simple connexion ferait croire à une édition du profil
Codes de profil : génération et unicité en direct
Chaque utilisateur possède un code de profil unique, qu’il peut choisir ou laisser l’application générer. C’est la partie la plus intéressante du projet, parce qu’elle traverse toute la pile — de la base de données au JavaScript.
- Génération côté serveur dans
UserManager: chaîne alphanumérique de longueur aléatoire entre 4 et 20 caractères, régénérée tant que le code tiré est déjà pris - Deux routes JSON dédiées,
checkProfileCodeetgenerateProfileCode, exposées au navigateur via FOSJsRoutingBundle plutôt qu’en codant les URL en dur dans le JavaScript - Vérification de disponibilité en direct pendant la saisie, avec un debounce pour ne pas déclencher une requête à chaque frappe
- Code courant ignoré lors de l’édition, sinon l’utilisateur se verrait reprocher son propre code comme déjà utilisé
API JSON
- Deux points d’entrée en lecture : récupération d’un profil par son login (
/api/profile/login/{login}) ou par son code de profil (/api/profile/profileCode/{profileCode}) - Sérialisation explicite : les champs exposés sont listés un par un dans le contrôleur, ce qui garantit qu’aucune donnée sensible — à commencer par le mot de passe haché — ne peut fuiter par inadvertance
- Réponse
404en JSON avec un message d’erreur structuré quand le profil n’existe pas
Édition de profil et outillage
- Formulaire d’édition distinct du formulaire d’inscription, avec changement de mot de passe optionnel : sans nouveau mot de passe saisi, l’ancien hachage est conservé tel quel
- Validation du numéro de téléphone par expression régulière côté serveur, remontée dans le formulaire sous forme d’erreur de champ
- Commande console
create:userpermettant de provisionner un compte — administrateur compris — depuis le terminal, indispensable pour peupler la base sans passer par l’interface - Pages d’erreur personnalisées (403, 404, 500) pour l’environnement de production
Architecture et sécurité
- Couche de services avec interfaces :
UserManager,FlashMessageHelperetUserFormHelpersont injectés via leur interface et non leur implémentation, ce qui garde les contrôleurs fins et applique concrètement l’inversion des dépendances - Authentification par formulaire configurée dans
security.yaml, avec protection CSRF activée, identification sur le champloginet déconnexion en POST uniquement - Autorisations par expression : les attributs
#[IsGranted]posés sur les routes d’édition et de suppression n’autorisent l’action qu’au propriétaire du compte ou à un administrateur - Hachage des mots de passe délégué au
UserPasswordHasherde Symfony, avec l’algorithmeauto - Souscripteur d’événements d’authentification mettant à jour la date de dernière connexion à la réussite du login et poussant les messages flash de connexion, d’échec et de déconnexion
- Mode maintenance : un écouteur sur
kernel.request, en priorité haute, redirige tout le trafic vers une page dédiée, le drapeau étant piloté par une commande console
Interface Twig
- Templates découpés et factorisés : un
base.html.twigcommun, des vues par domaine (profils, utilisateurs, pages utilitaires) et des fragments réutilisables inclus des deux côtés — le widget de code de profil est ainsi partagé entre l’inscription et l’édition - Champs de formulaire factorisés dans
UserFormHelper, pour éviter de dupliquer la définition des champs entre inscription et édition - Messages flash centralisés dans le layout, alimentés par un service qui convertit les erreurs de validation du formulaire en messages lisibles
- Affichage conditionnel selon l’état de connexion et le rôle : la navigation, les boutons d’édition et de suppression n’apparaissent que si l’utilisateur y a droit
- AssetMapper avec importmap, Stimulus et Turbo, sans build front à installer
Infrastructure
- Docker Compose avec deux services : un serveur Apache/PHP construit sur mesure et une base MySQL persistée sur un volume
- HTTPS en local via un certificat auto-signé, en plus du HTTP
- Migrations Doctrine versionnées pour reconstruire le schéma à l’identique sur n’importe quel poste
- Installation documentée pas à pas dans le README, du démarrage des conteneurs à la création de la base
Travail en équipe
- Mise en place de l’organisation du projet : création et animation du Trello, configuration du dépôt et de sa structure
- Répartition explicite des tâches — entité, API, édition et codes de profil de mon côté, intégration CSS et page d’accueil pour Téo, suppression et visibilité des profils pour Victor
- Versionnage sur le GitLab de l’IUT, dépôt ensuite miroité sur GitHub
- Points réguliers et relectures croisées, particulièrement utiles sur les permissions, où une décision prise par un membre change directement le comportement du code des autres
Technologies
- Back-end : Symfony 6.4, PHP 8.1+, Doctrine ORM 3, FOSJsRoutingBundle
- Front-end : Twig, AssetMapper et importmap, Stimulus, Turbo, JavaScript, CSS
- Base de données : MySQL, migrations Doctrine
- Infrastructure : Docker Compose, Apache
Ce que j’en retire
- Placer les contraintes au bon endroit : entre les assertions sur l’entité et celles sur le formulaire, la règle qui protège réellement les données est celle qui vit sur l’entité — la retenue d’évaluation la plus utile du projet
- Une fonctionnalité simple traverse toute la pile : le code de profil m’a fait toucher à la contrainte d’unicité en base, au service métier, à la route exposée et au JavaScript, jusqu’au debounce pour rendre la saisie agréable
- Les permissions se conçoivent avant de se coder : les exprimer par expression fonctionne, mais les regrouper dans des voters aurait rendu les règles bien plus lisibles et testables — c’est la première chose que je referais autrement


