Browse documentationOpen

byrcsc/laravel-data-sync · 1.x

Scheduling.

Register schedules from a definition or from config, disable them per definition or entirely, and know what the package schedules on your behalf.

Nothing is scheduled until you ask for it — with one exception: sync:prune, which runs daily whenever schedule registration is on.

Scheduling from the definition

Give the definition a schedule() method and it is registered automatically:

use Illuminate\Console\Scheduling\Event;

public function schedule(Event $event): void
{
    $event->hourlyAt(15)->withoutOverlapping();
}

The $event is the scheduled sync:run <name> command, so the whole scheduler API is available: ->timezone(), ->between(), ->onOneServer(), ->environments(), and so on.

Declaring the method is the opt-in. A definition that does not declare it is not scheduled, and there is no config key that would schedule it implicitly.

Scheduling from config

A cron expression in overrides schedules a definition without touching the class, and wins over a schedule() method on it:

// config/data-sync.php
'overrides' => [
    'products' => [
        'schedule' => '0 * * * *',
    ],
],

The expression is validated at boot; an invalid one throws an InvalidConfigurationException. This is the escape hatch for changing timing per environment without a deploy of the definition itself.

Turning schedules off

GoalSetting
Stop scheduling one definitionoverrides.<name>.schedule => false
Take a definition out of runs and schedulesoverrides.<name>.enabled => false
Register nothing at alldata-sync.register_schedules => false
'overrides' => [
    'products' => ['schedule' => false],
    'legacy-pricing' => ['enabled' => false],
],

register_schedules => false also stops the daily sync:prune. If you turn it off, wire both up yourself:

// routes/console.php
Schedule::command('sync:run products')->hourlyAt(15);
Schedule::command('sync:prune')->dailyAt('02:30');

That is the right choice when your scheduling lives entirely in one file, or when an external scheduler — Kubernetes CronJobs, a systemd timer — invokes the commands instead.

What gets registered

With register_schedules on, the package registers, at boot:

  • sync:prune, daily.
  • sync:run <name> for every enabled definition that has either a cron override or a declared schedule() method.

Registration order follows data-sync.syncs.

Overlap and drift

Scheduled runs overlap if a feed takes longer than its interval. Two protections apply, and they are not the same thing:

  • The discovery lock always applies. A second sync:run for the same definition, scheduled or manual, reports that discovery is already running and exits successfully. Processing that is already in flight continues.
  • withoutOverlapping() is yours to add in schedule(). It prevents the command from starting at all while a previous invocation is still running, which is stricter and worth having on the queued path where the command exits long before the work finishes.

For a feed that arrives once daily but at an unpredictable hour, schedule hourly and let the ledger do the deduplication. Re-running with nothing new to process costs one directory listing.

Verifying

php artisan schedule:list
php artisan sync:doctor

schedule:list shows the registered commands and their expressions. sync:doctor verifies the configuration those schedules depend on — sources reachable, disks writable, tables present — which is what turns a silent 2 a.m. failure into a deploy-time one.