Skip to Content
Course content

215: Server-Sent Events in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this all the time: a developer wants to push a notification or a live update to the browser and immediately starts hunting for a WebSocket library or wondering if they need to spin up a Node.js sidecar to handle "real-time" traffic. They assume that if you want the server to initiate a conversation with the client, you have to move away from standard HTTP and into the world of persistent socket connections.

The "WebSockets are the only way" Fallacy

The mistake is thinking that bidirectional communication is the only way to achieve "push" functionality. WebSockets are powerful, but they are overkill if your data only flows one way—from the server to the client. When you use WebSockets, you're upgrading the protocol entirely, which often means dealing with load balancer headaches and complex state management on the backend.

If you just need to stream a live feed of data—like a stock ticker, a progress bar for a long-running export, or a server resource monitor—Server-Sent Events (SSE) are a far more elegant choice. SSE operates over standard HTTP. There's no protocol upgrade, no special handshake, and it's natively supported by the EventSource API in the browser. It essentially keeps an HTTP connection open and allows the server to "drip" data to the client whenever it wants.

Using the HTTP Stream to Push Data

To make this work in PHP, you have to tell the browser that this isn't a standard HTML page or a JSON response. You're sending a stream. The magic happens with the text/event-stream MIME type.

Here is how I typically set up a simple resource monitor. Note the use of flush(); without it, PHP will buffer the output and the browser won't see any updates until the script actually ends, which defeats the entire purpose.

<?php
// Prevent the script from timing out
set_time_limit(0);

// Essential headers for SSE
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no'); // Disables buffering on Nginx

while (true) {
    // Simulate getting system load or a database update
    $load = sys_getloadavg();
    $data = [
        'cpu' => $load[0],
        'timestamp' => date('H:i:s')
    ];

    // SSE format requires "data: " prefix and two newlines at the end
    echo "data: " . json_encode($data) . "\n\n";

    // Push the data out of the PHP buffer to the browser immediately
    if (ob_get_level() > 0) {
        ob_flush();
    }
    flush();

    // Wait 2 seconds before the next update
    sleep(2);
}
?>

On the frontend, you don't need fetch or axios. You use the EventSource object. I love how clean the JS side is compared to the boilerplate required for WebSockets.

const evtSource = new EventSource("monitor.php");

evtSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log(`CPU Load at ${data.timestamp}: ${data.cpu}`);
    document.getElementById('cpu-display').innerText = data.cpu;
};

evtSource.onerror = function(err) {
    console.error("EventSource failed:", err);
};

One thing to keep in mind: because SSE keeps a connection open, you can quickly run into the maximum execution limit of your PHP process or hit the concurrent connection limit of your web server (especially if you're using Apache with a limited worker pool). If you're building this for a high-traffic site, this is where you'd start looking at an asynchronous engine like Swoole, but for internal tools or low-to-mid traffic apps, standard PHP is plenty.




📋 Practical Task

Build a Real-time System Log Tailer

Your task is to create a PHP-based SSE stream that simulates "tailing" a log file. Instead of reading a real file (which might vary by OS), you will simulate a log file by reading from a simple text file named app.log.

Requirements:

  • Create a file called app.log with a few lines of dummy text.
  • Write a PHP script stream_logs.php that:
    • Sets the correct text/event-stream headers.
    • Enters a while(true) loop.
    • Checks the file size of app.log. If the file has grown since the last check, read the new lines and push them to the browser using the data: [message]\n\n format.
    • Uses flush() to ensure data is sent immediately.
    • Sleeps for 1 second between checks to avoid CPU spiking.
  • Create a simple HTML page with a <div id="log-window"> and a script using EventSource to append every new log line received from the server into that div.

Testing your work: Open your HTML page in one tab, and in another tab (or via terminal), append a new line to app.log. You should see the new line appear in your browser instantly without refreshing the page.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.