8: 实时
还有一个空白:当其他人投票时,你的标签不会移动。在最后一章中,服务器通过 phloWS 将结果推送到每个打开的浏览器,然后你将整个内容发布出去。实时部分是可选的:它需要单独的 phlo-websocket Node 服务,没有它投票也可以完成。发布部分则是必不可少的。
8.1: phloWS,可选的 Node 服务
WebSockets 在 Phlo 中通过 phloWS 运行,这是一个小型的 Node.js 代理,位于框架之外。一个 phloWS 进程为您所有的应用程序提供服务,使用一个端口并通过 Host 头路由连接。对于每个传入的消息,它会对您的应用程序进行一次性 PHP 调用,因此您的处理程序可以获得完整的请求生命周期:数据库、会话、所有内容。
git clone https://github.com/q-ainl/phlo-websocket.git <ws>
cd <ws>
npm install
在 <ws>/websocket.js 中,将您的虚拟主机映射到应用程序入口点,然后运行它:
require('./phloWS.js')(3001, 'php', {
'localhost': '<app>/www/app.php',
})
node <ws>/websocket.js
在应用程序一侧,在 www/app.php 中为运行时提供其 WebSocket 端口,并将实时资源添加到 data/app.json:
phlo_app(
id: 'Poll',
host: 'localhost',
build: true,
debug: true,
app: dirname(__DIR__).'/',
langs: dirname(__DIR__).'/langs/',
websocket: 3001,
);
{
"resources": ["...", "websocket", "DOM/websocket", "wsCast", "HTTP"]
}
(... 代表您列表中已经存在的所有内容。) websocket 将传入事件分发到您的钩子,DOM/websocket 是自动连接的浏览器客户端,并通过您在第 6 章中使用的相同 apply() 机制传递传入消息,而 wsCast 和 HTTP 允许 PHP 广播。在生产环境中,您通过反向代理将 wss://.../websocket 传递到 3001 端口。重新构建;lint 保持为 []。
8.2: 钩子
您的应用通过普通函数对套接字事件做出反应。创建 poll.ws.phlo:
function wsConnect($host, $token, $socket){
return true
}
function wsReceive($host, $token, $socket, ...$data){
if (($data['type'] ?? null) === 'ping') return wsCast(wsTarget: $socket, pong: time())
}
wsConnect 在握手后运行;返回 true 以接受。wsReceive 对每个客户端消息运行,消息被 JSON 解码并展开到 $data 中。还有两个钩子存在(wsAuth 用于令牌认证,wsClose 用于清理);轮询不需要这两个,因为结果是公开的。$socket 是一个不透明的 ID,您可以直接针对它,正如 ping 回复所示。
一个命名陷阱:不要将此文件命名为 websocket.phlo。该类名已被引擎的 websocket 资源占用,构建会因重复类错误而失败。poll.ws.phlo 可以很好地将函数编译到应用中。
8.3: 在投票时广播
wsCast() 发布到 phloWS,后者将其推送到连接的浏览器。有效负载与 apply() 使用相同的命令语言。在投票路由中的一行:
route both POST poll vote $id {
if (!$option = type_poll::record(id: (int)$id)) return false
type_poll::change('id=?', (int)$id, votes: $option->votes + 1)
wsCast(wsTarget: 'all', outer: ['#results' => $this->results])
if (%req->async) return apply(
outer: ['#results' => $this->results],
)
location('/poll')
}
wsTarget: 'all' 到达每个连接的客户端;一个 socket id 或一个 id 数组可以缩小范围。浏览器接收 outer: {'#results': ...} 并更新条形图,就像他们自己投票一样。
在两个窗口中打开 http://localhost/poll 并在一个窗口中投票:两个窗口都会移动。如果 phloWS 关闭了呢?wsCast() 会静默失败,投票者仍然会收到他们的 apply() 响应,投票仍然可以继续。实时性在这里是一个层,而不是一个依赖。
8.4: 发货
是时候生成可部署的构建了。向 data/app.json 添加一个发布目标:
{
"resources": ["..."],
"release": "%app/release/"
}
然后构建它:
php www/app.php build::run
php www/app.php build::lint
php www/app.php build::release
build::release 编译一个压缩的生产构建:PHP 到 release/,网页资产到 release/www/。为发布提供一个独立的入口点,release/www/app.php,构建模式关闭,并将路径指向发布输出:
<?php
require '/phlo/phlo.php';
phlo_app(
id: 'Poll',
host: 'poll.example.com',
build: false,
app: dirname(__DIR__, 2).'/',
php: dirname(__DIR__).'/',
www: __DIR__.'/',
data: dirname(__DIR__, 2).'/data/',
langs: dirname(__DIR__, 2).'/langs/',
websocket: 3001,
);
data 和 langs 指向共享的应用文件夹:投票和翻译是状态,而不是构建输出。使用您开始时的相同 Docker 镜像进行部署,现在将 release/www 作为 webroot,并启用自动 HTTPS:
docker run -v $(pwd)/app:/app -p 80:80 -p 443:443 -p 443:443/udp \
-e SERVER_NAME=poll.example.com ghcr.io/q-ainl/phlo8.5: 下一步是什么
您在八个简短的文件中构建了一个全栈应用程序:一个模型,一个控制器,一个样式表,一个钩子文件,以及四个配置文件。路由、视图、CSS、ORM、异步 DOM 更新、翻译和实时功能,全部使用一种语言。
从这里开始:
- 指南 深入涵盖每一层:路由语法、apply 协议、ORM 的关系和模式、worker 模式、任务、AI 资源和追踪。
- 生态系统 页面展示了围绕您的应用程序的生产平台:FrankenPHP worker 模式、Phlo Dashboard、phloWS 和 phloWA,以及舰队管理。
发布一些东西。