directorytree/cadence
Cadence adds driver-based scheduling to Laravel Eloquent models. Attach one or many cron or RRULE recurrence schedules to any model, and Cadence tracks and dispatches events automatically when they're due.
Schedule facade is the right one.schedules table (type, expression, timezone, next_run_at, disabled_at) is effectively a persisted product feature, not infrastructure. It decides whether "let customers choose their digest frequency" is a UI ticket or an engineering epic. Adopting it moves a class of recurring requests ("can we get this weekly instead of daily?") out of the backlog entirely.timezone is a column and next_run_at is precomputed, "9am in the customer's local time" is achievable without per-tenant cron entries — which is the alternative, and it's not one you want to own. This makes international/local-time delivery a launch-ready capability rather than a follow-up.resolveNextOccurrence() base class means the buy side is cheap and the exit is clean. Recommendation: buy, but wrap it.schedulable). Roadmap implication: subsequent scheduling-adjacent features (exports, reminders, digests) extend the same table and event rather than each adding a last_sent_at column and its own cron entry. This is a platform-consolidation decision as much as a feature decision.Adopt when all of these hold:
ScheduleTriggered carries SerializesModels as of v1.0.1, so queued listeners re-fetch the model — good).Look elsewhere when:
Schedule facade. Adding a table, a migration, and an every-minute polling command for this is pure overhead.next_run_at; it does not guarantee delivery, track run outcomes, or retry failed work. Workflow/orchestration needs (Temporal, Laravel Workflow, robust queue topology) are a different purchase.schedules:run polls next_run_at <= now() every minute. At hundreds of thousands of schedules, validate indexing and query cost before committing; at millions, build a partitioned/queue-based design instead.schedules table may not satisfy strict isolation requirements.To executives: This is a small, MIT-licensed library that turns "scheduling" from an engineering task into a product capability. Today, every request like "can this report go out weekly, in my timezone, and can I pause it?" is a backlog item. With roughly a table, a trait, and a listener, those become settings. Cost is measured in days, not weeks, and we avoid hand-rolling recurrence math — timezone/DST/cron parsing — which is exactly the kind of code that produces silent, customer-visible failures at 3am. The strategic framing: it establishes a reusable scheduling primitive so future recurring features don't each get their own bespoke implementation.
To engineering: The integration surface is genuinely thin: implement Schedulable and HasSchedules on the models you care about, publish one migration, register schedules:run every minute with withoutOverlapping(), and handle ScheduleTriggered in small queued listeners with shouldQueue() predicates. The cron driver rides on dragonmantank/cron-expression, already a Laravel core transitive dependency, so the simple path adds no net dependency; RRULE support is opt-in via rlanvin/php-rrule or simshaun/recurr. Drivers auto-register when their library is present, and there's a documented base class plus a tap() hook for bespoke recurrence and library configuration.
The honest risk slide: This is a young, low-adoption package — 58 stars, zero dependents, a single maintainer, v1.0.0 in the recent past. We should not treat it as infrastructure we can't live without. Mitigations: (1) confine all Cadence calls behind one internal Scheduler service so the blast radius of a swap is one file; (2) it's MIT and small enough to vendor if upstream stalls; (3) next_run_at in our own table is plain data we keep, so the state survives any replacement. Also budget for the sharp edges: a missing driver library fails at addSchedule() time with a non-obvious error, so we add a startup/config assertion; and the README's listener example has a typo (= instead of ===) — read the source, not the snippet.
Recommendation: Adopt for per-entity, user-configurable recurrence; decline for global jobs. Fund it as a spike behind a feature flag on one model (a per-workspace digest is the ideal proving ground), measure the polling cost at realistic schedule volume, and only then generalize.
How can I help you explore Laravel packages today?