Setiap aplikasi dewasa punya pekerjaan yang harus berulang: kirim laporan tiap pagi, bersihkan tabel yang membengkak, sinkronisasi data ke layanan eksternal. Solusi klasik adalah menulis baris di crontab server — dan di situlah masalahnya: begitu aplikasi tumbuh, baris cron menyebar ke banyak server, tidak ikut ter-deploy, dan tidak pernah terekam di Git. Laravel menawarkan pembalikan pendekatan lewat task scheduler: seluruh jadwal didefinisikan sebagai kode PHP di satu berkas, sementara crontab hanya perlu satu baris. Pada chapter ini kita bedah mengapa pendekatan terpusat ini menyelesaikan masalah nyata operasional, sebelum menulis jadwal pertama kita di chapter berikutnya.

Masalah Cron yang Tersebar #

Cron bekerja dengan prinsip sederhana: sistem operasi mengeksekusi perintah pada menit-menit yang cocok dengan pola lima field (menit jam tanggal bulan hari). Masalahnya bukan pada mekanismenya, melainkan pada cara kita mengelolanya:

  • Per-server: crontab hidup di tiap server. Tiga application server berarti tiga tempat berbeda yang harus dikonfigurasi dan disinkronkan manual.
  • Tak terlihat: tidak ada perintah “tampilkan semua jadwal aplikasi” — kita harus SSH ke setiap server dan membaca crontab -l.
  • Tanpa riwayat: crontab tidak masuk Git, sehingga perubahan jadwal tidak ikut di-review, tidak bisa di-rollback, dan hanya diketahui admin yang menuliskannya.

Skenario khasnya: developer menambahkan emails:send di crontab server staging, lupa menyalinnya ke production, dan laporan harian berhenti terkirim tanpa error di mana pun. Scheduler Laravel dirancang untuk menutup celah operasional seperti ini.

Scheduler Terpusat: Satu Entri Cron #

Laravel membalik arsitekturnya. Kita menulis seluruh jadwal di dalam aplikasi, lalu crontab hanya memanggil satu perintah ini setiap menit:

bash
* * * * * cd /var/www/aplikasi && php artisan schedule:run >> /dev/null 2>&1

Perintah schedule:run memeriksa jadwal yang kita definisikan, lalu mengeksekusi task yang jatuh temponya pada menit ini. Ketika tidak ada yang jatuh tempo, perintah ini keluar dengan tenang:

bash
php artisan schedule:run

Output yang Dihasilkan:

text

   INFO  No scheduled commands are ready to run.  

Alur lengkapnya tiap menit terlihat seperti ini:

Karena hanya ada satu titik masuk, behavior yang sebelumnya tersebar kini jadi tanggung jawab framework: mencegah eksekusi tumpang tindih (withoutOverlapping), menjalankan task di background, hingga memfilter berdasarkan environment.

Jadwal Hidup di dalam Kode #

Sejak Laravel 11, definisi jadwal berada di routes/console.php — berkas yang sama tempat command artisan didefinisikan lewat Artisan::command(). Berkas ini terdaftar di bootstrap/app.php:

php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    // ...

Konsekuensinya besar: jadwal ikut di-commit, di-review, dan di-rollback bersama kode. Membandingkan dua versi jadwal cukup lewat git diff routes/console.php.

Karena jadwal adalah kode, framework bisa memeringkatnya. Perintah schedule:list menampilkan seluruh task berikut ekspresi cron mentahnya — jawaban atas masalah “tak terlihat” di atas. Contoh nyata dari sandbox dengan empat task terdefinisi:

text
  0  * * * *  ringkasan-tugas .................. Next Due: 38 minutes from now
  30 2 * * *  php artisan tasks:prune ............. Next Due: 6 hours from now
  0  6 * * *  laporan-harian ...................... Next Due: 9 hours from now
  0  3 * * 1  echo "backup database mulai" ......... Next Due: 5 days from now

Kondisi Eksekusi Berbasis Environment #

Jadwal juga bisa dibatasi per environment, sehingga task berat cukup aktif di production. Runner berikut mendaftarkan tasks:prune hanya untuk production, lalu membandingkan hasil eksekusinya di environment lokal:

php
Schedule::command('tasks:prune')
    ->dailyAt('02:30')
    ->name('prune-tugas')
    ->environments('production');

Output yang Dihasilkan:

text
== lokal (APP_ENV=local):

   INFO  No scheduled commands are ready to run.  

== environment=local (schedule:list --environment=local):

   INFO  No scheduled tasks have been defined.  

== environment=production:

  30 2 * * *  php artisan tasks:prune ............. Next Due: 6 hours from now

Di environment lokal task tersebut tidak dieksekusi dan tidak tampil di schedule:list --environment=local — sementara filter --environment=production memperlihatkannya. Pengecekan environment terjadi di dalam schedule:run, bukan di crontab, sehingga satu entri cron yang sama aman dipakai di semua server.

Dengan konsep terpusat ini, pertanyaan berikutnya bersifat praktis: bagaimana cara menuliskan jadwal itu sendiri? Chapter Mendefinisikan Scheduled Tasks membahas empat jenis task, penamaan, dan cara memverifikasinya lewat schedule:list. Rujukan resmi lengkap ada di dokumentasi Task Scheduling .