15: Tasks

Phlo has a built-in cross-app cron runner. One system cron entry per app triggers tasks::run every minute; the tasks resource matches declaratively against %app->tasks. No cron syntax in your app, no external scheduler.

15.1: Setup

Three steps.

1. Activate the resource in data/app.json:

{
    "resources": [..., "tasks"]
}

2. Describe your tasks in 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. One cron entry per app with an absolute path:

* * * * * php-zts <app>/www/app.php tasks::run

Place it in /etc/cron.d/example-tasks (system, 6 fields incl. user) or via crontab -u <user> (per-user, 5 fields).

15.2: Schedule

Pick exactly one scheduling key per task:

Key Format Example
every: PHP-readable duration string 'minute', '5 minutes', '2 hours', '1 day'
daily: 'HH:MM' '03:00'
weekly: '<weekday> HH:MM' 'monday 09:00'

every: 'minute' (without a leading number) becomes '1 minute' internally. Parsing via strtotime("+$every", 0).

15.3: Callable (`do:`)

The do: field accepts three forms:

Type Example Is called as
Closure fn() => external::pull() directly
'Class::method' 'account::cleanup' account::cleanup()
Resource name 'backup' phlo('backup')

Unlike during a normal request, a task runs outside an HTTP lifecycle: there is no %req, no %session. Write your task so that it is self-contained.

15.4: State on disk (`data/tasks/`)

tasks::run creates data/tasks/ automatically and guards each task with three files:

File Contents When
<name>.last raw unix timestamp Per successful run, for the due check
<name>.json {schedule, return} for the Control Center Per successful run
<name>.lock empty (mtime counts) During a run, TTL 1 hour

Locks prevent a slow task from lapping itself. The TTL is deliberately 1 hour: a failed task is parked until the lock expires; other tasks keep running as usual.

15.5: Error flow

No try/catch in tasks::run. A Throwable bubbles up to Phlo's framework exception handler and writes to data/errors.json, just like build errors do. The Phlo Control Center shows them in the tasks tab.

15.6: Phlo Control Center

The Phlo Control Center detects data/tasks/ automatically:

15.7: Example

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 entry:

* * * * * php-zts <app>/www/app.php tasks::run

Every minute tasks::run is invoked, sees that heartbeat is every: 'minute' and lastRun < 60s ago, and runs app::heartbeat(). The files data/tasks/heartbeat.last, .json and .lock are updated; intermediate cron ticks skip the task while it is running.

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.