Skip to content

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.

* * * * * php /path/to/project/bin/ama schedule:run

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:

Vendor/ModuleName/Configuration/Cli/Di.php
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)