write = $write ?? static function (string $s): void { echo $s; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- a text/event-stream frame whose data line is json_encode(..., JSON_THROW_ON_ERROR) output, not HTML; escaping it as HTML would corrupt the stream }; $this->header = $header ?? static function (string $h): void { header($h); }; $this->flush = $flush ?? static function (): void { // Flush only a buffer that can be flushed (a buffer started without that flag would raise a notice). if (ob_get_level() > 0 && (((int) (ob_get_status()['flags'] ?? 0)) & PHP_OUTPUT_HANDLER_FLUSHABLE) !== 0) { ob_flush(); } flush(); }; } public function started(): bool { return $this->started; } /** @param array $data */ public function send(string $event, array $data): void { $this->start(); ($this->write)('event: ' . $event . "\n" . 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n\n"); ($this->flush)(); } private function start(): void { if ($this->started) { return; } $this->started = true; foreach ([ 'Content-Type: text/event-stream; charset=utf-8', 'Cache-Control: no-store, private', 'Vary: Cookie', 'X-Accel-Buffering: no', // nginx: disable proxy buffering for this response 'X-Content-Type-Options: nosniff', ] as $h) { ($this->header)($h); } // Comment line to push past small intermediary buffers. ($this->write)(':' . str_repeat(' ', 2048) . "\n\n"); } }