Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cadence Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the package and dependencies:

    composer require directorytree/cadence dragonmantank/cron-expression
    

    (Choose either rlanvin/php-rrule or simshaun/recurr for RRULE support.)

  2. Publish migrations and run them:

    php artisan vendor:publish --provider="DirectoryTree\Cadence\CadenceServiceProvider"
    php artisan migrate
    
  3. Implement the Schedulable interface on a model:

    use DirectoryTree\Cadence\HasSchedules;
    use DirectoryTree\Cadence\Schedulable;
    
    class Report extends Model implements Schedulable
    {
        use HasSchedules;
    }
    
  4. Add a schedule to a model instance:

    $report = Report::find(1);
    $report->addSchedule(new \DirectoryTree\Cadence\Drivers\CronSchedule('0 12 * * *'));
    $report->save();
    
  5. Set up the schedules:run command in routes/console.php:

    Schedule::command('schedules:run')->everyMinute();
    
  6. Listen for triggered schedules:

    // In EventServiceProvider or via event discovery
    ScheduleTriggered::listen(function ($event) {
        $event->schedulable->performAction();
    });
    

First Use Case

Example: Schedule a daily report generation at noon for a specific Report model.

$report = Report::create(['name' => 'Daily Sales']);
$report->addSchedule(new \DirectoryTree\Cadence\Drivers\CronSchedule('0 12 * * *'));
$report->save();

// Later, in a listener:
ScheduleTriggered::listen(function ($event) {
    if ($event->schedulable instanceof Report) {
        $event->schedulable->generate();
    }
});

Implementation Patterns

Core Workflow

  1. Define Schedules: Attach schedules to models during creation or via admin interfaces. Use cron for simplicity or RRULE for complex patterns.

    // Simple cron example
    $user->addSchedule(new \DirectoryTree\Cadence\Drivers\CronSchedule('0 9 * * 1-5')); // Weekdays at 9 AM
    
    // Complex RRULE example
    $campaign->addSchedule(new \DirectoryTree\Cadence\Drivers\RruleSchedule(
        'FREQ=MONTHLY;BYMONTHDAY=15;BYDAY=MO'
    ));
    
  2. Timezone Handling: Always specify timezones for schedules to avoid ambiguity, especially in global apps.

    $schedule = new \DirectoryTree\Cadence\Drivers\CronSchedule('0 9 * * *', 'America/New_York');
    $user->addSchedule($schedule);
    
  3. Event-Driven Execution: Use ScheduleTriggered events to decouple scheduling from business logic. Queue listeners for async processing.

    ScheduleTriggered::listen(function ($event) {
        dispatch(new GenerateReportJob($event->schedulable));
    });
    
  4. Dynamic Scheduling: Update schedules programmatically (e.g., pause/resume during maintenance).

    $schedule->disable(); // Temporarily pause
    $schedule->enable();  // Resume
    
  5. Batch Processing: Run the schedules:run command in a queue worker or serverless environment (e.g., AWS Lambda) for scalability.

    php artisan schedules:run
    

Integration Tips

  • Admin Panels: Expose schedule management via Laravel Nova or a custom admin interface.
  • Testing: Mock ScheduleTriggered events in unit tests:
    $this->fake(ScheduleTriggered::class);
    $this->assertFired(function ($event) {
        return $event->schedulable->id === 1;
    });
    
  • Monitoring: Log triggered schedules to track execution:
    ScheduleTriggered::listen(function ($event) {
        \Log::info('Schedule triggered', ['model' => $event->schedulable->class]);
    });
    
  • Custom Drivers: Extend for domain-specific logic (e.g., "run 7 days after order creation"):
    class OrderFollowUpSchedule extends \DirectoryTree\Cadence\Drivers\Schedule
    {
        protected function resolveNextOccurrence(CarbonInterface $after): ?CarbonInterface
        {
            return $after->copy()->addDays(7);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Timezone Mismatches:

    • Issue: Schedules may fire at unexpected times if timezones aren’t set or are inconsistent.
    • Fix: Always specify timezones when creating schedules. Use Carbon::now('America/New_York') for consistency.
    • Debug: Check next_run_at in the database to verify timezone handling.
  2. Overlapping Schedules:

    • Issue: Multiple schedules on a model may conflict or fire too frequently.
    • Fix: Use withoutOverlapping() in routes/console.php to prevent race conditions:
      Schedule::command('schedules:run')->withoutOverlapping()->everyMinute();
      
  3. RRULE Complexity:

    • Issue: Invalid RRULE expressions may cause silent failures or incorrect scheduling.
    • Fix: Validate expressions before saving:
      try {
          new \DirectoryTree\Cadence\Drivers\RruleSchedule('INVALID_RRULE');
      } catch (\Exception $e) {
          // Handle error
      }
      
  4. Database Locking:

    • Issue: Concurrent schedules:run executions may cause deadlocks when updating next_run_at.
    • Fix: Use database transactions or optimistic locking:
      $schedule->newQuery()->lockForUpdate()->update(['next_run_at' => $nextRun]);
      
  5. Event Listener Order:

    • Issue: Listeners may not fire if registered after schedules are triggered.
    • Fix: Ensure listeners are bound in EventServiceProvider or use event discovery.

Debugging Tips

  • Check next_run_at: Query the schedules table to verify next_run_at values match expectations:
    SELECT * FROM schedules WHERE schedulable_id = 1;
    
  • Log Schedule Events: Add logging to ScheduleTriggered listeners to trace execution:
    ScheduleTriggered::listen(function ($event) {
        \Log::debug('Schedule triggered', [
            'model' => $event->schedulable->class,
            'schedule_type' => $event->schedule->type,
            'next_run' => $event->schedule->next_run_at,
        ]);
    });
    
  • Test with now(): Override Carbon’s now() in tests to simulate time progression:
    use Carbon\Carbon;
    
    beforeEach(function () {
        Carbon::setTestNow(Carbon::parse('2023-01-01 12:00:00'));
    });
    

Configuration Quirks

  1. Driver Registration:

    • Ensure required dependencies are installed (dragonmantank/cron-expression, rlanvin/php-rrule, or simshaun/recurr).
    • Custom drivers must be registered in AppServiceProvider:
      \DirectoryTree\Cadence\Schedule::driver('custom', \App\Drivers\CustomSchedule::class);
      
  2. Migration Updates:

    • Always run the latest migration after updates (e.g., disabled_at column in v1.1.0):
      php artisan migrate
      
  3. Timezone Defaults:

    • Schedules default to the app’s timezone. Explicitly set timezones to avoid surprises:
      $schedule->setTimezone('UTC');
      

Extension Points

  1. Custom Drivers: Extend \DirectoryTree\Cadence\Drivers\Schedule to create domain-specific logic:

    class BusinessDaySchedule extends \DirectoryTree\Cadence\Drivers\Schedule
    {
        protected function resolveNextOccurrence(CarbonInterface $after): ?CarbonInterface
        {
            $next = $after->copy();
            while (!$next->isBusinessDay()) { // Custom logic
                $next->addDay();
            }
            return $next;
        }
    }
    
  2. Event Customization: Override the ScheduleTriggered event to add metadata:

    class CustomScheduleTriggered extends \DirectoryTree\Cadence\Events\ScheduleTriggered
    {
        public function broadcastOn()
        {
            return [];
        }
    }
    
  3. Query Scoping: Add global scopes to filter schedules (e.g., by model type):

    use Illuminate\Database\Eloquent\Builder;
    
    class ScheduleScope
    {
        public function
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky