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 namespace App\Http\Controllers;
Streaming a filtered CSV export in Laravel
streaming
csv-export
query-builder
Intermediate
9 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
php
<?php namespace App\Services;
Batch image resizing with PHP GD
image-processing
constructor-promotion
match-expression
Intermediate
10 steps
php
<?php namespace App\Cache;
A content-hashed fragment cache in PHP
caching
content-hashing
atomic-writes
Intermediate
10 steps
java
@RestController @RequestMapping("/api/quotes") public class QuoteStreamController {
Streaming market quotes with Spring WebFlux
reactive-streams
backpressure
server-sent-events
Advanced
9 steps
php
<?php namespace App\View\Composers;
How a Laravel view composer feeds the navigation
view-composer
caching
dependency-injection
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.