Home Freelance Projects Skills Blog Experience Education
← All projects
Symfony Directory

Symfony Directory

Profile Management Application - IUT Montpellier-Sète

Overview

An academic project from the fifth semester of the BUT degree, built by a team of three at IUT Montpellier-Sète. The goal: build a profile directory in Symfony — an application where users sign up, publish a profile card and decide whether it is publicly visible — an ideal playground for learning authentication, roles and data consistency.

The application is a classic Symfony monolith: pages are rendered server-side with Twig, persistence goes through Doctrine and MySQL, and everything runs inside a reproducible Docker environment.

  • Two roles: ROLE_USER browses the directory and manages their own profile, ROLE_ADMIN also sees hidden profiles and can delete any account
  • Full profile lifecycle: registration, login, viewing, editing, deletion
  • Around two months of development, from September to October 2024

My role

The User entity and data consistency

I took ownership of the domain model — a single entity, but one that carries most of the application’s rules.

  • User entity implementing UserInterface and PasswordAuthenticatedUserInterface, with core information (login, email, profile code, visibility, dates) and optional complementary fields (phone number, country, address)
  • Uniqueness constraints in the database on login, email and profile code, backed by #[UniqueEntity] attributes so the user gets a clear message instead of an SQL error
  • Validation assertions placed directly on the entity: bounded lengths for login and profile code, email format checking
  • #[ORM\PreUpdate] callback updating the last-modified date — with one subtlety: if the only changed field is the last-connection date, the modification date is left alone, otherwise merely logging in would look like a profile edit

Profile codes: generation and live uniqueness checking

Every user owns a unique profile code, either chosen by them or generated by the application. It is the most interesting part of the project, because it cuts through the entire stack — from the database to the JavaScript.

  • Server-side generation in UserManager: an alphanumeric string of random length between 4 and 20 characters, regenerated as long as the drawn code is already taken
  • Two dedicated JSON routes, checkProfileCode and generateProfileCode, exposed to the browser through FOSJsRoutingBundle rather than hard-coding URLs in the JavaScript
  • Live availability checking while typing, with debouncing so a request isn’t fired on every keystroke
  • The current code is ignored when editing, otherwise users would be told their own code is already taken

JSON API

  • Two read endpoints: fetch a profile by its login (/api/profile/login/{login}) or by its profile code (/api/profile/profileCode/{profileCode})
  • Explicit serialisation: exposed fields are listed one by one in the controller, guaranteeing that no sensitive data — starting with the hashed password — can leak by accident
  • Structured JSON 404 response when the profile does not exist

Profile editing and tooling

  • A separate edit form from the registration form, with an optional password change: if no new password is entered, the existing hash is kept as is
  • Phone number validation by regular expression on the server side, surfaced back into the form as a field-level error
  • create:user console command to provision an account — administrators included — from the terminal, essential for seeding the database without going through the UI
  • Custom error pages (403, 404, 500) for the production environment

Architecture and security

  • Service layer with interfaces: UserManager, FlashMessageHelper and UserFormHelper are injected through their interface rather than their implementation, keeping controllers thin and putting dependency inversion into practice
  • Form-based authentication configured in security.yaml, with CSRF protection enabled, identification on the login field and POST-only logout
  • Expression-based authorisation: the #[IsGranted] attributes on the edit and delete routes only allow the action for the account owner or an administrator
  • Password hashing delegated to Symfony’s UserPasswordHasher with the auto algorithm
  • Authentication event subscriber updating the last-connection date on successful login and pushing flash messages for login, failure and logout
  • Maintenance mode: a high-priority kernel.request listener redirects all traffic to a dedicated page, with the flag driven by a console command

Twig interface

  • Split, factorised templates: a shared base.html.twig, domain-specific views (profiles, users, utility pages) and reusable fragments included on both sides — the profile-code widget is shared between registration and editing
  • Form fields factorised in UserFormHelper, avoiding duplicated field definitions between registration and editing
  • Flash messages centralised in the layout, fed by a service that turns form validation errors into readable messages
  • Conditional rendering based on login state and role: navigation, edit and delete buttons only appear when the user is allowed to use them
  • AssetMapper with importmap, Stimulus and Turbo, with no front-end build step to install

Infrastructure

  • Docker Compose with two services: a custom-built Apache/PHP web server and a MySQL database persisted on a volume
  • Local HTTPS through a self-signed certificate, alongside plain HTTP
  • Versioned Doctrine migrations to rebuild an identical schema on any machine
  • Step-by-step installation documented in the README, from starting the containers to creating the database

Teamwork

  • Setting up the project organisation: creating and running the Trello board, configuring the repository and its structure
  • Explicit task split — entity, API, editing and profile codes on my side, CSS integration and home page for Téo, profile deletion and visibility for Victor
  • Version control on the IUT GitLab, with the repository later mirrored to GitHub
  • Regular check-ins and cross reviews, particularly valuable around permissions, where one member’s decision directly changes how everyone else’s code behaves

Technologies

  • Back-end: Symfony 6.4, PHP 8.1+, Doctrine ORM 3, FOSJsRoutingBundle
  • Front-end: Twig, AssetMapper and importmap, Stimulus, Turbo, JavaScript, CSS
  • Database: MySQL, Doctrine migrations
  • Infrastructure: Docker Compose, Apache

What I took away

  • Constraints belong in the right place: between assertions on the entity and assertions on the form, the rule that actually protects the data is the one living on the entity — the single most useful piece of feedback from the project’s review
  • A simple feature cuts through the whole stack: the profile code took me from a database uniqueness constraint to a domain service, an exposed route and the JavaScript, right down to debouncing to make typing feel pleasant
  • Permissions are designed before they are coded: expressing them inline works, but grouping them into voters would have made the rules far more readable and testable — it is the first thing I would do differently

Preview

Team

Clement Garcia
Clement GarciaMe
Developer
Teo Moerel
Teo Moerel
Developer
Victor Vidaux
Victor Vidaux
Developer