DÉMO
dev only

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-all en créant une opération custom sur NotificationResource et un processor dédié qui appelle NotificationService::markAllRead().
  • Exposer un compteur de non-lus dans MeResource en injectant NotificationRepository dans MeProvider.
  • 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.
Loading…
Loading the web debug toolbar…
Attempt #