3: 使用 wsCast 进行广播

wsCast 是处理程序向客户端推送的方式。它向 Phlo Realtime 发送消息,Phlo Realtime 将其分发到目标套接字,每个浏览器通过与您的 async routes 已经使用的相同 phlo.js apply 管道来应用它。

3.1: 目标

wsCast(wsTarget: 'all', inner: ['#online' => (string)$count])
wsCast(wsTarget: 'socket:'.$wsSocket, toast: 'Just for you')

目标之后的内容是普通的 apply 指令:innerouterprependappendtoastscroll 等等。客户端将 cast 视为对表单的响应,因此相同的视图更新代码适用于这两种传输方式。

3.2: Leaving the sender out

A token identifies a principal, not a connection. One user with two tabs, or one till with a customer display beside it, is one token holding several sockets. That is the point of token targeting, and it is also why token:not: cannot help you skip a sender: it drops every socket of that token, including the other screens you were trying to reach.

wsExcept names one socket to skip, whichever target picked it up:

function wsReceive($wsHost, $wsToken, $wsSocket, ...$data){
	wsCast(wsTarget: 'token:'.$wsToken, wsExcept: $wsSocket, inner: $data['inner'])
}

Every other screen of this token gets the message; the one that sent it does not. Use it whenever clients relay through the server to their peers, which is the usual shape for chat, typing indicators and second screens.

The exclusion is a separate argument rather than a target value, because the daemon has no idea who "me" is: a cast is its own HTTP call, unconnected to the socket that triggered your handler. The hook is handed $wsSocket, so the hook is the only place that can say.

Spell the payload out when you pass wsExcept. ...$data after a named argument is a fatal PHP error.

3.3: wsCast 可能会失败;请进行保护。

wsCast 会向 Phlo Realtime 发起 HTTP 调用。如果 Phlo Realtime 没有运行,它会抛出异常。将其包装起来,以便您的处理程序的其余部分(以及您的普通 async 路径)能够继续工作:

function cast(...$args){
	try {
		wsCast(...$args)
	}
	catch (\Throwable $e){
	}
}

这个守卫使得当 Phlo Realtime 停止服务时,Phlo Realtime 应用能够优雅地降级为非实时应用。

3.4: 浏览器客户端

加载 DOM/websocket 资源会发送一个小型客户端。它 在页面主体具有 wss 类时打开一个套接字,自动重新连接,并应用传入的转换。要为页面启用实时功能,请将应用程序置于该模式(演示中设置 prop options = 'wss');如果不启用,则该页面就是一个普通的 Phlo 页面。

3.5: Mirroring state between screens

With DOM/store loaded, a client can publish part of its own state instead of hand-rolling messages. Declare the path once:

app.sync('basket')            // over the socket, coalesced per 200 ms
app.sync('draft', {post: 'api/draft', delay: 1000})

Every change under that path now leaves as {sync: {basket: <value>}}. Changes are coalesced, so a burst of edits sends one message with the final value, not one per keystroke.

The server decides who may see it. A relay that hands the value to the other screens of the same principal is four lines:

function wsReceive($wsHost, $wsToken, $wsSocket, ...$data){
	if (!isset($data['sync'])) return
	wsCast(wsTarget: 'token:'.$wsToken, wsExcept: $wsSocket, sync: $data['sync'])
}

Nothing is relayed that you do not relay yourself: pick the paths you accept rather than passing the payload through unread, the same as with any other client input.

On the receiving side sync is an ordinary apply command, so it needs no code. It differs from store in one way that matters: a value applied through sync is not published again. Without that, two screens that both publish the same path would bounce it back and forth forever.

Two more things worth knowing:

最近更新于 2026年8月23日

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。