directorytree/cadence
Cadence adds model-based scheduling to Laravel. Attach one or more cron or RRULE schedules to any Eloquent model, track due runs, and dispatch events when schedules trigger. Driver-based design supports cron, php-rrule, Recurr, or custom drivers.
Strengths:
User, Invoice, Report). The polymorphic schedulable_type/schedulable_id design ensures clean, scalable integration.php-rrule/Recurr), catering to both basic and advanced recurrence needs. Custom drivers allow domain-specific extensions (e.g., fiscal-year scheduling).ScheduleTriggered events enable decoupled, observable workflows, aligning with Laravel’s ecosystem (e.g., queue jobs, notifications). The SerializesModels trait simplifies event payloads.next_run_at: Optimizes query performance for due schedules, reducing runtime overhead compared to runtime calculation.Fit for:
Misalignment:
schedules:run. Poor fit for AWS Lambda or cron-less architectures.Stack Compatibility:
directorytree/cadence).dragonmantank/cron-expression (Cron): Stable, widely used.rlanvin/php-rrule or simshaun/recurr (RRULE): Choose one; Recurr is more feature-rich but less maintained.carbon/carbon versions) may require composer overrides.schedules table with polymorphic relations. Migration is straightforward but requires downtime if using zero-downtime deployments.Migration Path:
HasSchedules trait to target models (e.g., Report, User).ScheduleTriggered handlers (e.g., queue jobs, send emails).schedules:run to routes/console.php and configure Laravel’s scheduler (e.g., everyMinute()).Key Questions:
php-rrule or Recurr suffice, or do we need custom logic?next_run_at across regions)?next_run_at.Driver Limitations:
php-rrule’s BYSETPOS vs. Recurr’s implementation). Test edge cases like:
DTSTART in the past.UNTIL or COUNT with timezone offsets.resolveNextOccurrence() logic.Race Conditions:
next_run_at updates: If schedules:run executes concurrently, two instances might process the same schedule. Mitigate with:
FOR UPDATE locks.withoutOverlapping() (already included in the example).next_run_at may become stale. Solution: Use UTC internally, convert to local time only for display.Observability:
ScheduleTriggered events or add custom logging to track runs/errors.Failure Modes:
schedules:run fails or is misconfigured, schedules may not fire. Monitor with:
ScheduleTriggered events in 5 minutes").schedules table growth. Consider archiving old schedules (e.g., last_run_at > 2 years ago).Laravel Ecosystem:
Schedule::fake()) to mock schedules in unit/feature tests.Compatibility:
array_merge hacks.Extensions:
DirectoryTree\Cadence\Drivers\Schedule for domain-specific logic (e.g., "run every fiscal quarter").ScheduleTriggered (e.g., Slack alerts, analytics).Preparation:
Report, Subscription).Installation:
composer require directorytree/cadence dragonmantank/cron-expression rlanvin/php-rrule
php artisan vendor:publish --provider="DirectoryTree\Cadence\CadenceServiceProvider"
php artisan migrate
Model Integration:
HasSchedules trait to target models:
class Report extends Model implements Schedulable {
use HasSchedules;
}
Event System:
ScheduleTriggered:
// app/Listeners/GenerateReport.php
public function handle(ScheduleTriggered $event) {
if ($event->schedulable instanceof Report) {
ReportJob::dispatch($event->schedulable);
}
}
shouldQueue() to filter by model type.Scheduler Setup:
routes/console.php:
Schedule::command('schedules:run')
->withoutOverlapping()
->everyMinute();
Testing:
ScheduleTriggered events.schedules:run command processes due schedules.Deprecation:
Artisan::call() in routes).How can I help you explore Laravel packages today?