Scheduling
The scheduling system runs recurring tasks (jobs) on cron-like schedules. Each job is a PHP class that declares its own schedule, runs in its own process, and can optionally acquire a distributed lock to prevent concurrent execution.
How it works
A system cron entry invokes schedule:run every minute. The command evaluates every registered
job's cron expression against the current time and, for each due job, spawns an independent
background process running schedule:run-job <job-name>. Jobs therefore run in isolation — no
shared memory, no shared state between jobs running at the same time.
schedule:run itself acquires a process-level lock so only one invocation evaluates the schedule
at a time, even if the previous minute's run was slow.
Implementing a job
Implement JobInterface:
use Amarant\Framework\Contract\Scheduling\JobConfigurationInterface;
use Amarant\Framework\Contract\Scheduling\JobContextInterface;
use Amarant\Framework\Contract\Scheduling\JobInterface;
use Override;
final class SendDailyReportJob implements JobInterface
{
public function __construct(
private readonly ReportService $reportService
) {
}
#[Override] public static function getName(): string
{
return 'vendor_module_send_daily_report';
}
#[Override] public function configure(JobConfigurationInterface $configuration): void
{
$configuration
->cron('0 8 * * *')
->lock(true);
}
#[Override] public function execute(JobContextInterface $context): void
{
$this->reportService->send();
$context->getLogger()->info('Daily report sent.');
}
}
JobInterface methods
| Method | Description |
|---|---|
static getName(): string |
Unique machine name for the job. Used by CLI commands to look up and run it. Recommended format: vendor_module_description. |
configure(JobConfigurationInterface $configuration): void |
Called before every execution to read the schedule and options. Use the fluent $configuration methods to declare the schedule, locking, and email notifications. |
execute(JobContextInterface $context): void |
The job body. Receives a JobContextInterface with a logger and a result code. |
JobConfigurationInterface — schedule helpers
| Method | Cron equivalent | Description |
|---|---|---|
cron(string $schedule) |
any | Set an arbitrary cron expression. |
everyMinute() |
* * * * * |
Run every minute. |
everyFiveMinutes() |
*/5 * * * * |
Run every five minutes. |
everyTenMinutes() |
*/10 * * * * |
Run every ten minutes. |
everyFifteenMinutes() |
*/15 * * * * |
Run every fifteen minutes. |
everyHalfHour() |
*/30 * * * * |
Run every thirty minutes. |
everyHour() |
0 * * * * |
Run at the start of every hour. |
JobConfigurationInterface — options
| Method | Default | Description |
|---|---|---|
lock(bool $lock) |
true |
Whether to acquire a distributed lock before executing. When true and the lock is already held, the job exits immediately without running. |
lockName(?string $name) |
null |
Override the lock name. Defaults to getName(). The actual lock key is {lockName}_{merchantPrivateId}, scoped per merchant in multi-tenant deployments. |
email(array $recipients) |
[] |
Email addresses to notify on every run. |
emailOnFailure(array $recipients) |
[] |
Email addresses to notify only on failure. |
JobContextInterface
| Method | Description |
|---|---|
getLogger(): LoggerInterface |
Logger bound to the scheduling channel (DiEnum::LOGGER_SCHEDULING). |
getResultCode(): int |
Current result code (default 0). |
setResultCode(int $code): self |
Set a non-zero result code to signal a failure without throwing. |
Registering a job
Tag the job class with DiEnum::SCHEDULED_JOB in the CLI area DI configuration:
use Amarant\Framework\Contract\Di\ConfigurableContainerInterface;
use Amarant\Framework\Enum\DiEnum;
use Vendor\ModuleName\Scheduling\Job\SendDailyReportJob;
$container->tag(
[SendDailyReportJob::class],
[DiEnum::SCHEDULED_JOB]
);
Jobs are resolved from the DI container, so constructor dependencies are injected normally.
CLI area only
Jobs and the JobPool are only available in the CLI area. Register jobs in
Configuration/Cli/Di.php, not in Configuration/Di.php.
CLI commands
| Command | Description |
|---|---|
php bin/ama schedule:run |
Evaluate all job schedules and spawn due jobs as background processes. Invoke this from the system cron every minute. |
php bin/ama schedule:run-job <job-name> |
Run a single job directly by name. This is what schedule:run calls internally for each due job. Useful for manual triggering and debugging. |
php bin/ama schedule:list |
List all registered jobs with their name, cron expression, lock setting, and lock name. |
php bin/ama schedule:toggle enable\|disable |
Enable or disable the entire scheduler. Disabling creates a flag file at var/.scheduling.disabled; enabling removes it. schedule:run exits immediately when scheduling is disabled. |
Execution flow
[system cron every minute]
│
▼
schedule:run
├── acquires process lock
├── checks var/.scheduling.disabled → exits if present
└── for each registered job:
├── calls job->configure($configuration)
├── evaluates cron expression against current time
└── if due: exec("php bin/ama schedule:run-job {name} > /dev/null 2>&1 &")
schedule:run-job {name} ← independent process per job
└── JobRunner::run()
├── calls job->configure($configuration)
├── acquires distributed lock (if lock=true) → exits if already locked
├── calls job->execute($context)
├── logs duration and peak memory
└── catches Throwable and logs error (job never crashes the runner process)