Server-Sent Events avec PHP 8.4 : le temps réel sans la complexité WebSocket
Mise à jour en temps réel, données live, compteurs dynamiques... Le réflexe habituel est souvent d'aller vers les WebSockets. Pourtant, pour une large catégorie de besoins — afficher un compteur de vues, diffuser une progression, notifier un statut — il existe une solution bien plus simple, native au web et parfaitement adaptée à une stack PHP classique : les Server-Sent Events (SSE).
Cet article s'appuie sur un retour d'expérience publié sur dev.to par l'équipe de TopVideoHub, un agrégateur de vidéos tendance sur neuf régions Asie-Pacifique, qui a remplacé un polling toutes les 30 secondes par un flux SSE sous PHP 8.4, LiteSpeed et Cloudflare.
Pourquoi les WebSockets sont souvent surdimensionnés
Les WebSockets ouvrent un canal bidirectionnel persistant entre le client et le serveur. C'est puissant — et c'est exactement le problème quand votre besoin est unidirectionnel.
Dans le cas de TopVideoHub, le flux d'information est simple : le serveur pousse un chiffre, le navigateur l'affiche. Le client n'envoie jamais rien en retour. Implémenter WebSocket dans ce contexte implique pourtant :
- Un process dédié (Swoole, ReactPHP ou équivalent), incompatible avec LSAPI
- Une gestion d'authentification séparée, les sessions HTTP ne se réutilisant pas nativement côté WS
- Une couche supplémentaire dans Cloudflare, qui traite les connexions WebSocket différemment du trafic HTTP classique (hors cache, hors Page Rules)
Pour un flux unidirectionnel, ce surcoût architectural ne se justifie pas. Les SSE répondent exactement au même besoin avec le protocole HTTP standard.
Ce que sont les Server-Sent Events — et pourquoi PHP s'y prête bien
Les SSE reposent sur une connexion HTTP longue durée : le serveur garde la réponse ouverte et y pousse des événements formatés en texte brut, au fil de l'eau. Le navigateur gère nativement la reconnexion via l'API EventSource.
Format d'un événement SSE :
data: {"views": 142857}\n\n
Du côté PHP, l'implémentation est directe :
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // désactive le buffering LiteSpeed/Nginx
while (true) {
$views = fetchViewCount(); // appel Redis, DB, API externe...
echo "data: " . json_encode(['views' => $views]) . "\n\n";
ob_flush();
flush();
sleep(5);
}
PHP 8.4 n'introduit pas de fonctionnalité SSE spécifique, mais ses améliorations sur la gestion mémoire et les fibers rendent ce type de long-running script plus stable. L'essentiel reste que PHP tient très bien ce pattern sans dépendance externe.
Côté client, la connexion s'établit en quelques lignes :
const source = new EventSource('/stream/views');
source.onmessage = (e) => {
const { views } = JSON.parse(e.data);
document.getElementById('view-count').textContent = views.toLocaleString();
};
Le navigateur se reconnecte automatiquement en cas de coupure. Pas de bibliothèque, pas de configuration particulière.
Les points d'attention en production
L'expérience TopVideoHub met en lumière plusieurs ajustements nécessaires avant de passer en production.
Désactiver le buffering côté serveur
LiteSpeed (et Nginx) bufferisent les réponses par défaut. Sans le header X-Accel-Buffering: no, les événements sont accumulés et envoyés en bloc — ce qui annule l'effet temps réel. Ce header doit être présent dans la réponse PHP et potentiellement dans la configuration du vhost.
Gérer les limites de connexions simultanées
Chaque visiteur qui consulte une page avec un compteur live ouvre une connexion persistante. Sur un serveur classique en mode synchrone (PHP-FPM), cela consomme un worker par connexion active. L'équipe recommande de limiter le TTL du stream (fermeture après N secondes côté serveur, reconnexion automatique côté client) pour éviter l'accumulation de connexions zombies.
$start = time();
while (time() - $start < 120) { // stream de 2 minutes max
// ...
}
// Le client EventSource se reconnecte automatiquement
Cloudflare et le buffering de proxy
Cloudflare peut buffériser les réponses streamées sur certaines configurations. L'activation du mode "streaming" (désactivation du polish et du buffering pour les routes SSE) est nécessaire. Une règle de cache ciblant le content-type text/event-stream suffit généralement.
L'en-tête Last-Event-ID
En cas de reconnexion, le navigateur renvoie le dernier ID d'événement reçu. Implémenter ce mécanisme permet d'éviter de renvoyer un état déjà affiché et de gérer proprement les reprises de connexion.
Résultat : moins de complexité, meilleure expérience
TopVideoHub a remplacé son polling toutes les 30 secondes par un flux SSE. Le bénéfice utilisateur est immédiat : le compteur se met à jour en continu, la page ne semble plus figée. Côté infrastructure, aucun nouveau process, aucun nouveau protocole — la stack PHP/LiteSpeed/Cloudflare existante gère le tout.
Pour MulerTech, ce pattern s'applique à de nombreux contextes Symfony :
- Progression d'un traitement asynchrone (import, génération de fichier)
- Statut d'une commande ou d'un paiement
- Tableau de bord avec métriques live
- Notifications légères sans besoin de réponse client
Avant de partir sur une architecture WebSocket ou un broker de messages, posez-vous la question : le flux est-il unidirectionnel ? Si oui, les SSE font le travail, proprement, avec le stack que vous avez déjà.
💡 À retenir : les Server-Sent Events sont une technologie HTTP native, sous-utilisée, qui couvre efficacement les cas de push unidirectionnel. PHP 8.4 les supporte nativement, sans dépendance. Le vrai défi est la configuration du buffering côté proxy, pas l'implémentation elle-même.