Daravelv1.9.0
ENID GitHub

Cache, events, queues and scheduling

final total = await Cache.remember(
  'posts.count',
  const Duration(minutes: 5),
  () => Post.query().count(),
);

await Event.dispatch(const PostPublished(1));
await Queue.dispatch(const PublishPost(1)).onQueue('posts');

Cache#

Stores: memory (per isolate, LRU), process (shared by every isolate of the server through the coordinator), file and database (Laravel's cache table). Cache.remember, flexible (stale-while-revalidate), increment, pull, lock (atomic locks, fail closed) mirror Laravel.

Events#

Event.listen<OrderShipped>((event) async {...}) or listener classes in bootstrap/listeners.dart. Event.dispatchAfterCommit waits for the current transaction. Queued listeners run through the queue.

Queues#

final class PublishPost extends Job {
  const PublishPost(this.postId);

  final int postId;

  @override
  String get jobName => 'publish-post';

  @override
  Map<String, Object?> toJson() => {'post_id': postId};

  @override
  Future<void> handle(JobContext ctx) async {
    final post = await Post.query().findOrFail(postId);
    await post.update({'published': true});
  }
}

Jobs are registered by name in bootstrap/jobs.dart (JobDefinition('publish-post', (json) => PublishPost(...))), because Daravel does not use reflection. Drivers: sync, database (Laravel's jobs / failed_jobs tables) and memory. Retries, backoff, timeouts, unique jobs, job middleware, queue:work, queue:failed, queue:retry and queue:restart are supported. Dispatch inside a transaction is deferred until commit by default.

For monitoring and operations (the hooks a Horizon-style dashboard builds on):

  • List<String> get tags on a job (for example ['user:42']) is stored in the payload's tags key.
  • Queue.connection().oldestWait('emails') says how long the oldest ready job has been waiting.
  • dartisan queue:pause database:emails stops every worker taking jobs from that queue (running jobs finish) until dartisan queue:continue database:emails; QueueWorker.pause() / resume() do the same for one worker.

Context#

Laravel 11's Context holds data about the current request, job or command. Visible entries are added to every log record, and everything, hidden entries included, travels with the jobs dispatched from there:

// A middleware
Context.add('tenant_id', tenant.id);
Context.addHidden('api_token', token);    // carried, never logged

// Later in the request, or inside the queued job minutes later
final tenantId = Context.get<int>('tenant_id');

addMany, addIf, push(key, [values]), has, forget, all() and the …Hidden counterparts complete it. Each request starts empty. Values must be JSON-encodable, like job data, and hidden values are stored in the job payload (the jobs table), so keep real secrets out of it.

Scheduling#

routes/console.dart declares the schedule:

void consoleRoutes(Schedule schedule) {
  schedule.command('reports:send').dailyAt('13:00').onOneServer();
  schedule.job(const PruneTokens()).hourly().withoutOverlapping();
}

Run it with schedule:work (long-running) or schedule:run from cron; schedule:list shows the next runs and each task's zone.

Cron expressions are read in SCHEDULE_TIMEZONE (Laravel's app.schedule_timezone), or in UTC when it is not set, unless a task names its own zone. Laravel falls back to APP_TIMEZONE; Daravel 1.x does not, so upgrading never moves existing schedules, and dartisan doctor warns when APP_TIMEZONE is not UTC while SCHEDULE_TIMEZONE is empty. schedule:list --timezone=Asia/Jakarta shows the next runs in that zone.

schedule.command('reports:send').dailyAt('08:00').timezone('Asia/Jakarta');
schedule.job(const Digest()).dailyAt('09:00').timezone('America/New_York');
schedule.call(sync).hourly().timezone(const Duration(hours: 7)); // fixed offset

IANA zones follow daylight saving time as Laravel does: a time skipped when the clocks go forward runs that much later (02:30 runs at 03:30), and a time repeated when they go back runs twice. (This assumes clock changes of at most 3 hours, more than 3 hours apart, as in every current DST rule; a one-off change such as Samoa's 24-hour jump in 2011 could misfire once.) The zone database is compiled into the binary (about 0.5 MB, with the alias names PHP accepts: UTC, GMT, US/Eastern, ...); an unknown zone name throws at boot, and dartisan lint reports it (invalid_timezone).