0

Command output — line(), info(), error(), table(), progressBar()

Beginner5 min read·lv-25-003

Concept

Scheduling runs Artisan commands (and PHP callables) automatically at defined intervals. Laravel's scheduler replaces N cron entries with a single cron job that runs php artisan schedule:run every minute.

Single cron entry:

cron
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1

This fires every minute. The scheduler decides which tasks are due and runs them.

Defining schedules: In routes/console.php (Laravel 11+) or app/Console/Kernel.php:

Frequency methods (chainable):

  • ->everyMinute(), ->everyFiveMinutes(), ->everyFifteenMinutes(), ->everyThirtyMinutes().
  • ->hourly(), ->hourlyAt(17) — at minute 17.
  • ->daily(), ->dailyAt('13:00'), ->twiceDaily(1, 13).
  • ->weekly(), ->weeklyOn(1, '8:00') — Monday at 8am.
  • ->monthly(), ->monthlyOn(4, '15:00') — 4th of month at 3pm.
  • ->cron('0 */6 * * *') — raw cron expression.

Constraints:

  • ->weekdays() / ->weekends(): Limit to weekdays or weekends.
  • ->between('8:00', '17:00'): Only within time range.
  • ->when(callable): Custom condition.
  • ->environments('production'): Only on specific environments.

Safety:

  • ->withoutOverlapping(): Don't run if previous instance still running (uses a cache lock).
  • ->onOneServer(): Only one server runs the task (requires cache + same Redis/DB).

schedule:work: For development — polls every minute without a cron job.

Code Example

php
<?php
// routes/console.php (Laravel 11+)
use Illuminate\Support\Facades\Schedule;

// Run artisan commands
Schedule::command('report:weekly')->weekly()->mondays()->at('8:00');
Schedule::command('orders:process')->everyFifteenMinutes()->withoutOverlapping();
Schedule::command('backup:run')->dailyAt('2:00')->environments('production');
Schedule::command('cache:prune-stale-tags')->hourly();
Schedule::command('telescope:prune --hours=48')->daily();

// Call a closure (for lightweight tasks)
Schedule::call(function() {
    \App\Models\User::where('last_login_at', '<', now()->subYear())->update(['active' => false]);
})->monthly()->at('1:00');

// Call an invokable class
Schedule::call(new \App\Jobs\CleanTempFilesJob)->daily();

// Queue a job
Schedule::job(new \App\Jobs\ProcessPendingOrders)->everyFiveMinutes()->onQueue('scheduled');

// Run shell command
Schedule::exec('node /path/to/script.js')->daily();

// onOneServer — only run on ONE server (requires shared Redis/DB cache)
Schedule::command('newsletter:send')->daily()->at('9:00')->onOneServer();

// Safety constraints
Schedule::command('payment:reconcile')
    ->dailyAt('23:00')
    ->withoutOverlapping(10)     // max 10 minute overlap window
    ->onOneServer()
    ->environments(['production', 'staging'])
    ->when(fn() => config('features.payments_enabled'));

// app/Console/Kernel.php (Laravel 10 and below)
// protected function schedule(Schedule $schedule): void
// {
//     $schedule->command('report:weekly')->weekly();
// }