Panel Providers
Panel Provider adalah class yang hanya memiliki satu tanggung jawab: mengonfigurasi satu Panel. Di sinilah fluent API Panel dipanggil, dan class inilah yang dicantumkan pada config/panda-panel.php.
Gunakan dokumentasi ini ketika Anda menambahkan Panel baru ke application, atau ketika perlu memahami bagaimana sebuah Panel yang sudah dikonfigurasi berubah menjadi route dan registry yang aktif.
Sebuah Provider
<?php
declare(strict_types=1);
namespace App\Panels\Admin;
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'))
->discoverPages(app_path('Panels/Admin/Pages'))
->discoverWidgets(app_path('Panels/Admin/Widgets'));
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// config/panda-panel.php
'panels' => [
App\Panels\Admin\AdminPanelProvider::class,
],2
3
4
php artisan make:panel Admin membuat Provider tersebut — dengan ->name('Admin') dan default ->icon('layout-grid') — sekaligus membuat tiga directory discovery, lalu mencetak baris config yang perlu Anda tambahkan.
Command ini tidak mengedit file config secara otomatis.
php artisan make:panel Admin
php artisan make:panel Admin --path=back-office
php artisan make:panel Admin --force2
3
Contract
PandaPanel\Core\PanelProvider adalah abstract class dengan tiga method.
| Method | Signature | Catatan |
|---|---|---|
panel | abstract public function panel(Panel $panel): Panel | Satu-satunya method yang wajib ditulis subclass. |
panelId | public function panelId(): string | Diturunkan dari nama class kecuali dioverride. |
build | public function build(): Panel | Menjalankan panel(Panel::make($this->panelId())). |
panelId() mengambil basename class, menghapus suffix PanelProvider, lalu mengubah sisanya menjadi kebab-case:
(new AdminPanelProvider)->panelId(); // 'admin'
(new BackOfficePanelProvider)->panelId(); // 'back-office'2
Override jika id harus berbeda dari nama class:
final class AdminPanelProvider extends PanelProvider
{
public function panelId(): string
{
return 'administration';
}
public function panel(Panel $panel): Panel
{
return $panel->path('admin');
}
}2
3
4
5
6
7
8
9
10
11
12
Id tersebut digunakan untuk membuat Panel::make(), sehingga argument $panel yang masuk ke method panel() sudah mengetahui id-nya.
Memanggil ->id() kembali di dalam panel() akan mengoverride id tersebut. Normalnya tidak diperlukan kecuali Provider memang sengaja membangun Panel dengan id berbeda.
Hindari me-resolve service request-scoped di dalam panel(). Method ini berjalan saat provider boot, sebelum binding request-scoped digunakan untuk request tertentu. Konfigurasi Panel dibangun sebagai configuration yang digunakan oleh request berikutnya.
Logic yang bergantung pada user atau request sebaiknya ditempatkan di bootUsing().
Registration
PandaPanel\PandaPanelServiceProvider::boot() membaca daftar Provider dari config, melewati entry yang bukan class-string<PanelProvider> yang dapat di-resolve, lalu mendaftarkan sisanya:
foreach ($this->configuredPanels() as $provider) {
if (! $manager->has((new $provider)->panelId())) {
$manager->registerProvider($provider);
}
}2
3
4
5
Dua konsekuensi penting:
- Panel yang dicantumkan dua kali tetap hanya diregistrasikan satu kali. Menjalankan registration dua kali hanya akan mengulang discovery tanpa mengubah hasil.
- Nama class yang sudah tidak dapat di-resolve dilewati daripada menyebabkan fatal error langsung saat boot. Fatal error pada fase boot terjadi sebelum route mana pun tersedia.
php artisan panel:cachemembantu membuat daftar konfigurasi tersebut terlihat saat dijalankan melalui console.
firstAccessibleTo() berjalan melalui PanelRegistry::all(), yang diurutkan berdasarkan Panel id, bukan berdasarkan urutan pada config. Karena itu tujuan default user ditentukan oleh id, bukan posisi Provider di dalam array config.
Mendaftarkan Panel dari Kode Anda Sendiri
Package atau test dapat mendaftarkan Panel tanpa mengedit config:
use PandaPanel\Core\Panel;
use PandaPanel\Facades\PandaPanel;
// From a provider class:
PandaPanel::registerProvider(App\Panels\Admin\AdminPanelProvider::class);
// From a built panel:
PandaPanel::register(Panel::make('reports')->path('reports'));2
3
4
5
6
7
8
| Method | Signature | Return |
|---|---|---|
registerProvider | registerProvider(string $provider): Panel | Panel yang telah diregistrasikan |
register | register(Panel $panel): Panel | Instance Panel yang sama |
Keduanya melewati PanelRegistry.
Registry menolak:
- duplicate id melalui
PanelRegistrationException::duplicatePanelId(); - duplicate pasangan path/domain melalui
duplicatePanelPath().
Route diregistrasikan pada tahap terpisah oleh PanelRouteRegistrar::registerAll(), setelah seluruh configured Panel selesai diregistrasikan.
Panel yang baru diregistrasikan setelah tahap tersebut — misalnya dari test atau Service Provider lain yang boot lebih lambat — memiliki registry tetapi tidak memiliki route. Lihat Routing.
PanelManager
PandaPanel\Core\PanelManager adalah container singleton dan menjadi entry point utama untuk seluruh operasi terkait Panel.
PandaPanel\Facades\PandaPanel adalah facade untuk manager tersebut.
use PandaPanel\Core\PanelManager;
$manager = app(PanelManager::class);2
3
| Method | Signature | Catatan |
|---|---|---|
registerProvider | registerProvider(string $provider): Panel | Membangun Provider lalu meregistrasikan Panel. |
register | register(Panel $panel): Panel | Meregistrasikan Panel dan membangun registries. |
all | all(): list<Panel> | Diurutkan berdasarkan id. |
has | has(string $id): bool | |
get | get(string $id): Panel | Melempar PanelRegistrationException::unknownPanel(). |
resolveFromRequest | resolveFromRequest(Request $request): ?Panel | Mencocokkan path prefix terpanjang terlebih dahulu dan menghormati domain(). |
firstAccessibleTo | firstAccessibleTo(?Authenticatable $user): ?Panel | Berdasarkan urutan id, sama dengan all(). |
currentPanel | currentPanel(): ?Panel | Didelegasikan ke PanelContext. |
hasCurrentPanel | hasCurrentPanel(): bool | |
setCurrentPanel | setCurrentPanel(?Panel $panel): void | Dipanggil oleh ResolvePanel. |
resources | resources(Panel|string $panel): ResourceRegistry | |
pages | pages(Panel|string $panel): PageRegistry | |
widgets | widgets(Panel|string $panel): WidgetRegistry | |
navigation | navigation(Panel|string $panel): NavigationRegistry |
use Illuminate\Http\Request;
$manager->resolveFromRequest(Request::create('/admin/users/3/edit'))?->getId(); // 'admin'
$manager->resolveFromRequest(Request::create('/dashboard')); // null2
3
4
Registries
register() memanggil buildRegistries(), yang menggabungkan:
explicit registration
+
PanelManifest::for()2
3
PanelManifest::for() mengembalikan cached class list jika manifest tersedia, atau hasil discovery jika tidak.
Class yang muncul melalui kedua jalur hanya diregistrasikan sekali karena masing-masing registry menggunakan slug atau id sebagai key.
Resource configuration diregistrasikan lebih dahulu, sehingga class yang sudah memiliki konfigurasi khusus pada Panel tidak ikut diregistrasikan kembali menggunakan default slug.
ResourceRegistry
Registry Resource menggunakan slug sebagai key.
Effective slug dimiliki registry, bukan class. Artinya class yang sama dapat muncul di dua Panel dengan slug berbeda.
use App\Panels\Admin\Resources\Users\UserResource;
$resources = $manager->resources('admin');
$resources->all(); // list<class-string>, sorted by class name
$resources->slugs(); // list<string>
$resources->bySlug('users'); // class-string|null
$resources->has('users'); // bool
$resources->contains(UserResource::class); // bool
$resources->slugFor(UserResource::class); // 'users'
$resources->configurationFor(UserResource::class); // ResourceConfiguration|null
$resources->count(); // int2
3
4
5
6
7
8
9
10
11
12
register(string|ResourceConfiguration $resource): void melempar duplicateResourceSlug() ketika:
- dua class mengklaim slug yang sama; atau
- satu class diregistrasikan dua kali dengan slug berbeda.
slugFor() untuk class yang tidak dimiliki Panel melakukan fallback ke defaultSlug() milik class tersebut, atau '' jika class bukan subclass Resource.
PageRegistry
Page Registry juga menggunakan slug sebagai key.
Validation dilakukan terhadap Resource Registry juga agar Page tidak dapat menimpa route Resource dalam Panel yang sama.
$pages = $manager->pages('admin');
$pages->all(); // list<class-string>, sorted
$pages->bySlug('settings'); // class-string|null
$pages->has('settings'); // bool
$pages->count(); // int2
3
4
5
6
register(string $page): void melempar:
duplicatePageSlug()untuk dua Page dengan slug sama;slugCollidesWithResource()jika slug sudah digunakan Resource.
WidgetRegistry
Widget Registry menggunakan Widget id sebagai key.
Default Widget id berasal dari Widget::id(), yaitu class basename yang diubah ke kebab-case kecuali Widget menentukan id sendiri.
$widgets = $manager->widgets('admin');
$widgets->all(); // list<class-string>, sorted
$widgets->byId('user-stats');
$widgets->has('user-stats');
$widgets->count();2
3
4
5
6
register(string $widget): void melempar duplicateWidgetId() jika dua Widget menggunakan id yang sama.
NavigationRegistry
Registry ini mengelola urutan sidebar group untuk satu Panel.
Group yang dideklarasikan Panel mempertahankan declaration order.
Group yang hanya muncul karena suatu Resource/Page mereferensikannya ditambahkan kemudian secara alphabetical. Dengan demikian group yang tidak dideklarasikan tidak akan mengubah urutan sidebar hanya karena filesystem discovery order berubah.
$navigation = $manager->navigation('admin');
$navigation->declaredGroups(); // list<string>, in declaration order
$navigation->isDeclared('System'); // bool
$navigation->isCollapsible('System'); // bool — false for the null group
$navigation->sortFor(null); // -1, the ungrouped bucket
$navigation->sortFor('System'); // 0-based among declared groups
$navigation->sortFor('Reports', ['Reports', 'Audit']); // 1000 + alphabetical position2
3
4
5
6
7
8
collapsible(bool $collapsible): self mengikuti sidebar(collapsible:) milik Panel dan diatur secara otomatis ketika registry dibangun.
Mematikan Sebagian Proses Registration
config/panda-panel.php menyediakan tiga switch yang memengaruhi apa yang terjadi saat Provider diregistrasikan:
| Key | Default | Jika false |
|---|---|---|
register_routes | true | Route group untuk semua Panel tidak diregistrasikan. Registry tetap dibangun. |
register_web_middleware | true | Empat middleware web PandaBear tidak ditambahkan; Anda harus mendaftarkannya sendiri di bootstrap/app.php. |
register_guest_redirect | true | Authenticate::redirectUsing() tidak diubah. |
Daftar Panel sendiri tetap selalu dibaca.
Test harness yang ingin membangun Panel tanpa HTTP routing dapat menonaktifkan register_routes.
Catatan
panel()pada Provider dipanggil satu kali per Panel per process. Di bawah Octane, konfigurasi tersebut tidak dibangun ulang setiap request. Perlakukan object Panel sebagai immutable configuration setelah registration.- Discovery berjalan saat
register()kecuali manifest sudah tersedia. Pada cold boot tanpa manifest, discovery path setiap Panel dipindai sebelum request pertama selesai me-resolve route. Karena itu production sebaiknya menjalankanpanel:cache. PanelRegistrationExceptionadalahRuntimeException. Framework tidak menangkapnya; duplicate slug memang sengaja menggagalkan boot daripada menghasilkan Panel yang hanya setengah terdaftar.- Provider class tidak pernah diserialisasi ke cache.
panel:cachehanya menyimpan nama class Resource, Page, dan Widget per Panel id.