Konfigurasi Panel
Sebuah Panel dikonfigurasi melalui code di PandaPanel\Core\PanelProvider, lalu didaftarkan berdasarkan nama class di config/panda-panel.php.
Halaman ini menjelaskan pembagian tersebut:
- keputusan apa yang berada di file config;
- keputusan apa yang berada pada object
Panel; - mengapa batasnya dibuat seperti itu;
- dan bagaimana membaca kembali configuration Panel saat runtime.
Untuk daftar setter lengkap, lihat Panel API Reference.
Contoh minimal yang berfungsi
<?php
declare(strict_types=1);
namespace App\Panels\Admin;
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use PandaPanel\Core\Panel;
use PandaPanel\Core\PanelProvider;
final class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->path('admin')
->name('Administrator')
->auth()
->discoverResources(app_path('Panels/Admin/Resources'))
->canAccess(static fn (?Authenticatable $user): bool => $user instanceof User && $user->is_admin);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// config/panda-panel.php
'panels' => [
App\Panels\Admin\AdminPanelProvider::class,
],2
3
4
5
panel('admin')->getPath(); // 'admin'
panel('admin')->getMiddleware(); // ['web', 'auth', 'verified']
panel('admin')->routeName('dashboard'); // 'panel.admin.dashboard'2
3
php artisan make:panel Admin membuat provider. php artisan panel:install membuat provider sekaligus mendaftarkannya di config.
Dua permukaan konfigurasi
| Lokasi | Menyimpan | Dibaca |
|---|---|---|
config/panda-panel.php | Panel mana yang ada dan switch registration framework | Satu kali saat boot |
PanelProvider::panel() | Seluruh konfigurasi satu Panel | Satu kali per Panel saat boot |
Batas utamanya adalah: apakah keputusan tersebut mengandung logic.
canAccess() menerima Closure, tenant() menerima resolver, dan configureActions() menerima callback. Value semacam ini tidak cocok disimpan di config yang akan diserialisasi config:cache menggunakan var_export().
Secara teknis path, name, atau branding memang dapat disimpan sebagai array. PandaBear tetap menaruhnya di provider agar definisi satu Panel tidak tersebar ke dua file berbeda. Developer tidak perlu membuka config dan provider hanya untuk memahami satu Panel.
Yang tersisa di config/panda-panel.php adalah switch yang memengaruhi apakah framework melakukan registration global:
- routes;
- web middleware;
- guest redirect;
- migrations;
- integration security boundary;
- frontend paths.
Semua key tersebut dijelaskan di config/panda-panel.php.
PanelProvider
abstract public function panel(Panel $panel): Panel;
public function panelId(): string;
public function build(): Panel;2
3
4
5
| Method | Behavior |
|---|---|
panel() | Satu-satunya method yang wajib diimplementasikan provider. Menerima Panel yang id-nya sudah diisi lalu mengembalikannya setelah dikonfigurasi. |
panelId() | Menghasilkan id dari nama class: AdminPanelProvider → admin. Override jika ingin id berbeda. |
build() | Menjalankan panel(Panel::make($this->panelId())). Dipanggil oleh PanelManager::registerProvider(). |
use App\Panels\Admin\AdminPanelProvider;
(new AdminPanelProvider)->panelId(); // 'admin'
(new AdminPanelProvider)->build()->getPath(); // 'admin'2
3
4
Override panelId() ketika nama class bukan id yang Anda inginkan:
final class BackOfficePanelProvider extends PanelProvider
{
public function panelId(): string
{
return 'admin';
}
public function panel(Panel $panel): Panel
{
return $panel->path('back-office');
}
}2
3
4
5
6
7
8
9
10
11
12
Panel id digunakan untuk:
panel.admin.*serta lookup:
panel('admin')Sedangkan path digunakan pada URL.
Keduanya default ke string yang sama tetapi boleh berbeda:
id = admin
path = back-office2
Hindari me-resolve request-scoped service dari panel().
Method ini berjalan pada boot provider, sebelum request dan authenticated user tersedia. Jika current user di-resolve di sini, yang sedang dibaca adalah user yang memang belum ada.
Panel
public static function make(?string $id = null): self;use PandaPanel\Core\Panel;
$panel = Panel::make('reports')
->path('reports')
->auth()
->settings(false);
$panel->getId(); // 'reports'2
3
4
5
6
7
8
make() tanpa id membiarkan id unset. Pemanggilan getId() kemudian melempar PandaPanel\Exceptions\PanelRegistrationException.
Di dalam PanelProvider, id sudah di-seed sebelum panel() dipanggil.
Setter menggunakan nama sederhana seperti:
path()
middleware()
brandName()2
3
Reader menggunakan prefix seperti:
getPath()
getMiddleware()
hasSettings()2
3
PandaBear menghindari combined setter/getter yang return type-nya harus menjadi string|static atau tipe campuran lain hanya untuk meniru overloading yang tidak dimiliki PHP.
Default setiap kelompok konfigurasi
| Kelompok | Setter | Default |
|---|---|---|
| Identity | id, name, path, domain | name Str::headline($id), path sama dengan id, domain null |
| Middleware | middleware, authMiddleware, auth | base ['web'], auth stack ['auth'] |
| Front door | login, registration, passwordReset, emailVerification, requireTwoFactor | semuanya off |
| Registration | resources, pages, widgets, discoverResources, discoverPages, discoverWidgets | tidak ada registration/discovery |
| Built-ins | settings | on — Profile, Security, Appearance |
| Landing | dashboard, dashboards | PandaPanel\Pages\Dashboard |
| Shell | sidebar, topNavigation, sidebarWidth, collapsedSidebarWidth, navigation, topbar, breadcrumbs, maxContentWidth | collapsible sidebar, appearance inset, width 16rem / 3rem, tiga shell area aktif, tanpa max width |
| Branding | brandName, brandLogo, darkBrandLogo, icon, darkIcon, favicon, darkFavicon, darkMode, colors, cssHooks | brand name config('app.name'), dark mode on |
| Behavior | databaseTransactions, strictAuthorization, unsavedChangesAlerts, bootUsing | transaction on, strict authorization off, alert on |
| Navigation behavior | prefetch, fullPageUrls, errorNotification, hideErrorNotification | prefetch 'hover', tanpa full-page URL, enam default error notification |
| Search | globalSearch | aktif, limit 50, debounce 300ms, ['mod+k'] |
| Notifications | notifications, broadcasting | keduanya on |
| Extension | renderHook, subNavigationPosition, assets, plugins, configureActions | kosong, SubNavigationPosition::Top |
| Tenancy | tenant, tenantUrlUsing | tenancy tidak aktif |
| Access | canAccess | tidak ada predicate — semua user yang lolos middleware diperbolehkan |
Beberapa configuration mengakumulasi value:
- discovery paths;
- navigation groups;
- assets;
- render hooks;
- boot callbacks.
Memanggil discoverResources() dua kali menambahkan dua path.
Sebaliknya:
middleware()
authMiddleware()2
mengganti stack sebelumnya.
Karena itu jika Anda memanggil:
$panel->middleware([...])Anda harus tetap menyertakan web sendiri jika Panel membutuhkan session dan web middleware.
Membaca configuration saat runtime
panel(); // the panel for this request, or null outside one
panel('admin'); // an explicit panel; throws PanelRegistrationException if unknown2
Facade:
use PandaPanel\Facades\PandaPanel;
PandaPanel::all(); // list<Panel>, sorted by id
PandaPanel::has('admin'); // bool
PandaPanel::get('admin'); // Panel
PandaPanel::currentPanel(); // Panel|null
PandaPanel::resolveFromRequest($request); // Panel|null — longest path prefix, honours domain()
PandaPanel::firstAccessibleTo($user); // Panel|null — the first, by id, that admits them
PandaPanel::resources('admin'); // ResourceRegistry
PandaPanel::pages('admin'); // PageRegistry
PandaPanel::widgets('admin'); // WidgetRegistry
PandaPanel::navigation('admin'); // NavigationRegistry2
3
4
5
6
7
8
9
10
11
12
Facade tersebut memproxy PandaPanel\Core\PanelManager, yang merupakan container singleton. Jika tidak ingin menggunakan facade, inject PanelManager secara langsung.
Dua reader penting:
public function isAccessibleTo(?Authenticatable $user): bool;
public function toSharedArray(): array;2
3
isAccessibleTo() memeriksa dua rule dan keduanya harus mengizinkan:
PanelUser::canAccessPanel()jika User Model mengimplementasikan contract;- predicate
Panel::canAccess().
Panel yang menjawab "boleh" tidak dapat mengoverride User Model yang menjawab "tidak".
toSharedArray() adalah data yang dikirim ke Vue sebagai prop panel.
Hanya configuration yang benar-benar dibutuhkan frontend yang ikut dikirim.
Contoh server-only concern yang tidak dikirim:
- middleware;
- database transaction behavior;
- strict authorization;
- boot callbacks.
Lihat Server Metadata to Vue.
Konfigurasi berbeda berdasarkan environment
config() dapat digunakan di dalam panel() karena config sudah dimuat sebelum provider boot.
public function panel(Panel $panel): Panel
{
return $panel
->path((string) config('panels.admin_path', 'admin'))
->strictAuthorization(app()->environment('local', 'testing'));
}2
3
4
5
6
Gunakan config() daripada env().
Setelah config:cache dijalankan, env() di luar file config dapat menghasilkan null. Panel yang path-nya dibaca langsung dari env() dapat berubah behavior di production tanpa error yang jelas.
Jika sebuah Panel hanya ingin didaftarkan pada environment tertentu, lakukan keputusan tersebut pada file config:
// config/panda-panel.php
'panels' => array_values(array_filter([
App\Panels\Admin\AdminPanelProvider::class,
env('APP_ENV') === 'local' ? App\Panels\Debug\DebugPanelProvider::class : null,
])),2
3
4
5
6
Di file config, env() memang legitimate karena dibaca sebelum configuration dicache.
Menyesuaikan Panel per request melalui bootUsing()
bootUsing() berjalan pada setiap request yang benar-benar masuk ke Panel. Callback dijalankan dari ResolvePanel setelah access check, sehingga authenticated user dan request context sudah tersedia.
$panel->bootUsing(function (Panel $panel): void {
// per-request work; the user is known here
});2
3
API:
public function bootUsing(Closure $callback): self;
public function getBootCallbacks(): array; // list<Closure(Panel): void>
public function boot(): void;2
3
Callbacks mengakumulasi, bukan replace.
User yang ditolak Panel tidak menjalankan callback tersebut.
Panel yang sudah terdaftar merupakan live object. Test dapat mengubah setting yang dibaca saat request:
use PandaPanel\Core\PanelManager;
app(PanelManager::class)->get('admin')->requireTwoFactor();2
3
Perubahan seperti ini dapat memengaruhi behavior middleware per request.
Namun perubahan tidak dapat menulis ulang route yang sudah diregistrasikan saat boot. Mengubah middleware Panel setelah route registration juga tidak mengubah middleware stack yang sudah ter-copy ke route group.
Mendaftarkan Panel dari code
use PandaPanel\Core\Panel;
use PandaPanel\Facades\PandaPanel;
$panel = PandaPanel::register(
Panel::make('reports')->path('reports')->settings(false),
);2
3
4
5
6
Atau dari provider:
PandaPanel::registerProvider(
App\Panels\Admin\AdminPanelProvider::class
);2
3
Keduanya langsung membangun registry Panel.
Namun keduanya tidak otomatis mendaftarkan route jika dipanggil setelah route registrar sudah berjalan pada boot.
Jika route memang dibutuhkan:
use Illuminate\Support\Facades\Route;
use PandaPanel\Routing\PanelRouteRegistrar;
app(PanelRouteRegistrar::class)->register($panel);
Route::getRoutes()->refreshNameLookups();2
3
4
5
6
Panel yang didaftarkan dinamis seperti ini juga tidak tercantum di:
config('panda-panel.panels')sehingga panel:cache, yang mengiterasi configured panels, tidak memasukkannya ke manifest.
Catatan
panel()dipanggil satu kali per Panel per process. Pada Octane, provider tidak dijalankan ulang setiap request. Perlakukan objectPanelsebagai immutable configuration, bukan tempat menyimpan request state.- Duplicate Panel id menggagalkan boot.
PanelRegistrationExceptionmerupakanRuntimeExceptiondan tidak ditangkap karena half-registered Panel lebih berbahaya daripada boot yang gagal dengan jelas. - Provider class tidak diserialisasi ke panel manifest.
panel:cachehanya menyimpan class name Resource, Page, dan Widget untuk tiap Panel id. - Panel id menentukan prioritas, bukan urutan config.
PanelRegistry::all()di-sort berdasarkan id danfirstAccessibleTo()berjalan mengikuti urutan tersebut. getId()melempar exception jika id tidak pernah di-set. Kondisi ini hanya normalnya dapat terjadi ketika menggunakanPanel::make()tanpa argument. Provider selalu menyiapkan id.