Laravel'de zamanlanmış görevler (task scheduling)
Laravel'in görev zamanlayıcısıyla sunucudaki tek bir cron satırından birden çok görevi kod içinde okunur biçimde nasıl planladığımı anlatıyorum.
Bir uygulamada rutin işler kaçınılmaz: günlük rapor e-postaları göndermek, süresi geçmiş oturumları temizlemek, bir API’dan belirli aralıklarla veri çekmek. Bu işleri sunucudaki crontab üzerinden yönetmek mümkün ama projeye her görev eklendiğinde sunucuya bağlanıp crontab’ı düzenlemek zahmetli, hataya açık ve takip edilmesi güç. Laravel’in task scheduler’ı bu işi kodun içine taşıyor.
Nasıl çalışıyor?
Fikir şu: sunucuya yalnızca tek bir cron girişi ekliyorsunuz. Bu giriş her dakika Laravel’in zamanlayıcısını çalıştırıyor. Hangi görevin ne zaman çalışacağını ise app/Console/Kernel.php dosyasındaki schedule metodunda kod olarak tanımlıyorsunuz.
Sunucuya eklenecek tek cron satırı:
* * * * * cd /projenin/yolu && php artisan schedule:run >> /dev/null 2>&1
Bu kadar. Artık tüm zamanlama mantığı uygulamanın içinde, sürüm kontrolünde, okunabilir hâlde.
schedule metodunu doldurmak
app/Console/Kernel.php içindeki schedule metodu görevlerin tanımlandığı yerdir:
<?php
protected function schedule(Schedule $schedule)
{
// Her sabah saat 08:00'de rapor gönder
$schedule->command('reports:send-daily')
->dailyAt('08:00');
// Her saat süresi geçmiş token'ları temizle
$schedule->command('auth:clear-expired-tokens')
->hourly();
// Her Pazartesi veritabanı istatistiklerini hesapla
$schedule->command('stats:calculate')
->weekly()
->mondays()
->at('02:00');
}
Her satır kendi içinde anlaşılır. Sunucuya bağlanmadan, crontab sözdizimini hatırlamadan hangi görevin ne zaman çalışacağını görebiliyorsunuz.
Artisan komutu oluşturmak
Zamanlanmış görev genellikle bir Artisan komutuna bağlanır. Komut oluşturmak için:
php artisan make:command SendDailyReport
Oluşan app/Console/Commands/SendDailyReport.php dosyasına iş mantığını yazıyorsunuz:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendDailyReport extends Command
{
protected $signature = 'reports:send-daily';
protected $description = 'Günlük özet raporunu yöneticilere gönderir';
public function handle()
{
$report = app(ReportService::class)->buildDaily();
app(ReportMailer::class)->send($report);
$this->info('Günlük rapor gönderildi.');
}
}
Komutu app/Console/Kernel.php’nin commands dizisine eklemek gerekiyor:
<?php
protected $commands = [
Commands\SendDailyReport::class,
];
Closure ile hızlı görev tanımı
Ayrı bir komut sınıfı oluşturmak yerine, basit görevleri doğrudan schedule içinde closure olarak yazabilirsiniz:
<?php
$schedule->call(function () {
DB::table('temp_sessions')->where('created_at', '<', now()->subDay())->delete();
})->daily();
Küçük temizlik işleri için bu yeterli; ayrı dosya açmaya değmeyebilir. Ama daha karmaşık iş mantığı varsa Artisan komutu daha temiz kalıyor.
Closure ile tanımlanan görevleri php artisan schedule:list çıktısında görmek zor oluyor — imzası yok, yalnızca “Closure” olarak görünüyor. Bu yüzden birden fazla kişinin çalıştığı projelerde closure görevlerini minimize ediyorum; her görevin neyi yaptığı listeye bakarak anlaşılsın istiyorum.
Frekans yardımcıları
Laravel’in frequency helper’ları oldukça kapsamlı. Sık kullandıklarım:
->everyMinute()— her dakika->everyFiveMinutes()— her beş dakikada bir->hourly()— her saat başı->daily()— her gün gece yarısı->dailyAt('13:00')— her gün belirtilen saatte->weekly()— her hafta Pazar gece yarısı->monthly()— her ayın ilk günü->cron('0 8 * * 1-5')— ham cron ifadesi
Ham cron ifadesi desteği de var; standart yardımcıların karşılamadığı bir zamanlama gerektiğinde ->cron() ile tam kontrol mümkün.
Çakışmayı önlemek
Bir görev çalışmayı bitirmeden bir sonraki zamanlaması gelirse iki örnek aynı anda çalışabilir. Bunu istemediğiniz durumlar için withoutOverlapping() metodu var:
<?php
$schedule->command('import:process-queue')
->everyFiveMinutes()
->withoutOverlapping();
Bu eklemeyle Laravel, bir önceki çalışma henüz bitmemişse yeni bir çalıştırma başlatmıyor.
withoutOverlapping() bu kilitleme işini Laravel’in cache’i üzerinden yapıyor. Bunu fark etmek önemli: önbellek sürücünüz düzgün yapılandırılmamışsa veya uygulama birden fazla sunucu üzerinde çalışıyorsa ve önbellek paylaşılmıyorsa bu mekanizma beklendiği gibi çalışmıyor. Çoklu sunucu ortamlarında paylaşımlı bir cache sürücüsü (Redis gibi) kullanmak gerekiyor.
Görev çıktısını kaydetmek
Görev hata verdiğinde ya da çalışıp çalışmadığını kontrol etmek istediğinizde çıktıyı bir dosyaya yönlendirmek işe yarıyor:
<?php
$schedule->command('reports:send-daily')
->dailyAt('08:00')
->appendOutputTo(storage_path('logs/scheduler.log'));
Sonuç
Task scheduling’i ilk uyguladığımda “crontab’ı neden değiştireyim” diye düşündüm. Birkaç görev ekledikten sonra farkı net anladım: tüm zamanlama mantığı kodun içinde, Git ile takip ediliyor, başka bir geliştiricinin sunucuya erişmeden ne çalıştığını görmesi mümkün. Sunucuda yalnızca tek satır var; geri kalan her şey Kernel.php’de.