Laravel Service Container: The Heart of Laravel Explained Simply
Understand Laravel's powerful dependency injection container.
Laravel's task scheduler provides a clean, expressive way to define command schedules within Laravel itself, eliminating the need to manage cron entries manually. With a single cron entry, you can schedule all your application tasks in a centralized location.
Add this single cron entry to your server:
# Edit crontab
crontab -e
# Add this line (adjust path to your project)
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
This cron calls Laravel's scheduler every minute, and Laravel determines if any scheduled tasks need to run.
Test your cron setup:
# Test if cron is working
php artisan schedule:list
# Run the scheduler manually
php artisan schedule:work
<?php
// app/Console/Kernel.php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule)
{
// Basic closure execution
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
// Artisan commands
$schedule->command('inspire')->hourly();
// Shell commands
$schedule->exec('node /home/forge/script.js')->daily();
}
/**
* Register the commands for the application.
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
// Method Description
->everyMinute(); // Run every minute
->everyTwoMinutes(); // Run every two minutes
->everyFiveMinutes(); // Run every five minutes
->everyTenMinutes(); // Run every ten minutes
->everyFifteenMinutes(); // Run every fifteen minutes
->everyThirtyMinutes(); // Run every thirty minutes
->hourly(); // Run every hour
->hourlyAt(17); // Run every hour at 17 minutes past the hour
->everyTwoHours(); // Run every two hours
->everySixHours(); // Run every six hours
->daily(); // Run every day at midnight
->dailyAt('13:00'); // Run every day at 13:00
->twiceDaily(1, 13); // Run daily at 1:00 & 13:00
->weekly(); // Run every week on Sunday at 00:00
->weeklyOn(1, '8:00'); // Run every week on Monday at 8:00
->monthly(); // Run on the first day of every month at 00:00
->monthlyOn(4, '15:00'); // Run every month on the 4th at 15:00
->quarterly(); // Run on the first day of every quarter at 00:00
->yearly(); // Run on the first day of every year at 00:00
<?php
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Clean old logs daily at 2 AM
$schedule->command('log:clean --days=30')
->dailyAt('02:00')
->description('Clean application logs older than 30 days')
->onOneServer();
// Optimize database tables weekly
$schedule->command('db:optimize')
->weeklyOn(1, '03:00') // Monday at 3 AM
->description('Optimize database tables')
->onOneServer();
// Clean expired sessions daily
$schedule->call(function () {
DB::table('sessions')
->where('last_activity', '<', now()->subDays(2)->timestamp)
->delete();
})->dailyAt('04:00')->description('Clean expired sessions');
// Backup database daily at 1 AM (production only)
$schedule->command('backup:run --only-db')
->dailyAt('01:00')
->environments(['production'])
->description('Daily database backup');
// Update application cache hourly
$schedule->command('cache:prune-stale-tags')
->hourly()
->description('Prune stale cache tags');
}
protected function schedule(Schedule $schedule)
{
// Send daily activity digest at 8 AM
$schedule->command('notifications:daily-digest')
->dailyAt('08:00')
->description('Send daily activity digest to users')
->onOneServer();
// Weekly newsletter every Monday at 9 AM
$schedule->command('newsletter:send')
->weeklyOn(1, '09:00')
->description('Send weekly newsletter')
->onOneServer();
// Birthday greetings at 10 AM daily
$schedule->command('greetings:birthday')
->dailyAt('10:00')
->description('Send birthday greetings to users');
// Subscription renewal reminders at 11 AM
$schedule->command('subscriptions:renewal-reminder')
->dailyAt('11:00')
->description('Send subscription renewal reminders');
// Abandoned cart notifications at 2 PM
$schedule->command('carts:abandoned-reminder')
->dailyAt('14:00')
->description('Send abandoned cart reminders');
}
protected function schedule(Schedule $schedule)
{
// Sync with payment gateway every 30 minutes
$schedule->command('payments:sync-transactions')
->everyThirtyMinutes()
->description('Sync payment transactions with gateway')
->withoutOverlapping(10); // Prevent overlapping for 10 minutes
// Update currency exchange rates every 4 hours
$schedule->command('currencies:update-rates')
->everyFourHours()
->description('Update currency exchange rates from API')
->onOneServer();
// Sync user data with CRM every 6 hours
$schedule->command('crm:sync-users')
->everySixHours()
->description('Sync user data with CRM system')
->onOneServer();
// Fetch weather data every hour for active locations
$schedule->command('weather:update')
->hourly()
->description('Update weather data for active locations')
->withoutOverlapping();
// Update social media statistics every 2 hours
$schedule->command('social:update-stats')
->everyTwoHours()
->description('Update social media statistics')
->onOneServer();
}
protected function schedule(Schedule $schedule)
{
// Process pending orders every 5 minutes
$schedule->command('orders:process-pending')
->everyFiveMinutes()
->description('Process pending orders')
->withoutOverlapping(5);
// Update inventory levels every 15 minutes
$schedule->command('inventory:sync-levels')
->everyFifteenMinutes()
->description('Sync inventory levels with suppliers')
->onOneServer();
// Generate sales reports daily at midnight
$schedule->command('reports:daily-sales')
->daily()
->description('Generate daily sales reports')
->emailOutputTo('reports@example.com');
// Process subscription renewals daily at 3 AM
$schedule->command('subscriptions:process-renewals')
->dailyAt('03:00')
->description('Process subscription renewals')
->onOneServer();
// Clean old carts every hour
$schedule->command('carts:clean-old')
->hourly()
->description('Clean abandoned carts older than 7 days');
// Update product recommendations weekly
$schedule->command('products:update-recommendations')
->weeklyOn(0, '04:00') // Sunday at 4 AM
->description('Update product recommendations')
->onOneServer();
}
protected function schedule(Schedule $schedule)
{
// Chain multiple tasks with success/failure hooks
$schedule->command('reports:generate-sales')
->dailyAt('23:00')
->before(function () {
// Prepare for report generation
logger('Starting sales report generation');
// Create backup before processing
\Spatie\DbDumper\Databases\MySql::create()
->setDbName(config('database.connections.mysql.database'))
->setUserName(config('database.connections.mysql.username'))
->setPassword(config('database.connections.mysql.password'))
->dumpToFile(storage_path('app/backups/pre-report-backup.sql'));
})
->after(function () {
// Cleanup after successful execution
logger('Sales report generation completed successfully');
// Remove temporary files
\Illuminate\Support\Facades\Storage::delete('temp/sales-report-*.csv');
})
->onSuccess(function () {
// Notify on success
\Illuminate\Support\Facades\Mail::to('admin@example.com')
->send(new \App\Mail\ReportGeneratedSuccessfully('Sales Report'));
})
->onFailure(function () {
// Alert on failure
\Illuminate\Support\Facades\Mail::to('alerts@example.com')
->send(new \App\Mail\ReportGenerationFailed('Sales Report'));
// Log critical error
logger()->critical('Sales report generation failed!');
})
->description('Generate and process daily sales reports')
->onOneServer();
}
protected function schedule(Schedule $schedule)
{
// Run only when a condition is true
$schedule->command('maintenance:clean-temp-files')
->daily()
->when(function () {
// Only run if disk usage is above 80%
$disk = \Illuminate\Support\Facades\Storage::disk('local');
$totalSpace = disk_total_space($disk->path(''));
$usedSpace = $totalSpace - disk_free_space($disk->path(''));
return ($usedSpace / $totalSpace) > 0.8;
})
->description('Clean temp files when disk space is low');
// Skip execution based on conditions
$schedule->command('backup:run')
->dailyAt('02:00')
->skip(function () {
// Skip backup if it's a holiday
return \App\Services\HolidayService::isHoliday(now());
})
->description('Daily backup (skipped on holidays)');
// Environment-specific scheduling
$schedule->command('queue:restart')
->hourly()
->environments(['staging', 'production'])
->description('Restart queue workers in production');
// Run only on weekdays
$schedule->command('notifications:daily-summary')
->dailyAt('17:00')
->weekdays()
->description('Daily summary notifications (weekdays only)');
// Run only on specific days of the month
$schedule->command('billing:monthly-invoices')
->monthlyOn(1, '09:00')
->description('Generate monthly invoices on 1st of month');
}
protected function schedule(Schedule $schedule)
{
// Prevent task overlapping with 10-minute lock
$schedule->command('reports:generate-complex')
->hourly()
->withoutOverlapping(10)
->description('Generate complex reports (no overlapping)');
// Long-running data processing with 30-minute lock
$schedule->command('data:process-large-dataset')
->dailyAt('03:00')
->withoutOverlapping(30)
->description('Process large datasets (prevent overlap)');
// Use file-based mutex for distributed systems
$schedule->command('sync:external-api')
->everyTenMinutes()
->withoutOverlapping()
->onOneServer() // Ensure only one server runs the task
->description('Sync with external API (distributed lock)');
}
<?php
// In AppServiceProvider boot method
use Illuminate\Console\Scheduling\Event;
Event::macro('everyOddHour', function () {
return $this->hourlyAt(1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23);
});
Event::macro('businessHours', function () {
return $this->weekdays()->between('9:00', '17:00');
});
// Usage in Kernel
protected function schedule(Schedule $schedule)
{
$schedule->command('notifications:send-alerts')
->everyOddHour()
->businessHours()
->description('Send alerts during business hours on odd hours');
}
<?php
// app/Console/Commands/MonitorScheduler.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
class MonitorScheduler extends Command
{
protected $signature = 'schedule:monitor';
protected $description = 'Monitor scheduled task health and execution';
public function handle()
{
$this->info('Scheduled Task Health Monitor');
$this->info('=============================');
$this->checkLastExecutions();
$this->checkUpcomingTasks();
$this->checkFailedTasks();
}
protected function checkLastExecutions()
{
$this->info("\nLast Executions:");
$tasks = [
'Database Backup' => 'backup:run',
'Log Cleanup' => 'log:clean',
'Cache Clear' => 'cache:clear',
'Queue Restart' => 'queue:restart',
];
foreach ($tasks as $name => $command) {
$lastRun = Cache::get("schedule:last_run:{$command}");
$status = $lastRun ? '✅ Last run: ' . $lastRun->diffForHumans() : '❌ Never run';
$this->line("{$name}: {$status}");
}
}
protected function checkUpcomingTasks()
{
$this->info("\nUpcoming Tasks (Next 24 hours):");
$schedule = app(\Illuminate\Console\Scheduling\Schedule::class);
$events = collect($schedule->events());
$now = now();
$next24Hours = $now->copy()->addDay();
$upcomingEvents = $events->filter(function ($event) use ($now, $next24Hours) {
$nextRun = $event->nextRunDate();
return $nextRun->between($now, $next24Hours);
})->sortBy(function ($event) {
return $event->nextRunDate();
});
if ($upcomingEvents->isEmpty()) {
$this->line('No upcoming tasks in the next 24 hours.');
return;
}
foreach ($upcomingEvents as $event) {
$nextRun = $event->nextRunDate();
$description = $event->description ?: $event->command;
$this->line("⏰ {$nextRun->format('H:i')} - {$description}");
}
}
protected function checkFailedTasks()
{
$this->info("\nRecent Failures:");
$failures = DB::table('failed_jobs')
->where('failed_at', '>', now()->subDay())
->orderBy('failed_at', 'desc')
->get();
if ($failures->isEmpty()) {
$this->line('✅ No recent task failures.');
return;
}
foreach ($failures as $failure) {
$this->line("❌ {$failure->failed_at} - {$failure->queue} - " . substr($failure->payload, 0, 100));
}
}
}
<?php
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Enhanced logging for critical tasks
$schedule->command('backup:run --only-db')
->dailyAt('01:00')
->before(function () use ($schedule) {
logger()->channel('scheduler')->info('Starting database backup', [
'timestamp' => now(),
'memory_usage' => memory_get_usage(true),
]);
})
->after(function () use ($schedule) {
logger()->channel('scheduler')->info('Database backup completed', [
'timestamp' => now(),
'execution_time' => microtime(true) - LARAVEL_START,
]);
})
->onSuccess(function () {
// Log success metrics
\App\Models\SchedulerLog::create([
'command' => 'backup:run',
'status' => 'success',
'executed_at' => now(),
'output' => 'Backup completed successfully',
]);
})
->onFailure(function () {
// Log failure details
\App\Models\SchedulerLog::create([
'command' => 'backup:run',
'status' => 'failed',
'executed_at' => now(),
'output' => 'Backup failed - check logs for details',
]);
})
->description('Daily database backup with enhanced logging')
->onOneServer();
}
<?php
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Get scheduling configuration from database
$scheduledTasks = \App\Models\ScheduledTask::where('active', true)->get();
foreach ($scheduledTasks as $task) {
$event = $schedule->command($task->command);
// Apply frequency based on database configuration
switch ($task->frequency) {
case 'hourly':
$event->hourly();
break;
case 'daily':
$event->dailyAt($task->time ?: '00:00');
break;
case 'weekly':
$event->weeklyOn($task->day ?: 0, $task->time ?: '00:00');
break;
case 'monthly':
$event->monthlyOn($task->day ?: 1, $task->time ?: '00:00');
break;
case 'custom':
$event->cron($task->cron_expression);
break;
}
// Apply additional options
if ($task->prevent_overlapping) {
$event->withoutOverlapping($task->overlap_timeout ?: 60);
}
if ($task->run_on_one_server) {
$event->onOneServer();
}
if ($task->environment) {
$event->environments(explode(',', $task->environment));
}
$event->description($task->description);
}
}
<?php
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Dynamic pricing updates based on market hours
$schedule->command('pricing:update-rates')
->everyFiveMinutes()
->between('09:00', '17:00')
->weekdays()
->when(function () {
// Only run during market hours in relevant timezones
$marketTimezones = ['America/New_York', 'Europe/London', 'Asia/Tokyo'];
foreach ($marketTimezones as $timezone) {
$marketTime = now()->setTimezone($timezone);
$marketHour = $marketTime->hour;
if ($marketHour >= 9 && $marketHour <= 17) {
return true;
}
}
return false;
})
->description('Update pricing during market hours');
// Seasonal task scheduling
$schedule->command('inventory:seasonal-adjustment')
->monthlyOn(1, '02:00')
->when(function () {
// Only run in specific months
$currentMonth = now()->month;
return in_array($currentMonth, [3, 6, 9, 12]); // Quarterly adjustments
})
->description('Quarterly seasonal inventory adjustments');
// Load-based scheduling
$schedule->command('reports:generate-heavy')
->dailyAt('23:00')
->when(function () {
// Check system load
$load = sys_getloadavg();
// Only run if system load is below threshold
return $load[0] < 2.0; // 2.0 load average threshold
})
->description('Generate heavy reports during low system load');
}
<?php
// tests/Unit/SchedulerTest.php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\Facades\Artisan;
class SchedulerTest extends TestCase
{
public function test_essential_tasks_are_scheduled()
{
$schedule = app(Schedule::class);
// Get all scheduled events
$events = collect($schedule->events());
// Test that backup task is scheduled daily
$backupEvent = $events->first(function (Event $event) {
return str_contains($event->command, 'backup:run');
});
$this->assertNotNull($backupEvent, 'Backup task should be scheduled');
$this->assertEquals('0 1 * * *', $backupEvent->expression);
// Test that queue restart is scheduled hourly
$queueEvent = $events->first(function (Event $event) {
return str_contains($event->command, 'queue:restart');
});
$this->assertNotNull($queueEvent, 'Queue restart should be scheduled');
$this->assertEquals('0 * * * *', $queueEvent->expression);
}
public function test_task_frequency()
{
$schedule = app(Schedule::class);
$events = collect($schedule->events());
$logCleanEvent = $events->first(function (Event $event) {
return str_contains($event->command, 'log:clean');
});
$this->assertNotNull($logCleanEvent);
// Test that log cleanup runs daily at 2 AM
$this->assertEquals('0 2 * * *', $logCleanEvent->expression);
}
public function test_task_overlap_prevention()
{
$schedule = app(Schedule::class);
$events = collect($schedule->events());
$reportEvent = $events->first(function (Event $event) {
return str_contains($event->command, 'reports:generate-complex');
});
$this->assertNotNull($reportEvent);
$this->assertTrue($reportEvent->withoutOverlapping);
}
public function test_environment_specific_tasks()
{
$schedule = app(Schedule::class);
$events = collect($schedule->events());
$productionOnlyEvents = $events->filter(function (Event $event) {
return !empty($event->environments) && in_array('production', $event->environments);
});
// Ensure production-only tasks exist
$this->assertGreaterThan(0, $productionOnlyEvents->count());
}
}
<?php
// tests/Feature/SchedulerExecutionTest.php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Mail;
class SchedulerExecutionTest extends TestCase
{
public function test_daily_backup_command()
{
// Mock the backup service
$this->mock(\Spatie\Backup\Tasks\Backup\BackupJob::class, function ($mock) {
$mock->shouldReceive('run')->once();
});
// Execute the backup command
Artisan::call('backup:run --only-db');
$this->assertEquals(0, Artisan::output());
}
public function test_email_notifications_are_sent()
{
Mail::fake();
// Execute notification command
Artisan::call('notifications:daily-digest');
// Assert emails were sent
Mail::assertSent(\App\Mail\DailyDigest::class);
}
public function test_queue_processing_tasks()
{
Queue::fake();
// Execute task that should queue jobs
Artisan::call('reports:generate-daily');
// Assert jobs were queued
Queue::assertPushed(\App\Jobs\GenerateReport::class);
}
public function test_database_cleanup()
{
// Create test data that should be cleaned up
\App\Models\Log::factory()->create([
'created_at' => now()->subDays(35)
]);
// Execute cleanup command
Artisan::call('log:clean --days=30');
// Assert old data was removed
$this->assertEquals(0, \App\Models\Log::where('created_at', '<', now()->subDays(30))->count());
}
}
protected function schedule(Schedule $schedule)
{
// Secure sensitive tasks with environment restrictions
$schedule->command('backup:run --only-db')
->dailyAt('01:00')
->environments(['production', 'staging'])
->onOneServer()
->description('Secure database backup');
// Add authentication for sensitive operations
$schedule->command('users:anonymize-old')
->monthly()
->when(function () {
return app()->environment('production');
})
->description('Anonymize old user data');
}
protected function schedule(Schedule $schedule)
{
// Distribute heavy tasks throughout the day
$schedule->command('reports:generate-sales')
->dailyAt('23:30')
->description('Nightly sales reports');
$schedule->command('analytics:process')
->dailyAt('02:00')
->description('Early morning analytics processing');
$schedule->command('backup:run')
->dailyAt('04:00')
->description('Pre-dawn backups');
// Use queue for heavy processing
$schedule->command('images:optimize-old')
->dailyAt('03:00')
->description('Optimize old images via queue')
->onOneServer();
}
#!/bin/bash
# check-scheduler-health.sh
# Check if scheduler is running
if ! crontab -l | grep -q "schedule:run"; then
echo "ALERT: Scheduler cron job missing!" | mail -s "Scheduler Alert" admin@example.com
fi
# Check last execution time
LAST_RUN=$(php artisan schedule:monitor --last-run)
if [ $(date -d "$LAST_RUN" +%s) -lt $(date -d "1 hour ago" +%s) ]; then
echo "ALERT: Scheduler hasn't run recently!" | mail -s "Scheduler Alert" admin@example.com
fi
1. What is Laravel Task Scheduling and why use it?
Laravel Task Scheduling provides a fluent API to define scheduled tasks within Laravel, eliminating the need to manage multiple cron entries manually. It offers better organization, testing capabilities, and Laravel integration.
2. How does Laravel scheduling work with only one cron entry?
A single cron entry runs php artisan schedule:run every minute. Laravel then checks all defined tasks and executes those that are due based on their schedule definitions.
3. What's the difference between ->daily() and ->dailyAt('14:00')?
->daily() runs at midnight (00:00), while ->dailyAt('14:00') runs at 2:00 PM specifically.
4. How do you prevent overlapping task execution?
Use ->withoutOverlapping() method which prevents a task from running if the previous execution is still running.
5. What's the purpose of ->onOneServer()?
It ensures the scheduled task only runs on one server in a multi-server setup, using atomic locks to prevent concurrent execution across servers.
6. How can you run tasks only in specific environments?
Use ->environments(['production']) to restrict task execution to specific environments.
7. What are task hooks and when to use them?
Task hooks (before, after, onSuccess, onFailure) allow you to execute code at different points in the task lifecycle, useful for logging, cleanup, or notifications.
8. How do you test scheduled tasks?
Use Schedule::events() to get all scheduled events and assert their properties, or test the actual command execution with Artisan.
9. What's the difference between ->cron() and other frequency methods?
->cron() allows you to define custom cron expressions for complex schedules, while other methods provide convenient shortcuts for common frequencies.
10. How do you monitor scheduled task health?
Create monitoring commands that check last execution times, failed tasks, and upcoming schedules, integrating with logging and alerting systems.
# Check cron status
sudo systemctl status cron
# Verify cron entry
crontab -l
# Test scheduler manually
php artisan schedule:work
// Add withoutOverlapping with reasonable timeout
$schedule->command('long:task')
->daily()
->withoutOverlapping(30); // 30 minute timeout
// Limit memory usage for heavy tasks
$schedule->command('memory:heavy-task')
->daily()
->before(function () {
ini_set('memory_limit', '512M');
});
// Set application timezone in config/app.php
'timezone' => 'America/New_York',
// Or set per task
$schedule->command('task:timezone-sensitive')
->dailyAt('09:00')
->timezone('America/New_York');
You've now mastered Laravel Task Scheduling! From basic cron replacements to advanced distributed scheduling patterns, you have the tools to automate any repetitive task in your Laravel applications efficiently and reliably.