Perbandingan dengan konsep Filament
Tabel penerjemah untuk developer yang datang dari Filament. Kosakatanya memang sengaja dipinjam, jadi sebagian besar konsep punya padanan langsung di sini; kodenya tidak dipinjam sama sekali, jadi tidak ada yang kompatibel untuk disalin. Baca halaman ini untuk memetakan apa yang sudah Anda kuasai ke nama-nama kelas di paket ini, dan untuk melihat di mana keduanya sengaja berpisah jalan.
Resource yang sama, ditulis di sini
namespace App\Panels\Admin\Resources\Users;
use App\Models\User;
use App\Panels\Admin\Resources\Users\Pages\CreateUser;
use App\Panels\Admin\Resources\Users\Pages\EditUser;
use App\Panels\Admin\Resources\Users\Pages\ListUsers;
use App\Panels\Admin\Resources\Users\Pages\ViewUser;
use PandaPanel\Forms\Components\TextInput;
use PandaPanel\Forms\FormSchema;
use PandaPanel\Resources\Resource;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
final class UserResource extends Resource
{
protected static string $model = User::class;
protected static ?string $navigationIcon = 'users';
protected static ?string $navigationGroup = 'User Management';
public static function table(TableSchema $table): TableSchema
{
return $table->columns([
TextColumn::make('name')->searchable()->sortable(),
TextColumn::make('email')->searchable(),
]);
}
public static function form(FormSchema $schema): FormSchema
{
return $schema->schema([
TextInput::make('name')->required()->maxLength(255),
TextInput::make('email')->email()->required(),
]);
}
/** @return array<string, class-string> */
public static function pages(): array
{
return [
'index' => ListUsers::class,
'create' => CreateUser::class,
'view' => ViewUser::class,
'edit' => EditUser::class,
];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Bentuknya terasa akrab. Setiap namespace-nya PandaPanel\*, setiap kelas skemanya milik paket ini sendiri, dan renderer di baliknya adalah SFC Vue di resources/js Anda.
Pemetaan konsep
| Konsep | Di sini |
|---|---|
| Panel | PandaPanel\Core\Panel, dikonfigurasi di PandaPanel\Core\PanelProvider::panel() |
| Registrasi panel | config/panda-panel.php → panels |
| Resource | PandaPanel\Resources\Resource |
| Halaman resource | PandaPanel\Resources\Pages\{ListRecords, CreateRecord, ViewRecord, EditRecord} |
| Tabel | PandaPanel\Tables\TableSchema + PandaPanel\Tables\Columns\* |
| Filter tabel | PandaPanel\Tables\Filters\* |
| Form | PandaPanel\Forms\FormSchema + PandaPanel\Forms\Components\* |
| Tata letak form | PandaPanel\Forms\Layouts\{Section, Grid, Tabs, Wizard, ...} |
| Infolist | PandaPanel\Infolists\InfolistSchema + PandaPanel\Infolists\Components\* |
| Action | PandaPanel\Actions\Action dan action bawaan di sebelahnya |
| Bulk action | kelas yang sama, dideklarasikan di ->bulkActions([...]) |
| Relation manager | PandaPanel\Resources\RelationManager |
| Cluster | PandaPanel\Clusters\Cluster |
| Widget | PandaPanel\Widgets\{StatsWidget, TableWidget, ChartWidget, CustomWidget} |
| Halaman kustom | PandaPanel\Pages\Page |
| Pencarian global | protected static array $globalSearchAttributes + $panel->globalSearch() |
| Notifikasi | PandaPanel\Notifications\Notification, PandaPanel\Broadcasting\PanelNotification |
| Tenancy | $panel->tenant() + protected static ?string $tenantRelationship |
| Plugin | PandaPanel\Plugins\Plugin |
| Render hook | $panel->renderHook(RenderHook::HeaderEnd, 'Panels/Admin/Hooks/Announcement') |
| Generator | make:panel, make:panel-resource, make:panel-page, make:panel-widget, make:panel-relation-manager |
| Cache saat deploy | php artisan panel:cache, terdaftar sebagai hook optimize |
Apa yang dipinjam, dan apa yang tidak
Yang dipinjam: kosakatanya, schema builder fluent-nya, pemisahan halaman resource, dan model discovery-nya. Pengalaman developer Filament untuk Panel, Resource, Page, Widget, Navigation, Form, Table, dan Action memang menjadi target eksplisitnya.
Yang tidak dipinjam: kodenya. Filament merender lewat Livewire, sementara frontend aplikasi ini adalah Vue dengan shadcn-vue dan TypeScript ketat. Menjalankan Livewire untuk panel dan Vue untuk sisanya berarti dua model komponen, dua model state, dan dua cerita build dalam satu aplikasi. Menyalin kode sumber Filament juga ditolak: yang dipinjam nilainya adalah bentuk API-nya, bukan implementasi yang dibangun di sekitar siklus hidup Livewire.
Itu punya satu konsekuensi praktis yang perlu disebut lebih dulu: tidak ada plugin, tema, atau field kustom Filament yang bisa dipakai di sini. Semuanya komponen Livewire. Field, kolom, atau widget yang Anda butuhkan harus ditulis terhadap kelas-kelas paket ini, dengan komponen Vue di bawah resources/js/pages/Panels/**.
Di mana keduanya sengaja berpisah
Setter memakai nama polos; pembacanya berawalan get
$panel->id('admin'); // setter
$panel->getId(); // pembaca
$panel->path('admin');
$panel->getPath();2
3
4
PHP tidak bisa melakukan overload, dan setter/getter gabungan yang mengembalikan string|static justru sihir yang dihindari framework ini. Di beberapa tempat memang jadi lebih bertele-tele; itu pertukarannya.
Panel didaftarkan, isinya ditemukan otomatis
// config/panda-panel.php
'panels' => [
App\Panels\Admin\AdminPanelProvider::class,
App\Panels\App\AppPanelProvider::class,
],2
3
4
5
Urutan pendaftaran terlihat di satu tempat, dan urutan itulah yang menentukan ke mana pengguna yang sudah login mendarat ketika request tidak menyebut panel. Di dalam panel tidak ada yang didaftarkan manual: resource, page, dan widget ditemukan dari path yang dideklarasikan, dan registrasi eksplisit tetap berfungsi serta digabungkan dengan hasil discovery.
Halaman adalah controller sungguhan
Halaman resource di-routing sebagai [Page::class, 'render'] untuk GET dan [Page::class, 'handle'] untuk verb tulis. Tidak ada siklus hidup komponen, tidak ada state komponen yang disimpan server, dan tidak ada closure di route mana pun — dan itulah yang menjaga php artisan route:cache tetap berfungsi.
Verb tulis mendapat nama route sendiri, karena Laravel mewajibkan nama route unik: resources.users.store untuk POST create dan resources.users.update untuk PUT edit.
Satu endpoint action per panel
Request action membawa nama action, slug resource, dan kunci record. Tidak ada yang lain. Backend me-resolve panelnya, mencari resource itu di registry panel tersebut, menemukan action-nya di skema resource itu, memuat record lewat Resource::query(), mengotorisasi, lalu barulah menjalankan handler-nya.
panel.{id}.actions.record panel.{id}.actions.bulk panel.{id}.actions.table
panel.{id}.actions.infolist panel.{id}.actions.cell panel.{id}.actions.reorder
panel.{id}.actions.form panel.{id}.actions.submit2
3
Masing-masing di-resolve terhadap skema yang mendeklarasikan action yang disebutnya, dan itu memang whitelist terpisah dengan sengaja: action milik halaman view bukan action milik tabel.
Form milik sebuah action diambil saat dialognya dibuka, bukan diserialisasi ke setiap baris:
Action::make('note')
->schema(static fn (?Model $record): FormSchema => FormSchema::make()->schema([
Textarea::make('note')->required()->maxLength(1000),
]))
->action(static function (Model $record, array $data): void {
$record->notes()->create(['body' => $data['note']]);
});2
3
4
5
6
7
Tabel berisi dua puluh record kalau tidak begitu akan mengirim dua puluh salinan form yang sama, demi membuka paling banyak satu.
Render hook menyebut komponen Vue, tidak pernah markup
Render hook Filament menyuntikkan Blade. Tidak ada yang bisa dirender menyeberangi kabel di framework ini, jadi sebuah hook menyebut key registry saat build dan membawa props yang bisa diserialisasi:
use PandaPanel\Enums\RenderHook;
$panel->renderHook(
RenderHook::HeaderEnd,
'Panels/Admin/Hooks/Announcement', // resources/js/pages/Panels/Admin/Hooks/Announcement.vue
['message' => 'Maintenance at 5pm'],
[UserResource::class], // scope opsional
);2
3
4
5
6
7
8
Enum-nya sengaja tertutup — hook yang didaftarkan pada nama yang tidak dirender shell akan diam-diam tidak melakukan apa pun. Nama komponen yang tidak terdaftar tidak merender apa pun alih-alih melempar exception: suntikan yang sifatnya dekoratif tidak boleh bisa merusak halaman yang didekorasinya. Scope direduksi menjadi slug saat registrasi, jadi UserResource::class menjadi resource:users dan tidak ada nama kelas yang pernah diserialisasi.
Tidak ada spa()
Inertia sudah merupakan aplikasi satu halaman, jadi pengaturan itu tidak punya apa pun untuk dinyalakan. Yang terbawa dari versi Filament-nya adalah prefetching dan pengecualian URL — yang di sini berarti tautan yang memang harus keluar dari SPA:
$panel
->prefetch('hover') // 'hover' (default), 'mount', 'click', atau false
->fullPageUrls('/admin/exports/*'); // navigasi browser sungguhan2
3
Pencocokannya terjadi di server, jadi keputusannya tiba bersama tautannya alih-alih diturunkan ulang pada setiap render. Item full-page dirender sebagai anchor biasa dan tidak pernah di-prefetch — tidak ada gunanya mengambil dokumen yang tidak bisa dipakai klien.
Error adalah toast yang disediakan panel
$panel
->errorNotification(403, 'Not allowed', 'Ask an administrator.')
->hideErrorNotification(404);2
3
Default-nya mencakup 403, 404, 419, 429, 500, dan 503. Ada tiga kemungkinan: entri yang menampilkan toast sekaligus menekan overlay error Inertia, entri bernilai null yang menekan keduanya tanpa suara, dan status tanpa entri yang diserahkan ke Inertia — status yang tidak punya penjelasan dari panel lebih baik ditampilkan mentah daripada ditelan.
Tabelnya di sisi server, dan URL adalah state-nya
/admin/users?search=ada&sort=name&direction=asc&perPage=25&page=2&filters[verified]=true&tab=adminsSetiap interaksi adalah satu perjalanan bolak-balik ke server. Sebagai gantinya tidak ada store klien yang terduplikasi, dan back, forward, refresh, serta bookmark bekerja. TanStack Table didaftarkan hanya untuk model kolom, visibilitas, dan pemilihan baris.
Grafiknya SVG tanpa dependensi
ChartVariant mencakup Bar, Line, Area, dan Doughnut; ChartOptions mencakup legenda, grid, stacked, filled, curved, label titik, rentang yang dipatok, dan format nilai. Tidak ada pustaka charting yang dipasang, jadi tidak ada tooltip, tidak ada zoom, dan tidak ada animasi. Apa pun di luar ChartOptions adalah CustomWidget, yang jujur menyatakan dirinya buatan khusus.
Hal yang mungkin Anda cari tapi tidak ada di sini
| Status | |
|---|---|
| Livewire, Blade, Alpine | tidak dipakai di mana pun; setiap layar adalah respons Inertia |
| Plugin, tema, field kustom Filament | tidak kompatibel secara konstruksi |
| Sorting, filter, paginasi di sisi klien | memang dirancang di sisi server |
| Pustaka charting | digantikan SVG inline; lihat di atas |
| Renderer React atau Svelte | separuh sisi server-nya tidak terikat framework, tapi belum ada yang ditulis dan belum ada rencananya |
| Test runner di browser | interaksi sisi klien ditutupi oleh tipe, proses build, dan test request di sisi server |
Cakupan test Select::relationship() | sudah diimplementasikan, tapi ditutupi tipe dan guard clause alih-alih feature test — tidak ada model di examples/ yang punya relasi yang layak dipilih |
Catatan
Action::form(Closure)danAction::schema(Closure)adalah dua hal berbeda di sini.schema()memberi action sebuah form sendiri, dibangun per record dan diambil saat dialognya dibuka.form()mengarahkan dialognya ke URL yang Anda hasilkan. Pakaischema()kecuali Anda memang sedang menyambungkan endpoint buatan sendiri.- Satu kelas resource boleh didaftarkan di beberapa panel dan berarti berbeda di masing-masing, lewat
PandaPanel\Resources\ResourceConfiguration. Kelas itu tidak boleh didaftarkan dua kali di dalam satu panel: panel meng-key resource berdasarkan slug, danResource::url()akan menjadi ambigu. - Prefix cluster mengubah path dan tidak pernah mengubah nama route, jadi mengadopsi cluster bukan perubahan yang merusak: setiap
Resource::url()yang sudah ditulis tetap berfungsi. - Otorisasinya adalah policy Laravel biasa. Tidak ada bagian policy yang perlu tahu bahwa panel itu ada, dan resource yang baru dibuat menjawab 403 sampai modelnya punya policy.
Baca juga
- Kenapa PandaBear — penalaran di balik perbedaan-perbedaan ini
- Ikhtisar fitur — daftar kelas lengkap untuk dipetakan
- Arsitektur sekilas — routing, middleware, dan registry
- Pendekatan Inertia dan Vue — apa yang menggantikan Livewire
- Batasan dan trade-off — biayanya, dinyatakan terang-terangan
- Action, Render Hook, Cluster