Loading Migration
Package menyediakan empat migration dan secara default menjalankannya langsung dari package.
Satu config key dapat mematikan loading tersebut, dan satu publish tag dapat menyalin migration ke database/migrations milik application sehingga application menjadi pemilik file-nya.
Gunakan halaman ini ketika Anda ingin:
- menyimpan migration PandaBear di repository application;
- memahami migration apa saja yang dijalankan
php artisan migrate; - atau memastikan behavior package aman ketika application sudah memiliki sebagian table/column yang sama.
Contoh minimal yang berfungsi
// config/panda-panel.php
'load_migrations' => true,2
3
php artisan migrateItu adalah behavior default.
Fresh installation dapat langsung bekerja tanpa harus mempublish migration terlebih dahulu. Hal ini penting karena Notification Centre membaca table notifications pada request Panel. Jika developer harus mengingat langkah publish sebelum migrate, first Page setelah install berpotensi gagal.
Jika Anda ingin application memiliki migration tersebut:
php artisan vendor:publish --tag=panda-panel-migrationskemudian:
'load_migrations' => false,Lakukan keduanya dengan urutan tersebut. Penjelasannya ada di Publishing.
Migration yang disediakan
Ada empat file di database/migrations milik package:
| File | Membuat/Mengubah | Rollback |
|---|---|---|
2026_08_14_130919_create_notifications_table.php | Table Laravel notifications | Hanya dihapus jika table tersebut memang dibuat package |
2026_08_14_143501_add_email_two_factor_to_users_table.php | users.two_factor_email_confirmed_at | Menghapus column |
2026_08_15_120000_create_panel_integrations_table.php | panel_integrations | Menghapus table |
2026_08_15_140000_add_history_and_signing_to_panel_integrations.php | panel_integrations.secret, panel_integration_deliveries | Menghapus perubahan/table terkait |
Setiap up() melakukan pemeriksaan sebelum mengubah schema, sehingga application yang sudah memiliki table atau column tersebut tidak disentuh.
notifications
PandaBear menggunakan schema notification bawaan Laravel, bukan table notification khusus framework.
Notification Centre membaca notification melalui trait Notifiable, sehingga API Laravel seperti:
markAsRead()
unreadNotifications2
tetap bekerja seperti biasa.
Konsekuensinya, notification yang dikirim bagian lain dari application juga dapat muncul di notification bell PandaBear.
Schema::create('notifications', function (Blueprint $table): void {
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index(['notifiable_type', 'notifiable_id', 'read_at']);
});2
3
4
5
6
7
8
9
10
read_at diindex bersama pemilik notification karena query utama terhadap table ini adalah:
- notification apa yang dimiliki user ini;
- berapa notification milik user ini yang belum dibaca.
up() langsung berhenti jika:
Schema::hasTable('notifications')menghasilkan true.
Banyak Laravel application memang sudah memiliki table tersebut. Migration package yang berasumsi table selalu belum ada akan membuat migrate pertama setelah install gagal.
users.two_factor_email_confirmed_at
$after = Schema::hasColumn('users', 'two_factor_confirmed_at')
? 'two_factor_confirmed_at'
: null;
Schema::table('users', function (Blueprint $table) use ($after): void {
$column = $table->timestamp('two_factor_email_confirmed_at')->nullable();
if ($after !== null) {
$column->after($after);
}
});2
3
4
5
6
7
8
9
10
11
Column menggunakan timestamp, bukan boolean, dengan alasan yang sama seperti email_verified_at.
Framework dapat menjawab dua pertanyaan sekaligus:
Apakah Email Code aktif?
→ value !== null
Kapan diaktifkan?
→ timestamp2
3
4
5
Jika Fortify column two_factor_confirmed_at tersedia, column baru ditempatkan setelahnya. Jika tidak, column hanya ditambahkan ke table. Package tidak boleh mengasumsikan migration Fortify selalu sudah berjalan lebih dahulu.
Kode OTP itu sendiri tidak disimpan di database. Kode berada di cache, di-key berdasarkan user dan disimpan dalam bentuk hash. Kode yang hanya berlaku sepuluh menit memang seharusnya ikut hilang ketika cache di-flush.
Lihat Email Code Challenge.
Seluruh migration ini menjadi no-op jika:
- table
userstidak ada; atau - column sudah tersedia.
panel_integrations dan panel_integration_deliveries
Kedua table hanya digunakan oleh Resource yang secara eksplisit mengaktifkan integrations.
Berbeda dari notifications, tidak ada komponen Laravel lain yang seharusnya memiliki table dengan nama tersebut. Karena itu migration dapat membuatnya secara langsung dan down() dapat menghapusnya.
Migration history/signing dibuat sebagai migration kedua, bukan mengedit migration pertama.
Alasannya sederhana: migration pertama mungkin sudah dijalankan pada installation yang lebih lama. Mengubah isi migration yang sudah pernah dijalankan membuat hasil schema bergantung pada kapan package pertama kali diinstall.
Config yang membatasi jumlah dan retention delivery history dijelaskan di config/panda-panel.php.
Rollback dan mengapa notifications diperlakukan khusus
up() melewati table notifications jika table tersebut sudah ada.
Akibatnya, saat down() dijalankan, package tidak dapat berasumsi bahwa table notifications memang dibuat oleh migration PandaBear.
Jika langsung menggunakan:
dropIfExists()rollback PandaBear dapat menghapus table notification milik application.
Package tidak memiliki table metadata sendiri hanya untuk mencatat ownership, sehingga ownership notifications diinferensikan dari dua sinyal dan hanya dianggap milik package jika keduanya setuju.
namespace PandaPanel\Support;
final class PackageSchema
{
/**
* @param string $migration the dropping migration's own recorded name
* @param list<string> $columns the exact column list up() creates
*/
public static function dropIfOwned(string $table, string $migration, array $columns): void;
/**
* @param list<string> $columns
*/
public static function isOwned(string $table, string $migration, array $columns): bool;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Sinyal | Arti |
|---|---|
Tidak ada migration lain yang sudah berjalan dengan nama *_create_<table>_table | Tidak ada migration lain yang mengklaim ownership table. Nama migration PandaBear sendiri diabaikan karena published copy tetap merupakan migration yang sama. |
Schema::getColumnListing() sama persis dengan daftar column yang dibuat up() | Jika ada extra column, application sudah mengembangkan table tersebut dan package memilih tidak menghapusnya. |
Pemeriksaan pertama bersifat fail-safe.
Jika migration repository tidak dapat dibaca, misalnya database connection terputus di tengah rollback, framework menganggap table dimiliki pihak lain. Ketidakmampuan membuktikan ownership bukan izin untuk menghapus table.
Database tanpa table migrations merupakan pengecualian: belum ada migration yang pernah tercatat, sehingga tidak ada pihak yang dapat mengklaim table melalui repository dan column list menjadi satu-satunya sinyal.
use PandaPanel\Support\PackageSchema;
PackageSchema::isOwned('notifications', '2026_08_14_130919_create_notifications_table', [
'id', 'type', 'notifiable_type', 'notifiable_id', 'data', 'read_at', 'created_at', 'updated_at',
]);2
3
4
5
Migration mengirim:
basename(__FILE__, '.php')bukan nama literal. Published copy di database/migrations mempertahankan filename dan tetap dapat mengenali dirinya sendiri.
Column two_factor_email_confirmed_at tidak memiliki ambiguity yang sama karena tidak ada alasan normal bagi package lain menggunakan nama PandaBear tersebut. Karena itu down() dapat menghapus column secara langsung, tetap di belakang hasTable() untuk kondisi application create_users_table sudah dirollback lebih dahulu.
Publishing
php artisan vendor:publish --tag=panda-panel-migrationsCommand menyalin seluruh empat migration ke:
database_path('migrations')php artisan panel:install menawarkan hal yang sama secara interaktif, dengan catatan penting: migration sebenarnya sudah dapat dijalankan langsung dari package.
Publish migration hanya jika Anda ingin application memiliki dan dapat mengedit file tersebut.
Setelah publish:
'load_migrations' => false,Jika config tidak dimatikan, biasanya schema tidak langsung rusak karena migrator menggunakan migration name dan application path dapat men-shadow package copy dengan filename yang sama.
Namun keadaan tersebut tetap tidak rapi dan memiliki dua potential source of truth.
Jika published migration kemudian di-rename atau diubah strukturnya, Anda dapat berakhir dengan dua migration name berbeda yang mencoba membuat object schema yang sama. Guard mungkin membuat salah satunya silent no-op, tetapi keadaan itu tetap sulit dipelihara.
Karena itu pilih satu ownership:
Package migrations
atau
Application-owned published migrations2
3
Mematikan migration loading sepenuhnya
'load_migrations' => false,Config dibandingkan dengan === true, sehingga value selain boolean true membuat loadMigrationsFrom() tidak dipanggil.
Bagian framework lain tetap:
- boot;
- route;
- render.
Yang tidak bekerja adalah feature yang membutuhkan schema tersebut.
| Yang tidak tersedia | Dampak |
|---|---|
notifications | Bell membaca 0. SharePanelData menangkap QueryException dan menggunakan unread count 0, sehingga Panel tidak 500. |
users.two_factor_email_confirmed_at | Email Code factor tidak dapat diaktifkan. |
panel_integrations | Screen integration tidak memiliki table untuk Resource yang mengaktifkan integrations. |
Behavior notification sengaja toleran karena sebelum migrate dijalankan, empty notification bell adalah jawaban yang lebih berguna daripada seluruh Panel gagal dibuka.
Hal yang perlu diperhatikan
Urutan migration mengikuti filename di seluruh registered migration path. Migration package bertanggal
2026_08_14dan setelahnya, sehingga normalnya berjalan setelah skeleton migration0001_01_01_*. Ini memungkinkan email two-factor column ditempatkan setelah Fortify column jika tersedia.Rollback dapat meninggalkan
notificationsberdiri jika package tidak dapat membuktikan bahwa table tersebut miliknya. Ini behavior yang benar, meskipun membuatmigrate:rollbacktidak selalu simetris sempurna denganmigrate.migrate:freshtetap menghapus semuanya.PackageSchemamelindungi ordinary rollback, bukandb:wipe.Publishing tidak otomatis mematikan package migration loading. Keduanya merupakan keputusan terpisah.
Dalam package test suite, migration perlu diload eksplisit. Testbench tidak membaca config milik application Anda:
phpprotected function defineDatabaseMigrations(): void { $this->loadMigrationsFrom(__DIR__.'/../vendor/chocoalano/panel/database/migrations'); }1
2
3
4