15: 任务
Phlo 内置了跨应用的任务运行器。每分钟会触发一次 tasks::run,这是一个系统 cron 条目或 Phlo Daemon;tasks 资源会根据 %app->tasks 进行声明式匹配。您的应用中没有 cron 语法,也没有外部调度程序。
15.1: 设置
三个步骤。
1. 在 data/app.json 中激活资源:
{
"resources": [..., "tasks"]
}
2. 描述您在 app.phlo 中的任务:
prop tasks => arr(
cleanup: arr(do: 'account::cleanup', every: '5 minutes'),
poll: arr(do: fn() => external::pull(), every: 'minute'),
backup: arr(do: 'backup::run', daily: '03:00'),
report: arr(do: 'report::weekly', weekly: 'monday 09:00'),
)
3. 每分钟触发 tasks::run。 使用 Phlo Daemon,在其主机映射中列出应用程序,守护进程会为您运行 tasks::run,完全不需要 cron 行:
require('./phlo-daemon.js')(3001, php, { '<host>': { app: '<app>/www/app.php', build: false } })
没有守护进程,系统的 cron 条目也能完成相同的任务:
* * * * * php-zts <app>/www/app.php tasks::run
将其放置在 /etc/cron.d/example-tasks(系统,6 个字段包括用户)或通过 crontab -u <user>(每个用户,5 个字段)。无论哪种方式,调度本身仍然保留在 %app->tasks 中;触发器仅提供分钟的滴答声。
15.2: 日程安排
每个任务选择一个调度键:
| 键 | 格式 | 示例 |
|---|---|---|
every: |
PHP 可读的持续时间字符串 | 'minute','5 minutes','2 hours','1 day' |
daily: |
'HH:MM' |
'03:00' |
weekly: |
'<weekday> HH:MM' |
'monday 09:00' |
every: 'minute'(没有前导数字)在内部变为'1 minute'。通过 strtotime("+$every", 0) 进行解析。
15.3: 可调用的 (`do:`)
do: 字段接受三种形式:
| 类型 | 示例 | 被调用为 |
|---|---|---|
| Closure | fn() => external::pull() |
直接 |
'Class::method' |
'account::cleanup' |
account::cleanup() |
| 资源名称 | 'backup' |
phlo('backup') |
与正常请求不同,任务在 HTTP 生命周期之外运行:没有 %req,没有 %session。编写任务时请确保其自包含。
15.4: 磁盘上的状态 (`data/tasks/`)
tasks::run 会自动创建 data/tasks/ 并用三个文件保护每个任务:
| 文件 | 内容 | 何时 |
|---|---|---|
<name>.last |
原始 Unix 时间戳 | 每次成功运行时,用于到期检查 |
<name>.json |
{schedule, return} 用于 Control Center |
每次成功运行时 |
<name>.lock |
空(mtime 计数) | 在运行期间,TTL 1 小时 |
锁定防止慢任务自我重叠。TTL 故意设置为 1 小时:失败的任务会被停放,直到锁定过期;其他任务会照常运行。
15.5: 错误流程
在 tasks::run 中没有 try/catch。一个 Throwable 会冒泡到 Phlo 的框架异常处理程序,并写入 data/errors.json,就像构建错误一样。Phlo Control Center 在任务选项卡中显示它们。
15.6: Phlo 控制中心
Phlo 控制中心自动检测 data/tasks/:
- 在导航中出现一个 任务标签(仅当目录存在时),紧接在首页之后。
- 每个任务:调度(来自 JSON)、上次运行时间、返回值(类型感知:标量 / 数组 / 字符串)、锁定状态。
- 控制中心对
tasks资源和应用程序完全无关,它仅从data/tasks/读取。调度信息来自<name>.json,而不是通过应用程序路由(这会触发 HTTP 响应并干扰控制中心的渲染)。
15.7: 示例
prop tasks => arr(
heartbeat: arr(do: 'app::heartbeat', every: 'minute'),
)
static heartbeat => file_put_contents(data.'heartbeat.log', date('Y-m-d H:i:s').' tasks::run fired'.lf, FILE_APPEND | LOCK_EX)
Cron 条目:
* * * * * php-zts <app>/www/app.php tasks::run
每分钟调用一次 tasks::run,检查 heartbeat 是否为 every: 'minute' 且 lastRun < 60s 之前,然后运行 app::heartbeat()。文件 data/tasks/heartbeat.last、.json 和 .lock 会被更新;在任务运行期间,中间的滴答会跳过该任务。
15.8: 不使用 cron 的调度
如果 Phlo Daemon 正在运行,它会每分钟为您运行 tasks::run,因此您不需要每个应用的 cron 条目。守护进程的主机映射中的每个应用都会收到每分钟的时间戳:
require('./phlo-daemon.js')(3001, '/usr/bin/php-zts', {
'example.com': { app: '/var/www/example/www/app.php', build: false },
})
守护进程每分钟执行一次 tasks::run;你的 %app->tasks 调度(每个任务上的 every:)决定了实际触发的内容,正如 cron tick 一样。其他一切都是相同的:相同的 %app->tasks 匹配,相同的到期检查,相同的 data/tasks/ 状态。对于没有守护进程的主机,cron 仍然是后备选项;选择一个触发器,而不是两个。