Mengelola skema database sebagai kode yang bisa di-versioning lewat migration Yii: membuat dan mengubah tabel dengan createTable dan addColumn, pola up()/down() yang reversibel, menjalankan migrasi di berbagai environment, serta praktik seeding data awal untuk pengembangan dan production.

Di episode 5 kalian sudah memakai posts dan comments sebagai tabel — tapi dari mana tabel itu lahir? Di banyak project, jawabannya "dibuat manual di phpMyAdmin", dan di situlah awal masalah: tidak ada jejak, tidak bisa di-replay, tidak konsisten antar developer.
Migrations menyelesaikan masalah ini dengan memperlakukan skema database seperti source code: setiap perubahan skema adalah file ber-version yang bisa di-commit, di-review, dan dijalankan ulang di environment mana pun. Di episode ini kita belajar membuat dan mengubah tabel lewat migration, menjalankannya dengan yii migrate, dan menyiapkan data awal untuk pengembangan.
Bayangkan tiga developer bekerja di fitur yang sama. Developer A menambah kolom slug di tabel posts manual. Developer B memakai posts tapi tanpa kolom itu. Developer C bingung mengapa migration production gagal. Inilah biang kerok kekacauan — dan migrations adalah solusinya:
yii migrate bisa dijalankan berulang dari nol di environment baru.yii migrate/down.migration mencatat migrasi mana yang sudah dijalankan.Analoginya: migrations adalah git untuk skema database. Kalian tidak lagi bertanya "tabel ini dari mana?", melainkan membuka history migrasi dan melihat jawabannya.
php yii migrate/create create_posts_tablePerintah ini membuat file migrations/mYYYYMMDDHHMMSS_create_posts_table.php. Pola nama create_..._table dikenali Yii: migration akan otomatis memanggil dropTable pada method down() — pola yang menghemat pekerjaan.
<?php
use yii\db\Migration;
class m20260816120000_create_posts_table extends Migration
{
public function safeUp(): void
{
$this->createTable('posts', [
'id' => $this->primaryKey(),
'title' => $this->string(255)->notNull(),
'slug' => $this->string(255)->notNull()->unique(),
'body' => $this->text(),
'status' => $this->string(16)->notNull()->defaultValue('draft'),
'user_id' => $this->integer()->notNull(),
'created_at' => $this->timestamp()->defaultExpression('CURRENT_TIMESTAMP'),
'updated_at' => $this->timestamp()->defaultExpression('CURRENT_TIMESTAMP'),
]);
$this->addForeignKey(
'fk-posts-user_id',
'posts',
'user_id',
'users',
'id',
'CASCADE',
'CASCADE'
);
}
public function safeDown(): void
{
$this->dropTable('posts');
}
}Perhatikan gaya penulisan: safeUp()/safeDown() membungkus semua operasi dalam transaksi — jika satu statement gagal, seluruhnya di-rollback. Ini wajib dipakai, bukan up()/down() biasa. Daftar method yang sering dipakai:
| Method | Fungsi |
|---|---|
createTable / dropTable | Membuat / menghapus tabel |
addColumn / dropColumn | Menambah / menghapus kolom |
alterColumn | Mengubah tipe atau constraint kolom |
createIndex / dropIndex | Indeks biasa dan unik |
addForeignKey / dropForeignKey | Foreign key |
insert / update | Data awal / perbaikan data |
php yii migrate/up # jalankan migrasi yang belum dijalankan
php yii migrate/down 1 # rollback 1 langkah terakhir
php yii migrate/redo # rollback lalu jalankan ulang migrasi terakhir
php yii migrate/up 5 # jalankan sampai migrasi ke-5 dari yang tersisa
php yii migrate/history # lihat riwayatJalankan migrasi pertama kalian:
php yii migrate/up --interactive=0--interactive=0 mematikan prompt konfirmasi — penting untuk otomasi (episode 22). Tabel migration di database akan mencatat file yang sudah dijalankan, sehingga yii migrate/up hanya menjalankan yang baru.
Warning
Satu aturan emas: jangan pernah mengubah migration yang sudah dijalankan di production. Migration adalah riwayat — mengeditnya akan membuat down/redo tidak konsisten dan state production menyimpang dari kode. Jika butuh perubahan, buat migration baru. Ini juga alasan mengapa safeDown() penting ditulis dengan benar sejak awal.
Menambah kolom bukan berarti mengubah migration lama — buat migration baru:
php yii migrate/create add_read_count_to_posts_table<?php
use yii\db\Migration;
class m20260816123000_add_read_count_to_posts_table extends Migration
{
public function safeUp(): void
{
$this->addColumn('posts', 'read_count', $this->integer()->notNull()->defaultValue(0));
$this->createIndex('idx-posts-read_count', 'posts', 'read_count');
}
public function safeDown(): void
{
$this->dropColumn('posts', 'read_count');
}
}Pola ini — migration sekecil mungkin dengan satu tujuan — membuat riwayat skema mudah dibaca dan mudah di-review.
Migrations juga bisa mengisi data, tapi sebaiknya dipisahkan: migrasi untuk skema, dan data awal untuk kebutuhan pengembangan. Ada dua pendekatan umum.
Pertama, insert di dalam migration ketika data itu memang prasyarat sistem (misal role admin):
public function safeUp(): void
{
$this->insert('users', [
'username' => 'admin',
'password_hash' => Yii::$app->security->generatePasswordHash('rahasia-sementara'),
]);
}Kedua, seeder terpisah untuk data dummy pengembangan — misalnya console command:
<?php
namespace app\commands;
use app\models\Post;
use yii\console\Controller;
class SeedController extends Controller
{
public function actionPosts(int $count = 10): int
{
for ($i = 1; $i <= $count; $i++) {
$post = new Post([
'title' => "Post percobaan #$i",
'slug' => "post-percobaan-$i",
'body' => str_repeat('Konten contoh untuk praktik migrations. ', 10),
'status' => 'published',
]);
$post->save();
}
$this->stdout("OK: $count posts dibuat\n");
return self::EXIT_CODE_NORMAL;
}
}Jalankan dengan php yii seed/posts 25 — data dummy tersedia tanpa mencemari migration. Di episode 16 kita lihat pendekatan yang lebih rapi dengan fixtures untuk keperluan testing.
Migration default memakai koneksi db. Untuk project yang punya database terpisah (misal db dan db_admin), daftarkan keduanya di config/console.php:
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=app_main',
'username' => 'root',
'password' => '',
],
'db_admin' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=app_admin',
'username' => 'root',
'password' => '',
],
'migrationNamespaces' => [
'app\migrations',
'app\migrations\admin',
],migrationNamespaces memungkinkan migration ditempatkan di namespace berbeda (misal folder migrations/admin/ khusus database admin). Konsol menjalankannya dengan --db=db_admin:
php yii migrate/up --db=db_adminInti yang harus dibawa pulang:
safeUp()/safeDown() agar perubahan dibungkus transaksi.--db dan migrationNamespaces.Di episode 7 selanjutnya, kita membangun lapisan interaksi: forms, validation & widgets — ActiveForm yang menghubungkan form HTML dengan model dan rules-nya, penanganan error yang konsisten, serta widget GridView dan DetailView yang mengubah query menjadi tabel dan detail halaman dengan sangat sedikit kode. Sampai jumpa di episode 7!