public/index.php calls $kernel->handle(), sends the response, then calls $kernel->terminate(), which invokes a terminate(Request $request, Response $response): void method on every middleware that defines one. The bookshop's RecordRequestMetrics, attached to /report by class name, logs spl_object_id($this) in handle, then sleeps two seconds in terminate to stand in for an analytics call before logging again. Under PHP-FPM, Symfony 6,093 's send() calls fastcgi_finish_request(), so the client is gone before terminate starts. This run went through Apache and proxy_fcgi to an FPM pool (PHP-FPM via proxy_fcgi):
curl -s -o /dev/null -w '%{http_code} in %{time_total}s\n' http://127.0.0.1:8304/report
sleep 3; tail -2 storage/logs/laravel.log200 in 0.015361s
[2026-09-23 07:20:04] local.INFO: handle {"object":316}
[2026-09-23 07:20:06] local.INFO: terminate {"object":316,"path":"report","status":200}The same request under php artisan serve took 2.05 s, because PHP's built-in server has no fastcgi_finish_request() and holds the connection until the script ends. Both lines show the same object only because AppServiceProvider::register() calls $this->app->singleton(RecordRequestMetrics::class); without it the container builds a fresh instance for terminate (the IDs were 315 and 226), so a property set in handle would be lost. The client leaves early but the FPM worker stays busy for those two seconds, so heavy after-work belongs on a queue (Queues and Scheduling) and defer() suits a light task.