Perubahan skema kadang perlu ditarik kembali: migration baru ternyata salah kolomnya, atau hasilnya tidak seperti dugaan. Karena setiap migration memiliki method down() yang menulis kebalikan dari up(), Laravel sanggup memundurkan skema database selangkah demi selangkah. Pada chapter ini kita praktikkan seluruh keluarga perintah pembatalan — migrate:rollback, migrate:reset, migrate:refresh, dan migrate:fresh — pada proyek latihan, lalu menutup dengan kebijakan penting tentang kapan rollback sebaiknya tidak dipakai di tim.

Nomor Batch dalam migrate:status #

Kita mulai dari kondisi proyek setelah dua chapter terakhir: tabel posts sudah dibuat, ditambah kolom excerpt, dan body sudah berganti nama menjadi content. Perintah migrate:status menampilkan seluruh riwayatnya beserta nomor batch:

bash
php artisan migrate:status

Output yang Dihasilkan:

text
  Migration name .............................................. Batch / Status  
  0001_01_01_000000_create_users_table ............................... [1] Ran  
  0001_01_01_000001_create_cache_table ............................... [1] Ran  
  0001_01_01_000002_create_jobs_table ................................. [1] Ran  
  2026_10_13_153049_create_personal_access_tokens_table .............. [1] Ran  
  2026_10_13_170046_create_posts_table ............................... [2] Ran  
  2026_10_13_170257_add_excerpt_to_posts_table ....................... [3] Ran  
  2026_10_13_170304_rename_body_to_content_in_posts_table ............ [4] Ran  

Bacaan kolomnya: batch 1 berisi migration bawaan dari instalasi, batch 2 saat kita membuat tabel posts, lalu batch 3 dan 4 saat kedua migration perubahan berjalan — masing-masing lewat perintah migrate yang berbeda. Nomor batch inilah satuan pembatalan: rollback selalu memproses migration dari batch bernomor paling besar.

migrate:rollback #

Perintah migrate:rollback tanpa opsi membatalkan seluruh migration pada batch terakhir:

bash
php artisan migrate:rollback

Output yang Dihasilkan:

text
   INFO  Rolling back migrations.  

  2026_10_13_170304_rename_body_to_content_in_posts_table ........ 4.78ms DONE

Hanya satu baris muncul karena batch 4 memang hanya berisi satu migration: rename_body_to_content_in_posts_table. Laravel menjalankan method down()-nya, sehingga kolom content kembali bernama body. Statusnya pun berubah:

bash
php artisan migrate:status

Output yang Dihasilkan:

text
  Migration name .............................................. Batch / Status  
  0001_01_01_000000_create_users_table ............................... [1] Ran  
  0001_01_01_000001_create_cache_table ............................... [1] Ran  
  0001_01_01_000002_create_jobs_table ................................. [1] Ran  
  2026_10_13_153049_create_personal_access_tokens_table .............. [1] Ran  
  2026_10_13_170046_create_posts_table ............................... [2] Ran  
  2026_10_13_170257_add_excerpt_to_posts_table ....................... [3] Ran  
  2026_10_13_170304_rename_body_to_content_in_posts_table ............ Pending  

Baris terakhir kini berstatus Pending — barisnya dihapus dari tabel migrations, dan migration itu akan dijalankan lagi bila kita memanggil migrate.

Rollback Sejumlah Langkah dengan –step #

Opsi --step membatalkan sejumlah migration terakhir secara presisi, mengabaikan batas batch:

bash
php artisan migrate:rollback --step=1

Output yang Dihasilkan:

text
   INFO  Rolling back migrations.  

  2026_10_13_170257_add_excerpt_to_posts_table ................... 6.71ms DONE

Angka pada --step menghitung migration, bukan batch — --step=1 membatalkan satu migration terakhir, dalam kasus ini add_excerpt_to_posts_table. Perilaku ini terverifikasi lewat percobaan langsung: pada database yang seluruh migration-nya berada di batch 1, --step=2 tetap membatalkan tepat dua migration terakhir.

Menjalankan Ulang Migration yang Dibatalkan #

Kedua migration yang barusan kita batalkan bisa langsung dipulihkan dengan migrate:

bash
php artisan migrate

Output yang Dihasilkan:

text
   INFO  Running migrations.  

  2026_10_13_170257_add_excerpt_to_posts_table ................... 4.89ms DONE
  2026_10_13_170304_rename_body_to_content_in_posts_table ........ 1.78ms DONE

