Batasan dan trade-off paket
Setiap keputusan di paket ini ada harganya. Halaman ini menyebutkan harganya, menunjuk API tempat masing-masing bermuara, dan menyatakan terus terang apa yang tidak didukung. Bacalah sebelum mengadopsi paketnya, lalu baca lagi ketika ada sesuatu yang terasa seperti bug — beberapa entri di bawah ini justru berjalan sesuai rancangan.
Contoh paling jelas dari pertukarannya
Jenis kolom baru berarti dua suntingan, bukan satu. Itulah harga dari batas eksplisit dan ber-type-check antara PHP dan Vue:
use PandaPanel\Tables\Columns\CustomColumn;
CustomColumn::make('health')
->component('Panels/Admin/Columns/HealthBar')
->state(static fn (Model $record): array => [
'percent' => $record->getAttribute('health'),
]);2
3
4
5
6
7
<!-- resources/js/pages/Panels/Admin/Columns/HealthBar.vue -->
<script setup lang="ts">
defineProps<{ state: { percent: number } | null }>();
</script>
<template>
<span>{{ state?.percent ?? '—' }}%</span>
</template>2
3
4
5
6
7
8
npm run buildLangkah build itu tidak opsional: registry-nya adalah import.meta.glob yang dievaluasi saat build, jadi komponen yang tidak pernah dilihat proses build akan merender fallback netral. Sebagai gantinya, nama yang tidak ikut dikompilasi tidak bisa dijangkau bagaimanapun sebuah request mengejanya, dan tipe PHP yang tidak punya renderer Vue menjadi error kompilasi alih-alih sel kosong.
Pertukaran yang diterima
| Diterima | Biayanya |
|---|---|
| Perjalanan bolak-balik ke server per interaksi tabel | Lebih lambat dari tabel sisi klien; sebagai gantinya URL adalah state-nya dan tidak ada store klien yang terduplikasi |
| Metadata PHP plus renderer Vue | Dua tempat yang harus disentuh untuk jenis kolom baru; sebagai gantinya batasnya eksplisit dan ber-type-check di kedua sisi |
| Eksplisit mengalahkan sihir | Di beberapa tempat lebih bertele-tele dibanding konvensi Filament, misalnya getId() dibanding accessor gabungan |
| Grafik SVG tanpa dependensi | Tanpa tooltip, zoom, atau animasi; sebagai gantinya tidak ada pustaka charting dan union widget-nya tetap lengkap |
| Panel didaftarkan manual | Satu suntingan per panel baru; sebagai gantinya daftar panel terlihat jelas |
| Tanpa test runner di browser | Interaksi sisi klien hanya ditutupi oleh tipe, proses build, dan test request di sisi server |
Perjalanan bolak-balik ke server
Pencarian, sorting, filter, paginasi, grouping, dan tab semuanya melakukan navigasi:
/admin/users?search=ada&sort=name&direction=asc&perPage=25&page=2&filters[verified]=trueuseResource() menulis ke query string dan membiarkan server yang menjawab, dengan preserveState dan preserveScroll agar mengetik tidak menghilangkan fokus. Tidak ada mode tabel sisi klien, dan tidak ada opsi untuk menyalakannya. Kalau sebuah tabel harus interaktif tanpa request, tabel itu bukan index resource — PandaPanel\Tables\ArrayTableData menangani tabel atas data yang tidak berada di database, tetapi state-nya tetap hidup di URL.
Eksplisit mengalahkan sihir
$panel->path('admin'); // setter, nama polos
$panel->getPath(); // pembaca, berawalan get2
Setiap setter fluent memakai nama polos dan setiap pembacanya berawalan get. PHP tidak bisa melakukan overload, dan accessor gabungan yang mengembalikan string|static justru sihir yang dihindari framework ini. Serupa dengan itu, panel didaftarkan di config/panda-panel.php alih-alih ditemukan otomatis, dan make:panel hanya mencetak baris yang perlu ditambahkan alih-alih menyunting config diam-diam — panel:install yang menuliskannya, karena instalasi yang selesai dengan panel tak terjangkau adalah hasil yang lebih buruk.
Grafik
ChartVariant berisi Bar, Line, Area, Doughnut. ChartOptions mencakup legenda, grid, stacked, filled, curved, label titik, range() yang dipatok, serta format() untuk nilainya. Apa pun di luar itu adalah CustomWidget:
use PandaPanel\Widgets\CustomWidget;
final class Heatmap extends CustomWidget
{
protected static string $component = 'Panels/Admin/Widgets/Heatmap';
/** @return array<string, mixed> */
public function data(): array
{
return ['cells' => /* ... */];
}
}2
3
4
5
6
7
8
9
10
11
12
Kekurangan yang diketahui, dinyatakan alih-alih disiratkan
Select::relationship()sudah diimplementasikan tetapi belum punya feature test. Tidak ada model diexamples/yang punya relasi yang layak dipilih, jadi fitur ini ditutupi oleh tipe dan guard clause, bukan oleh test request.- Middleware
verifiedtidak berefek kecualiUserAnda mengimplementasikanMustVerifyEmail. Panel mendeklarasikannya dengan benar lewat->auth()dan akan menegakkannya begitu model mengimplementasikan kontraknya. Apakah verifikasi diwajibkan adalah keputusan produk. $panel->assets()berarti dua suntingan. Path-nya juga harus muncul diinputmilikvite.config.ts, atau Vite tidak punya apa pun untuk disajikan dan halamannya gagal dengan error manifest. Kegagalan itu adalah kegagalan yang tepat — aset yang dideklarasikan tapi tidak pernah di-build memang kesalahan — tetapi itulah sebabnya ini bukan perubahan satu baris.- Kelas dari
cssHooks()harus selamat melewati build Tailwind. String bebas dari panel provider tidak ada di berkas mana pun yang dipindai Tailwind, jadi gunakan kelas yang juga muncul di tempat lain dalam aplikasi, atau tambahkan provider-nya ke glob konten. - Warna yang disetel lewat
colors()dibuang diam-diam bila tidak valid. Propertinya harus properti yang dibaca stylesheet dan nilainya harus bisa di-parse sebagai warna. Panel dengan satu warna keliru tetap dirender bersama sisa temanya — itu kegagalan yang tepat — tetapi tidak ada yang memberi tahu bahwa nilainya diabaikan. - Nama ikon yang tidak terdaftar tidak merender apa pun, tanpa error. Jalankan
php artisan panel:iconssetelah mendeklarasikan ikon baru;--checkakan gagal alih-alih menulis, untuk keperluan CI.
Sengaja tidak didukung
| Alasannya | |
|---|---|
| Inertia (server) 2.x | Form panel memakai komponen <Form> milik Inertia 3 dan event router flash. Keduanya tidak ada di 2.x, dan tidak ada shim yang bisa mengadakannya. |
| Tailwind 3 | resources/css/panda-panel.css adalah stylesheet Tailwind 4 — @theme, @custom-variant, @source. Tailwind 3 tidak membaca satu pun direktif itu. |
| React, Svelte | Komponennya adalah SFC Vue. Separuh sisi server-nya diserialisasi menjadi array biasa dan tidak terikat framework, jadi renderer lain mungkin saja dibuat; belum ada yang ditulis, dan belum ada rencananya. |
| Aplikasi Blade murni | Setiap layar panel adalah respons Inertia. Tanpa middleware Inertia dan root view, URL panel pertama sudah 500. |
| Laravel 11 ke bawah | Sudah di luar dukungan keamanan, dan tidak bisa di-resolve composer — lihat di bawah. |
| Livewire | Tidak dipakai di mana pun. Tidak ada plugin, tema, atau custom field Filament yang bisa dipakai di sini. |
Kenapa bukan Laravel 11
Ada dua alasan, dan yang kedua menentukan. Jendela keamanan Laravel 11 ditutup pada Maret 2026. Dan setiap rilis 11.x, dari v11.0.0 sampai v11.55.1, ditandai oleh peringatan keamanan yang belum ditambal, sehingga composer update terhadap constraint ^11.x tidak bisa di-resolve — composer melaporkan ID peringatannya lalu berhenti. Sebuah paket tidak bisa mengklaim mendukung versi yang CI miliknya sendiri pun tidak bisa memasangnya.
Jaraknya lebih kecil daripada yang tampak: aplikasi di PHP 8.2 didukung sepenuhnya, lewat Laravel 12, yang merupakan Laravel terbaru yang jalan di atasnya. Yang di luar jangkauan adalah Laravel 11 secara spesifik, bukan PHP lama.
Dukungan versi
| Didukung | |
|---|---|
| PHP | 8.2, 8.3, 8.4 |
| Laravel | 12.x, 13.x |
| Inertia (server) | inertiajs/inertia-laravel 3.x |
| Fortify | 1.37.2+ |
| Node | 20.19+, 22, 24 |
| Vue | 3.5+ |
| Vite | 7.x |
| Tailwind | 4.1+ |
| TypeScript | 5.7+ |
| Database | MySQL 8+, PostgreSQL 13+, SQLite 3.35+, MariaDB 10.6+ |
CI menjalankan perkalian silang PHP × Laravel × prefer-lowest/prefer-stable, jadi batas bawah setiap rentang yang dideklarasikan juga diuji. PHP 8.2 × Laravel 13 dikecualikan karena kombinasi itu tidak ada: Laravel 13 membutuhkan PHP 8.3.
Asumsi starter kit
Komponen yang dipublikasikan mengimpor sembilan belas modul yang tidak ikut dikirim , dan kedua jenisnya memang milik aplikasi dengan sengaja: @/routes/* dan @/actions/* dihasilkan Wayfinder dari tabel route Anda sendiri, sementara sisanya — @/components/UserMenuContent.vue, @/composables/useTwoFactorAuth, dan delapan lainnya — adalah tempat sebuah proyek menyimpan UI akunnya sendiri.
Praktiknya: aplikasi Laravel Vue starter kit langsung jalan; selain itu, sembilan belas berkas itu harus ditulis lebih dulu. panel:install memeriksa semuanya dan menyebutkan mana yang belum ada.
Dua alamat bawaan starter kit berubah perilaku, dan keduanya tetap menjadi alamat:
| Yang terjadi | Cara mempertahankan milik Anda | |
|---|---|---|
/dashboard | Pengguna yang sudah login diarahkan ke panel pertama yang boleh dimasukinya. Route, namanya, dan komponen halamannya tidak disentuh. | home_redirect.enabled => false |
/settings/* | Paket ini tidak melakukan apa pun. Aplikasi contoh mengarahkannya ke halaman pengaturan milik panel. | jangan salin SettingsRedirectController |
Memiliki frontend-nya
Komponen Vue milik panel dipublikasikan ke resources/js — itulah yang membuatnya bisa di-debug dan itulah yang dituntut registry saat build. Biayanya: composer update tidak bisa memperbaiki berkas yang kini Anda miliki, dan vendor:publish tidak bisa membantu — tanpa --force ia tidak memperbarui apa pun, dengan --force ia menimpa suntingan Anda, dan ia tidak bisa membedakan keduanya.
php artisan panel:assets # apa yang tertinggal, apa yang Anda ubah, apa yang bentrok
php artisan panel:assets --update # hanya menulis berkas yang belum pernah Anda sentuh
php artisan panel:assets --force # ikut menimpa berkas yang sudah disunting aplikasi ini
npm run build2
3
4
| Di disk | Di paket | Dilaporkan sebagai | --update |
|---|---|---|---|
| tidak berubah | tidak berubah | mutakhir | — |
| tidak berubah | berubah | tertinggal versi | ditulis |
| berubah | tidak berubah | milik Anda | dibiarkan |
| berubah | berubah | konflik | tidak pernah ditulis |
Semuanya bekerja dari .panel-assets.json, yang ditulis saat instalasi, dan mencatat hash setiap berkas sebagaimana saat dipublikasikan. Commit berkas itu: ia adalah catatan apa yang dipublikasikan aplikasi Anda, sebagaimana composer.lock mencatat apa yang dipasangnya. Konflik disebutkan per path dan dibiarkan apa adanya — itulah satu kasus yang tidak boleh diselesaikan sendiri oleh sebuah alat.
Di luar request
Tidak ada bagian paket ini yang menyimpan state request di variabel statis. Panel saat ini, record induk saat ini, dan tenant saat ini semuanya hidup di PanelContext, sebuah binding container ber-scope yang direset oleh ResetPanelContext — yang didaftarkan di seluruh grup web justru supaya ia berjalan untuk request yang tidak pernah mencapai panel. Karena itu Octane aman — selama Anda tidak mematikan register_web_middleware tanpa mendaftarkannya kembali.
Pekerjaan berantre berada di luar request dan karenanya di luar semua itu:
use PandaPanel\Tenancy\Tenancy;
Tenancy::for($tenant, fn () => InvoiceResource::query()->count());2
3
Resource ber-scope tenant yang ditanyai di luar tenant akan memunculkan exception, bukan berjalan tanpa scope. Justru itulah maksudnya: query tanpa scope akan mengembalikan record milik semua tenant dan tampak seperti halaman yang berfungsi. Begitu pula panel() mengembalikan null di luar panel; kode yang membutuhkannya meminta berdasarkan id.
Postur keamanan, dan sampai mana batasnya
Beberapa default sengaja dibuat merepotkan:
- Integrasi bersifat tolak-secara-default.
integrations.allowed_hostskosong, jadi tidak ada yang bisa dijangkau sampai sebuah tujuan ditambahkan ke config — lewat deploy, bukan lewat kiriman form. Layar itu mengirim HTTP dari sisi server ke tujuan yang diketik seseorang, yang secara konstruksi merupakan permukaan SSRF.integrations.block_private_networksmenolak host yang me-resolve ke rentang privat, loopback, atau link-local, diperiksa saat penyimpanan dan diperiksa lagi tepat sebelum setiap request. - Sel CSV yang diawali
=,+,-,@, tab, atau carriage return diberi apostrof di depan. Spreadsheet mengevaluasi sel semacam itu saat dibuka.Exporter::escapesFormulas()mematikannya untuk umpan data yang dibaca mesin lain. - Berkas ekspor dan impor mendarat di disk privat dalam direktori per pengguna, dan endpoint unduhannya menyusun segmen itu dari siapa pun yang sedang meminta.
- Policy yang tidak ada berarti menolak. Resource yang baru dibuat menjawab 403 sampai modelnya punya policy. Nyalakan
strictAuthorization()agar policy yang hilang — atau policy yang tidak punya metode untuk kemampuan tersebut — memunculkan exception alih-alih menolak diam-diam.
Yang tidak dilakukan paket ini: ia tidak membuat database, tidak berpindah koneksi, dan tidak memutuskan arti sebuah subdomain. Identifikasi tenant adalah urusan aplikasi Anda — itu pula sebabnya mengubah tenant kembali menjadi URL harus Anda sediakan (tenantUrlUsing()), dan sebabnya pemindah tenant tidak dirender tanpanya.
Hal yang mudah terlewat
--updatetidak pernah menyelesaikan konflik. Berkas yang berubah di kedua sisi dilaporkan dan dibiarkan. Bandingkan denganvendor/chocoalano/panel, gabungkan manual, lalu jalankan--force.- Mematikan
register_web_middlewareikut menghapusResetPanelContext. Di bawah Octane, itulah yang tidak boleh dilewatkan. DeleteBulkActionselalu transaksional apa pun kata panelnya. Semua-atau-tidak-sama-sekali adalah jaminan yang diiklankannya, jadidatabaseTransactions(false)tidak menjangkaunya.- Resource yang menimpa
query()harus memanggilparent::query(), kalau tidak penyempitanmodifyQueryUsing()per panel akan hilang diam-diam. - Panel yang terdaftar dua kali di
panelshanya didaftarkan sekali, tetapi dua panel yang berbagi id, atau berbagi pasangan path/domain, melemparPandaPanel\Exceptions\PanelRegistrationExceptionsaat boot. panel:cacheberarti discovery sama sekali tidak berjalan. Resource baru yang ditambahkan setelah cache saat deploy tidak terlihat sampaipanel:cachedijalankan lagi. Di development manifest-nya memperingatkan saat sudah basi; di produksi tidak, karena tidak ada yang perlu diperingatkan.
Baca juga
- Kenapa PandaBear — penalaran yang dibayar oleh biaya-biaya ini
- Ikhtisar fitur — apa saja yang memang ada
- Perbandingan dengan konsep Filament — di mana kedua framework berpisah jalan
- Matriks Kompatibilitas — tabel dukungan selengkapnya
- Persyaratan Frontend dan Modul Host
- Memperbarui Aset, Konflik Aset
- Octane, Antrean
- Checklist Keamanan Tenancy