1, 'name' => 'Alice']; * yield ['id' => 2, 'name' => 'Bob']; * } * * return new StreamedNdJsonResponse(records()); */ class StreamedNdJsonResponse extends StreamedResponse { /** * @param iterable $items Items to serialize; each becomes one JSON line * @param int $flushInterval Flush to client after this many items (default 10) * @param int $status HTTP status code (default 200) * @param array $headers Additional HTTP headers * @param int $encodingOptions Flags passed to json_encode() */ public function __construct( iterable $items, int $flushInterval = 10, int $status = 200, array $headers = [], private readonly int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, ) { parent::__construct(null, $status, $headers); if (!$this->headers->get('Content-Type')) { $this->headers->set('Content-Type', 'application/x-ndjson'); } if (!$this->headers->has('X-Accel-Buffering')) { $this->headers->set('X-Accel-Buffering', 'no'); } $encodingOptions = $this->encodingOptions; $this->setCallback(static function () use ($items, $flushInterval, $encodingOptions): void { $count = 0; foreach ($items as $item) { echo json_encode($item, \JSON_THROW_ON_ERROR | $encodingOptions) . "\n"; $count++; if ($count >= $flushInterval) { @ob_flush(); flush(); $count = 0; } } // final flush for any remaining buffered items if ($count > 0) { @ob_flush(); flush(); } }); } }