php
61 lines · 8 steps
Streaming job progress with SSE in Laravel
An invokable controller pushes live job-progress updates to the browser using Server-Sent Events.
Explained by
highlit
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Repositories\JobProgressRepository;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\StreamedResponse;
8
9class JobProgressStreamController extends Controller
10{
11 public function __construct(private JobProgressRepository $progress)
12 {
13 }
14
15 public function __invoke(Request $request, string $jobId): StreamedResponse
16 {
17 $response = new StreamedResponse(function () use ($jobId) {
18 $lastStatus = null;
19
20 while (! connection_aborted()) {
21 $snapshot = $this->progress->find($jobId);
22
23 if ($snapshot && $snapshot->status !== $lastStatus) {
24 $lastStatus = $snapshot->status;
25
26 $this->send('progress', [
27 'status' => $snapshot->status,
28 'percent' => $snapshot->percent,
29 'message' => $snapshot->message,
30 ]);
31
32 if (in_array($snapshot->status, ['completed', 'failed'], true)) {
33 $this->send('done', ['status' => $snapshot->status]);
34 break;
35 }
36 }
37
38 usleep(750_000);
39 }
40 });
41
42 $response->headers->set('Content-Type', 'text/event-stream');
43 $response->headers->set('Cache-Control', 'no-cache');
44 $response->headers->set('Connection', 'keep-alive');
45 $response->headers->set('X-Accel-Buffering', 'no');
46
47 return $response;
48 }
49
50 private function send(string $event, array $data): void
51 {
52 echo "event: {$event}\n";
53 echo 'data: ' . json_encode($data) . "\n\n";
54
55 if (ob_get_level() > 0) {
56 ob_flush();
57 }
58
59 flush();
60 }
61}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Server-Sent Events let you push updates over one long-lived HTTP response without WebSockets.
- 2Checking connection_aborted() and diffing state keeps a streaming loop cheap and safe from runaway output.
- 3Disabling buffering at the framework, PHP, and proxy layers is what actually makes streamed chunks reach the client.
Related explainers
php
<?php class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/streaming-job-progress-with-sse-in-laravel-explained-php-526f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.