Skip to Content
SymfonyPartie 12 — Temps réel & asynchrone12.5 — Mercure : le temps réel

Chapitre 12.5 — Mercure : le temps réel

Partie 12 : Temps réel & async — Chapitre 5/7 Version de référence : Symfony 7.4 / Mercure.

⏱️ TL;DR

  • Mercure apporte le temps réel via SSE (Server-Sent Events) : un hub relaie des mises à jour du serveur vers les navigateurs abonnés, sur HTTP (pas de WebSocket à gérer).
  • Le hub est un service à part (binaire autonome, ou intégré à FrankenPHP). Symfony publie des updates au hub ; les clients s’abonnent à des topics via EventSource.
  • Autorisation par JWT : un JWT publisher pour publier, un JWT subscriber pour recevoir des topics privés.
  • Intégrations : API Platform (mercure: true publie auto sur changement) et Turbo (#[Broadcast], Partie 8.2).
  • Modèle pull-friendly : le serveur pousse, le client écoute — parfait pour « places restantes en direct », notifications, tableaux collaboratifs.
  • Pour un dev Next.js : Mercure ≈ un service de SSE/pub-sub managé (façon Pusher/Ably), mais open source et auto-hébergeable, piloté par Symfony.

🎯 Objectifs

  • Comprendre l’architecture hub/publisher/subscriber.
  • Publier une mise à jour depuis Symfony.
  • Gérer les topics privés et l’autorisation JWT.
  • Connaître les intégrations API Platform / Turbo.

1. L’architecture Mercure

  • Le hub : un service dédié qui relaie les mises à jour. Il maintient les connexions SSE des abonnés. (Le hub officiel est un binaire ; FrankenPHP l’embarque — pratique en prod, Partie 15.)
  • Le publisher (Symfony) : POST une mise à jour au hub, authentifié par un JWT publisher.
  • Les subscribers (navigateurs / Next.js) : ouvrent une connexion EventSource vers le hub sur un ou plusieurs topics, et reçoivent les updates en push.

SSE est un flux HTTP unidirectionnel (serveur → client) : plus simple que WebSocket (pas de handshake bidirectionnel), reconnexion automatique, et suffisant pour la diffusion (le client envoie ses actions via l’API REST habituelle).

2. Installer et configurer

composer require symfony/mercure

La recipe configure le hub (URL) et les clés. En dev, la Symfony CLI peut lancer un hub local ; en Docker, on ajoute le service Mercure (ou FrankenPHP).

# .env MERCURE_URL=https://localhost:8000/.well-known/mercure # publier (côté serveur) MERCURE_PUBLIC_URL=https://localhost:8000/.well-known/mercure # s'abonner (côté client) MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"

3. Publier une mise à jour depuis Symfony

use Symfony\Component\Mercure\HubInterface; use Symfony\Component\Mercure\Update; final class SlotBroadcaster { public function __construct(private readonly HubInterface $hub) {} public function publishAvailability(Slot $slot): void { $update = new Update( topics: 'https://booklab.test/slots/' . $slot->getId(), // le topic (souvent une IRI) data: json_encode([ 'slotId' => $slot->getId(), 'seatsLeft' => $slot->getSeatsLeft(), ]), ); $this->hub->publish($update); // envoie au hub → poussé à tous les abonnés du topic } }

On appelle publishAvailability() là où l’état change (dans un handler Messenger après réservation, par exemple) : tous les navigateurs abonnés au topic du créneau reçoivent immédiatement le nouveau nombre de places.

4. Topics privés et autorisation

Par défaut, un topic est public (n’importe qui peut s’abonner). Pour des données privées (les notifications d’un utilisateur), on publie une mise à jour ciblée et le subscriber doit présenter un JWT subscriber l’autorisant sur ce topic.

$update = new Update( topics: 'https://booklab.test/users/' . $user->getId() . '/notifications', data: $json, private: true, // seuls les abonnés autorisés (JWT) reçoivent );

Côté client, le JWT subscriber (généralement posé en cookie par Symfony/Next.js) autorise l’abonnement au(x) topic(s) permis. Ainsi, un utilisateur ne reçoit que ses notifications.

⚠️ Piège sécurité : sans private: true et sans contrôle du JWT subscriber, n’importe qui connaissant l’URL du topic reçoit les updates. Pour toute donnée non publique (notifications, données d’un compte), utilisez des updates privées et un JWT subscriber cadrant les topics autorisés. Ne diffusez jamais de données sensibles sur un topic public.

5. Intégrations : API Platform & Turbo

Deux raccourcis puissants :

  • API Platform : #[ApiResource(mercure: true)] publie automatiquement une mise à jour Mercure à chaque création/modification/suppression de la resource. Un client abonné à la collection reçoit les changements en direct — sans code de publication.
    #[ApiResource(mercure: true)] class Slot { /* ... */ }
  • Turbo : #[Broadcast] (Partie 8.2) diffuse des Turbo Streams via Mercure → le back-office Twig se met à jour en direct, sans JS.

Ces deux intégrations couvrent l’essentiel : API + Next.js (via mercure: true) et back-office Twig (via #[Broadcast]), sur le même hub.

6. Où l’utiliser dans BookLab

  • Places restantes en direct : quand une réservation est créée, publier le nouveau seatsLeft du créneau → le front Next.js et le back-office l’affichent instantanément.
  • Notifications utilisateur (privées) : « votre réservation est confirmée » poussée en temps réel.
  • Tableau admin collaboratif : plusieurs admins voient les réservations arriver en direct (#[Broadcast]).

💡 Pour un dev Next.js : Mercure remplace un service comme Pusher/Ably (temps réel managé) par une brique open source auto-hébergeable, pilotée par votre backend. Côté front, vous consommez avec l’API standard EventSource (native, pas de SDK) — c’est l’objet du chapitre suivant. Et rappel (Partie 2.7) : le « temps réel » ne vient pas d’un async/await PHP, mais de ce hub dédié.

✏️ Exercices

Exercice 1. Publiez une mise à jour du nombre de places d’un créneau sur son topic. Écrivez le code.

✅ Solution

$update = new Update( 'https://booklab.test/slots/' . $slot->getId(), json_encode(['slotId' => $slot->getId(), 'seatsLeft' => $slot->getSeatsLeft()]), ); $this->hub->publish($update);

À appeler après un changement d’état (idéalement dans un handler Messenger déclenché par la réservation).

Exercice 2. Comment s’assurer qu’un utilisateur ne reçoit que ses notifications via Mercure ?

✅ Solution

Publier des updates privées (private: true) sur un topic propre à l’utilisateur (ex. .../users/{id}/notifications), et exiger côté abonnement un JWT subscriber cadrant les topics autorisés (posé en cookie par le serveur). Ainsi, seul l’utilisateur autorisé sur ce topic reçoit les updates ; un topic privé sans JWT valide ne délivre rien.

Exercice 3. Quelle intégration publie automatiquement des updates Mercure quand une resource API change ?

✅ Solution

API Platform avec #[ApiResource(mercure: true)] : chaque création/modification/suppression de la resource publie automatiquement une mise à jour Mercure sur le topic correspondant — les clients abonnés reçoivent le changement en direct, sans code de publication manuel.

🧠 Quiz de révision

1. Quel protocole Mercure utilise-t-il, et via quoi côté client ?

SSE (Server-Sent Events) sur HTTP ; côté client, l’API native EventSource.

2. Quels sont les trois rôles de l’architecture ?

Le hub (relaie), le publisher (Symfony, POST des updates) et les subscribers (navigateurs/Next.js, abonnés aux topics).

3. Comment publie-t-on depuis Symfony ?

Via HubInterface::publish(new Update($topic, $data)) (authentifié par un JWT publisher).

4. Comment sécurise-t-on des données privées ?

Avec des updates private: true sur un topic propre à l’utilisateur, et un JWT subscriber autorisant l’abonnement à ce topic.

5. Quel raccourci fait publier automatiquement sur changement d’une resource API ?

#[ApiResource(mercure: true)] (API Platform publie auto à chaque création/màj/suppression).


Prochain chapitre : 12.6 — Consommer Mercure dans Next.js — recevoir les mises à jour en direct côté front.