Skip to content

Cron Architecture ​

Main pieces ​

The package has four public building blocks:

  • CronManager loads task files and runs matching tasks
  • CronTask wraps a task name, a cron expression, and a callback
  • Schedule builds CronTask instances with a fluent API
  • CronLock uses lock files plus flock() to prevent overlapping runs

Task discovery ​

CronManager resolves its task directory in this order:

  1. constructor argument
  2. cron_config('path')
  3. base_dir()/cron

loadTasks() scans that directory for *.php files and requires each file.

Each required file must return either:

  • a ready-made CronTaskInterface object, or
  • an array definition with name, expression, and callback

Array definitions are converted into CronTask objects before execution.

Due-check and execution flow ​

runDueTasks() loads tasks, asks each task shouldRun(), and sends due tasks through the same runner used by runTaskByName().

The manager keeps execution counters in one stats array:

  • total
  • executed
  • skipped
  • failed
  • locked

Those counters live on the manager instance. If you reuse the same instance for multiple runs, stats accumulate instead of resetting.

Lock lifecycle ​

Each task run creates a CronLock keyed by the task name.

Lock files live under the configured lock directory and use a sanitized version of the task name. Empty or fully sanitized-away names fall back to default.

Lock behavior is:

  • constructor ensures the lock directory exists
  • constructor also cleans up stale *.lock files older than max_lock_age
  • acquire() creates or opens the task lock file and tries a non-blocking exclusive lock
  • a successful lock writes the current Unix timestamp into the file
  • release() unlocks, closes, and removes the lock file

isLocked() is a read-style probe. It checks whether another process currently holds the lock, but it does not delete files on its own.

Force mode ​

When you run runDueTasks(true) or runTaskByName($name, true), the manager skips lock acquisition entirely.

That also means the finally block does not release a lock for that run. Force mode is meant to bypass the lock system, not refresh or clean it up.

Logging behavior ​

The manager logs start, skip, success, and failure events through LoggerFactory::get(LoggerType::SINGLE).

If the logger cannot be resolved, the package falls back to error_log().

Failure boundaries ​

There are two different failure layers:

  • task-definition and setup failures throw CronException
  • task callback failures are caught inside the manager, counted, and logged

That split matters in production: a broken task file stops the run setup, while a failing due task does not stop other due tasks from being processed.