Keduanya dijalankan berurutan sesuai timestamp, lalu dicatat kembali ke tabel migrations dengan nomor batch yang sama. Detailnya menarik: karena batch tertinggi yang tersisa adalah 2, kedua migration ini masuk batch 3 — nomor batch lama yang sudah dikosongkan tidak dilanjutkan, nomor baru selalu dihitung dari batch tertinggi yang masih ada ditambah satu.

reset, refresh, dan fresh #

Tiga perintah berikut membatalkan dalam skala jauh lebih besar, dan tabel ini memetakan perbedaannya:

PerintahYang dilakukanPemakaian khas
migrate:resetRollback seluruh migration, dari yang terbaru sampai paling awalMenormalkan database development dari nol tanpa menghapus berkas
migrate:refreshreset lalu migrate kembaliMengulang seluruh skema setelah banyak perubahan beruntun
migrate:freshMenjatuhkan semua tabel lalu migrate dari nolSkema bersih di development; tercepat saat testing

Kita lihat migrate:reset beraksi:

bash
php artisan migrate:reset

Output yang Dihasilkan:

text
   INFO  Rolling back migrations.  

  2026_10_13_170304_rename_body_to_content_in_posts_table ........ 2.77ms DONE
  2026_10_13_170257_add_excerpt_to_posts_table ................... 3.40ms DONE
  2026_10_13_170046_create_posts_table ........................... 0.30ms DONE
  2026_10_13_153049_create_personal_access_tokens_table .......... 0.46ms DONE
  0001_01_01_000002_create_jobs_table ............................ 0.84ms DONE
  0001_01_01_000001_create_cache_table ........................... 0.63ms DONE
  0001_01_01_000000_create_users_table ........................... 0.86ms DONE

Urutan pembatalan berjalan terbalik dari daftar migrate:status — migration termuda dibatalkan pertama, sesuai logika menarik kembali perubahan layer demi layer. Adapun migrate:fresh bahkan tidak menjalankan down() sama sekali; ia menjatuhkan seluruh tabel sekaligus lalu membangunnya dari nol:

bash
php artisan migrate:fresh

Output yang Dihasilkan:

text
  Dropping all tables ............................................ 3.13ms DONE

   INFO  Preparing database.  

  Creating migration table ....................................... 2.49ms DONE

   INFO  Running migrations.  

  0001_01_01_000000_create_users_table ........................... 1.65ms DONE
  0001_01_01_000001_create_cache_table ........................... 0.86ms DONE
  0001_01_01_000002_create_jobs_table ............................ 1.62ms DONE
  2026_10_13_153049_create_personal_access_tokens_table .......... 0.79ms DONE
  2026_10_13_170046_create_posts_table ........................... 0.37ms DONE
  2026_10_13_170257_add_excerpt_to_posts_table ................... 3.15ms DONE
  2026_10_13_170304_rename_body_to_content_in_posts_table ........ 1.61ms DONE

Kebijakan Forward-Only di Tim #

Pada database pribadi, rollback adalah alat yang aman untuk dicoba-coba seperti di atas. Namun untuk migration yang sudah dibagikan — di-commit ke repositori atau sudah berjalan di staging dan produksi — kebijakan yang lazim di industri adalah forward-only: kesalahan diperbaiki dengan migration baru, bukan dengan me-rollback migration lama. Ada dua alasan kuat. Pertama, down() yang menjalankan dropColumn atau dropIfExists ikut membuang data nyata yang sudah terkumpul. Kedua, rollback membuat anggota tim berkecipak: Siti yang sudah menjalankan migration versi terbaru akan memiliki skema berbeda dengan Budi yang telah membatalkannya lewat rollback, padahal tabel migrations di database keduanya mencatat nama berkas yang sama.

Praktik amannya sederhana: sebelum commit, boleh rollback dan perbaiki migration selama migration itu belum pernah dibagikan; setelah commit dan rekan lain menjalankannya, tulis migration koreksi baru. Dokumentasi migrations menyediakan rujukan lengkap seluruh perintah yang kita bahas di sini.

Skema posts kita kini utuh kembali — dibuat, diubah, dan dipulihkan lewat siklus migration yang sehat. Fondasi ini membuka materi berikutnya: memilih tipe kolom yang tepat untuk setiap data, dari angka pecahan sampai JSON, pada chapter Column Types.