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:
php artisan migrate:statusOutput yang Dihasilkan:
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:
php artisan migrate:rollbackOutput yang Dihasilkan:
INFO Rolling back migrations.
2026_10_13_170304_rename_body_to_content_in_posts_table ........ 4.78ms DONEHanya 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:
php artisan migrate:statusOutput yang Dihasilkan:
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:
php artisan migrate:rollback --step=1Output yang Dihasilkan:
INFO Rolling back migrations.
2026_10_13_170257_add_excerpt_to_posts_table ................... 6.71ms DONEAngka 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:
php artisan migrateOutput yang Dihasilkan:
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 DONEKeduanya 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:
| Perintah | Yang dilakukan | Pemakaian khas |
|---|---|---|
migrate:reset | Rollback seluruh migration, dari yang terbaru sampai paling awal | Menormalkan database development dari nol tanpa menghapus berkas |
migrate:refresh | reset lalu migrate kembali | Mengulang seluruh skema setelah banyak perubahan beruntun |
migrate:fresh | Menjatuhkan semua tabel lalu migrate dari nol | Skema bersih di development; tercepat saat testing |
Kita lihat migrate:reset beraksi:
php artisan migrate:resetOutput yang Dihasilkan:
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 DONEUrutan 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:
php artisan migrate:freshOutput yang Dihasilkan:
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 DONEKebijakan 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.