Notifications
Système de notifications in-app persistées en base, exposées via l'API REST.
Vue d'ensemble
Le système de notifications stocke des messages in-app par utilisateur en base de données.
Chaque notification est liée à un utilisateur, optionnellement à une
organisation, et possède un statut lu/non-lu via le champ readAt.
Les notifications sont créées programmatiquement via NotificationService::notify()
et déclenchées par le système d'événements Symfony. Deux subscribers d'exemple sont fournis.
| Fichier | Rôle |
|---|---|
src/Entity/Notification/Notification.php |
Entité Doctrine (UUID, user, org, type, title, message, readAt, actionUrl) |
src/Enum/NotificationType.php |
Enum des types : info, success, warning, danger |
src/Service/Notification/NotificationService.php |
Service central — crée et marque les notifications comme lues |
src/ApiResource/NotificationResource.php |
DTO API Platform exposant les notifications |
src/State/Api/NotificationProvider.php |
Fournit la liste ou un item pour l'utilisateur connecté |
src/State/Api/NotificationProcessor.php |
Traite le PATCH (marquer comme lu) |
src/EventSubscriber/Notification/ |
Subscribers d'exemple déclenchant des notifications sur les événements métier |
Endpoints
| Méthode | URI | Description |
|---|---|---|
| GET | /api/notifications |
Liste les 50 dernières notifications de l'utilisateur connecté, triées par date décroissante |
| PATCH | /api/notifications/{id} |
Marque la notification comme lue (peuple readAt). Idempotent. |
Exemple — lister les notifications
GET /api/notifications Authorization: Bearer <jwt>
[
{
"id": "018f...",
"type": "success",
"title": "Organisation créée",
"message": "Votre organisation \"Acme\" a bien été créée.",
"readAt": null,
"actionUrl": null,
"organizationId": "018e...",
"createdAt": "2026-04-19T10:00:00+00:00"
}
]
Exemple — marquer comme lue
PATCH /api/notifications/018f...
Authorization: Bearer <jwt>
Content-Type: application/merge-patch+json
{}
Le corps peut être vide — le processor force readAt à l'heure courante
quelle que soit la valeur envoyée. L'opération est idempotente.
Créer une notification
Injectez NotificationService dans n'importe quel service,
controller ou subscriber, puis appelez notify() :
use App\Enum\NotificationType;
use App\Service\Notification\NotificationService;
$this->notificationService->notify(
user: $user,
type: NotificationType::Info,
title: 'Votre titre',
message: 'Le corps de la notification.',
organization: $organization, // nullable
actionUrl: '/dashboard', // nullable
);
Via un Event Subscriber
Le pattern recommandé est de déclencher les notifications depuis des subscribers d'événements.
Deux exemples sont fournis dans src/EventSubscriber/Notification/ :
| Subscriber | Événement écouté | Notification créée |
|---|---|---|
NotifyOnOrganizationCreatedSubscriber |
OrganizationCreatedEvent |
Notification success au propriétaire |
NotifyOnMemberJoinedSubscriber |
MemberJoinedEvent |
Notification info au nouveau membre |
Sécurité
Le provider et le processor vérifient systématiquement que la notification appartient à l'utilisateur connecté. En cas d'accès à une notification d'un autre utilisateur, une 404 est retournée (pas de 403, pour éviter l'énumération de ressources).
La colonne user_id est indexée avec
read_at et created_at
pour des requêtes efficaces même avec un grand volume de notifications.
Interface compte utilisateur
La page /account/notifications affiche toutes les notifications
de l'utilisateur connecté. Les notifications non lues sont mises en évidence (fond teinté, point coloré,
titre en gras) et un compteur de badge est affiché dans le titre.
| Méthode | Route | Description |
|---|---|---|
| GET | app_account_notifications |
Affiche la liste des notifications de l'utilisateur |
| POST | app_account_notifications_read/account/notifications/{id}/read |
Marque une notification individuelle comme lue. Protégé par token CSRF notification_read_{id}. |
| POST | app_account_notifications_read_all/account/notifications/read-all |
Marque toutes les notifications de l'utilisateur comme lues via NotificationService::markAllRead(). Protégé par token CSRF notifications_read_all. |
Le bouton "Tout marquer comme lu" n'est visible que si au moins une notification est non lue.
Chaque ligne non lue dispose d'un bouton individuel (icône check).
Si une notification possède un actionUrl, un lien "Voir" est affiché.
Le marquage se fait via des formulaires HTML POST classiques avec token CSRF — pas d'appel AJAX.
Après soumission, l'utilisateur est redirigé vers app_account_notifications.
Interface admin
La page /admin/notifications (réservée à
ROLE_SUPER_ADMIN) permet de consulter toutes les notifications
de la plateforme et d'en envoyer manuellement à n'importe quel utilisateur.
Envoi manuel
Un bouton "Envoyer une notification" ouvre une modale contenant le formulaire
SendNotificationFormType. Les champs disponibles sont :
| Champ | Description |
|---|---|
userId |
Sélecteur de l'utilisateur destinataire |
type |
Type parmi info, success, warning, danger |
title |
Titre de la notification |
message |
Corps de la notification |
À la soumission, le controller appelle NotificationService::notify()
directement (sans organisation ni actionUrl).
Un flash success confirme l'envoi avec l'e-mail du destinataire.
Tableau de toutes les notifications
Le tableau liste toutes les notifications de la plateforme via
NotificationRepository::findAllForAdmin().
Un bouton "œil" par ligne ouvre une modale de détail (titre, type, statut lu/non-lu,
utilisateur, message, organisation, date) sans rechargement de page.
Étendre le système
Idées d'extension courantes :
- Ajouter un endpoint
POST /api/notifications/read-allen créant une opération custom surNotificationResourceet un processor dédié qui appelleNotificationService::markAllRead(). - Exposer un compteur de non-lus dans
MeResourceen injectantNotificationRepositorydansMeProvider. - Ajouter un champ
metadata(JSON) sur l'entité pour stocker des données contextuelles structurées. - Envoyer un email en parallèle depuis le subscriber si la notification est de type
danger.