Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/Response/SseResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,21 @@ public function __construct(iterable|\Closure $producer, array $options = [])
return;
}

$emitter->send($event);
if ($event instanceof Event || is_string($event) || is_array($event)) {
$emitter->send($event);
continue;
}

$sendObject = [$emitter, 'sendObject'];

if (is_callable($sendObject)) {
$sendObject($event);
continue;
}

throw new InvalidArgumentException(
'The current SSE emitter does not support object events; implement ObjectEmitterInterface.'
);
}
};
}
Expand Down
14 changes: 12 additions & 2 deletions src/Sse/AbstractEmitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@
* SSE 发送器公共实现。
*
* 统一帧归一化、注释帧、重连间隔和关闭状态;具体运行时只需实现 write()。
* 对象事件通过 ObjectEmitterInterface::sendObject() 提供,保持 send() 的旧签名。
*/
abstract class AbstractEmitter implements EmitterInterface
abstract class AbstractEmitter implements ObjectEmitterInterface
{
protected bool $closed = false;

public function send(string|array|object $event): void
public function send(Event|string|array $event): void
{
if ($this->isClosed()) {
return;
}

$this->write($this->normalize($event)->format());
}

public function sendObject(object $event): void
{
if ($this->isClosed()) {
return;
Expand Down
4 changes: 2 additions & 2 deletions src/Sse/EmitterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ interface EmitterInterface
/**
* 发送一条事件。
*
* @param Event|string|array<array-key, mixed>|object $event string 作为 data;数组/对象 JSON 编码
* @param Event|string|array<array-key, mixed> $event string 作为 data;数组 JSON 编码
*/
public function send(string|array|object $event): void;
public function send(Event|string|array $event): void;

/**
* 发送注释帧(常用于心跳)。
Expand Down
19 changes: 19 additions & 0 deletions src/Sse/ObjectEmitterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace FastD\Http\Sse;

/**
* 可选能力:支持对象事件的 SSE 发送器。
*
* 保持 EmitterInterface::send() 的参数类型不变,避免破坏已有自定义实现;
* 需要发送对象时使用 sendObject()。
*/
interface ObjectEmitterInterface extends EmitterInterface
{
/**
* 发送对象事件;对象会由 Event::data() 序列化为 JSON。
*/
public function sendObject(object $event): void;
}
2 changes: 1 addition & 1 deletion tests/sse/CgiEmitterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function testSendAcceptsStringArrayAndEvent(): void

$emitter->send('hello');
$emitter->send(['name' => 'fastd']);
$emitter->send((object) ['source' => 'object']);
$emitter->sendObject((object) ['source' => 'object']);
$emitter->send(new Event('done', 'done', '9'));

$this->assertSame([
Expand Down
12 changes: 10 additions & 2 deletions tests/sse/SseResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use FastD\Http\Response\StreamingResponseInterface;
use FastD\Http\Sse\EmitterInterface;
use FastD\Http\Sse\Event;
use FastD\Http\Sse\ObjectEmitterInterface;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -50,6 +51,7 @@ public function testIterableProducerIsWrapped(): void
$response = new SseResponse([
'a',
['b' => 2],
(object) ['d' => 4],
Event::data('c', 'custom', '3'),
]);

Expand All @@ -59,6 +61,7 @@ public function testIterableProducerIsWrapped(): void
$this->assertSame([
"data: a\n\n",
"data: {\"b\":2}\n\n",
"data: {\"d\":4}\n\n",
"id: 3\nevent: custom\ndata: c\n\n",
], $emitter->frames);
}
Expand Down Expand Up @@ -170,11 +173,16 @@ public function testCloneKeepsProducerAndOptions(): void
*/
private function collectingEmitter(): EmitterInterface
{
return new class implements EmitterInterface {
return new class implements ObjectEmitterInterface {
/** @var list<string> */
public array $frames = [];

public function send(string|array|object $event): void
public function send(Event|string|array $event): void
{
$this->frames[] = ($event instanceof Event ? $event : Event::data($event))->format();
}

public function sendObject(object $event): void
{
$this->frames[] = ($event instanceof Event ? $event : Event::data($event))->format();
}
Expand Down
Loading