Background cron tasks
When WebEngine projects need work to happen outside the normal request cycle, use background cron tasks.
A browser request should render a page, return an API response, or accept user input. It should not wait while the application refreshes a remote cache, sends a digest email, imports a feed, or cleans old generated files. Those jobs belong in the project, but they should run on their own schedule.
WebEngine uses the PHP.GT/Cron component for this. The schedule lives in a project-level crontab file, and the task entry points usually live in the project’s cron/ directory.
Project layout
Here’s an example project with two background cron tasks:
.
├── class/
│ └── ServiceLoader.php
├── cron/
├───├── cleanup/
│ │ └── cache.php
│ └── refresh-news.php
├── page/
├── www/
└── crontab
The crontab file decides when jobs are due. The cron/ directory contains the project code that runs when those jobs fire.
The crontab file
Create a file named crontab in the project root. It follows the standard unix crontab format. Example:
# Refresh remote content every 15 minutes.
*/15 * * * * refresh-news?source=php
# Clear generated cache files every night.
30 2 * * * cleanup/cache
# Run a normal shell command at the top of every hour.
0 * * * * bash bin/report-status.sh
The first five fields use familiar crontab syntax:
* * * * * command
| | | | |
| | | | +-- day of week
| | | +---- month
| | +------ day of month
| +-------- hour
+---------- minute
The command after the schedule is interpreted relative to the project. It can be a cron/ script, a go() cron script with query-string input, a static PHP method, or a shell command.
PHP.GT/Cron also provides a few helper extensions that are not part of every Unix cron implementation. For sub-minute jobs, add an s suffix to the first field, such as */10s * * * * refresh-news, to run every 10 seconds. It also supports special @ schedule names such as @hourly, @daily, @weekly, @monthly, @yearly, and @annually. These helpers are convenient for WebEngine projects, but keep in mind that they are interpreted by PHP.GT/Cron rather than by the system crontab command.
Cron go() scripts
The most WebEngine-friendly style is a PHP file in cron/ that defines a go() function. It feels similar to page logic: declare the services you need as parameters and let the container provide them.
crontab:
*/15 * * * * refresh-news?source=php
cron/refresh-news.php:
<?php
use GT\Input\Input;
use App\NewsImporter;
function go(Input $input, NewsImporter $newsImporter):void {
$source = $input->getString("source");
$newsImporter->refresh($source);
}
The ?source=php part is parsed as query-string input and is available through Input. Other parameters are resolved through the same service-loading conventions used by WebEngine page logic.
This makes go() scripts a good fit for application tasks that need repositories, API clients, configuration, logging, or other project services.
Nested tasks
Cron scripts can be organised into subdirectories:
30 2 * * * cleanup/cache
This can point at:
cron/cleanup/cache.php
Nested paths are for go() cron scripts. In this example, cron/cleanup/cache.php should define function go(...).
Ordinary PHP scripts
For simple jobs that do not need service injection, create a standalone script:
crontab:
0 * * * * build-index --quiet
cron/build-index.php:
<?php
[$script, $mode] = $argv + [null, null];
// Build generated files, warm a cache, or call application code here.
The runner resolves build-index to cron/build-index.php and passes any trailing arguments through to the script. Ordinary script aliases are direct children of cron/; use a go() script when you want nested paths such as cleanup/cache.
Static methods and shell commands
The command can also call a static PHP method, loaded using the project’s autoloader:
0 9 * * MON-FRI App\Task\Digest::send("team@example.com")
Or it can run a shell command exactly as written:
30 2 * * * php bin/nightly-maintenance.php
Use a go() script for application-integrated work, a normal cron/ script for standalone PHP scripts, and a shell command when the job naturally belongs to another executable.
Local development
When running the application locally, gt run starts the WebEngine development server and can run the cron watcher alongside it.
gt run
If a project has cron tasks but you only want the web server and other development processes, start without cron:
gt run --no-cron
If you only want the scheduler, run:
gt cron
With no flags, gt cron reads ./crontab, runs jobs that are due now, prints the current time, prints the next scheduled job, and exits.
Example output:
Current time: 16:49:52 (15:49:52 UTC)
Just ran 0 jobs
Next job at: 17:00:00 (16:00:00 UTC) [build-index]
Use watch mode to keep the scheduler running:
gt cron --watch
Useful local checks:
gt cron --validate
gt cron --now
gt cron --now --watch
--validate checks the crontab file without running jobs. --now runs every task once before normal scheduling begins, which is useful when testing new task code.
Production use
gt run is for local development. In production, the web server handles requests and the cron runner should be managed as a separate long-running process.
Use the process manager provided by your hosting environment, such as systemd, Supervisor, a container process, or a platform-specific worker service. The important part is that the process starts from the project root and runs the scheduler in watch mode:
gt cron --watch
The process manager should restart it after deploys, reboots, or crashes. Keep only one active scheduler process per environment unless a task is explicitly designed to run concurrently.
Move back to Run an application, or continue to the request-response lifecycle.