El scheduler de Laravel lleva años siendo una de las herramientas más útiles del framework. Con unas pocas líneas puedes programar tareas automáticas sin complicarte la vida. Pero tiene un límite claro: todas las tareas comparten el mismo horario para toda la aplicación.
¿Qué pasa cuando eso no es suficiente?
Imagina que tienes una plataforma con suscripciones. Cada usuario se dio de alta en una fecha diferente, con un ciclo de facturación diferente y en una zona horaria diferente. Con el scheduler clásico acabas filtrando registros dentro del comando y rezando para que no explote nada. No es la solución más elegante.
Aquí es donde entra Cadence.
¿Qué es Cadence?
Cadence es un paquete de que permite asociar schedules directamente a modelos Eloquent. En vez de tener todos los horarios centralizados en un fichero, cada instancia del modelo tiene los suyos propios, guardados en base de datos.
Al instalar el paquete y publicar las migraciones obtienes una tabla schedules con una relación polimórfica a cualquier modelo, la expresión del schedule, la zona horaria y dos timestamps: next_run_at y last_run_at.
Para preparar el modelo:
use DirectoryTree\Cadence\HasSchedules;
use DirectoryTree\Cadence\Schedulable;
class Subscription extends Model implements Schedulable
{
use HasSchedules;
}Y ya puedes asignar un horario a cada registro:
$subscription->addSchedule(new CronSchedule('0 0 1 * *'));
Cron o RRULE
Cadence funciona con drivers intercambiables. Puedes usar expresiones cron para casos simples o RRULE si necesitas recurrencias más complejas. Hay tres drivers disponibles y se instalan por separado según lo que necesites:
# Cron clásico
composer require dragonmantank/cron-expression
# RRULE con php-rrule
composer require rlanvin/php-rrule
# RRULE con Recurr
composer require simshaun/recurrCon RruleSchedule puedes expresar cosas diferentes a cron:
// Todos los días laborables
$subscription->addSchedule(
new RruleSchedule('FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR')
);
// Mensualmente el día 1, durante 12 meses
$subscription->addSchedule(
new RruleSchedule('FREQ=MONTHLY;BYMONTHDAY=1;COUNT=12')
);O con RecurrSchedule:
// Cada semana los lunes, miércoles y viernes
$subscription->addSchedule(
new RecurrSchedule('FREQ=WEEKLY;BYDAY=MO,WE,FR')
);Cada schedule también puede tener su propia zona horaria:
$subscription->addSchedule(
new CronSchedule('0 9 * * *', 'America/New_York')
);
Cómo se ejecuta
Para ejectuar, registraremos el comando schedules:run en el scheduler de Laravel para que corar cada minuto:
Schedule::command('schedules:run')
->withoutOverlapping()
->everyMinute();El comando busca los registros con next_run_at ya vencidos, lanza un evento ScheduleTriggered por cada uno y actualiza el próximo next_run_at. La aplicación reacciona con un listener normal:
class ProcessDueSubscription
{
public function handle(ScheduleTriggered $event): void
{
$event->schedule->schedulable->processRenewal();
}
}
Si ninguno de los drivers encaja con tu caso, puedes crear uno propio extendiendo la clase base de Schedule e implementando resolveNextOccurrence().
Para más información, puedes consultar el repositorio en GitHub.
Fuente: Repositorio oficial de Cadence.