Register dan Boot
Plugin memiliki dua fase. Menentukan pekerjaan harus diletakkan di fase mana adalah salah satu sumber bug plugin yang paling umum. Gunakan halaman ini ketika Anda menentukan tempat suatu kode, atau ketika plugin bekerja normal di development tetapi berperilaku aneh di production, Octane, atau ketika beberapa request dijalankan dalam satu test.
Contoh minimal yang berfungsi
<?php
declare(strict_types=1);
namespace App\Panels\Plugins;
use App\Panels\Admin\Resources\Reports\ReportResource;
use PandaPanel\Core\Panel;
use PandaPanel\Enums\RenderHook;
use PandaPanel\Plugins\Plugin;
final class ReportingPlugin extends Plugin
{
public function register(Panel $panel): void
{
// Configuration. No request exists yet.
$panel->resources([ReportResource::class]);
}
public function boot(Panel $panel): void
{
// A request exists, the user is known, routes are registered.
$panel->renderHook(RenderHook::PageStart, 'Panels/Admin/Hooks/ReportBanner');
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$panel->plugins([new ReportingPlugin]);Dua fase
register(Panel $panel) | boot(Panel $panel) | |
|---|---|---|
| Dipanggil dari | Panel::plugins() | Panel::boot() |
| Dipicu oleh | panel provider, saat application boot | middleware ResolvePanel, per request |
| Berjalan untuk | setiap request yang dilayani aplikasi | hanya request yang mencapai Panel tersebut |
| Frekuensi | sekali per application boot | sekali per matching request |
| Container tersedia | belum siap sepenuhnya | ya |
$request->user() | tidak | ya |
route() / URL | tidak | ya |
| Database | jangan digunakan | boleh |
Fase 1: register()
Panel::plugins() melakukan empat langkah untuk setiap plugin, secara berurutan:
- membaca
id()dan melemparPandaPanel\Exceptions\PanelRegistrationExceptionjika ID sudah digunakan plugin lain; - memanggil
PluginCompatibility::assert(), membacametadata(), lalu melempar exception jikarequiresPaneltidak terpenuhi; - menyimpan plugin menggunakan ID sebagai key;
- memanggil
register($panel).
Compatibility check ditempatkan sebelum register() karena itulah momen terakhir sebelum plugin mengubah Panel dan momen paling awal saat jawaban compatibility sudah diketahui.
Plugin diproses sesuai urutan array. Plugin berikutnya dapat melihat konfigurasi yang sudah diregistrasikan plugin sebelumnya. Bergantung pada perilaku ini membuat urutan plugins([...]) menjadi load-bearing dan sebaiknya dihindari jika tidak benar-benar diperlukan.
$panel->plugins([new ReportingPlugin]); // register() has already run
$panel->getResources(); // contains ReportResource::class2
Posisi fase ini dalam application lifecycle
PandaPanelServiceProvider::boot() membangun seluruh Panel yang dikonfigurasi:
provider boot
└── PanelManager::registerProvider(AdminPanelProvider::class)
└── AdminPanelProvider::build()
└── AdminPanelProvider::panel(Panel::make('admin'))
└── Panel::plugins([...])
├── PluginCompatibility::assert()
└── ReportingPlugin::register($panel)
└── PanelManager::register($panel)
└── buildRegistries($panel) ← resources, pages, widgets, navigation
└── PanelRouteRegistrar::registerAll() ← routes2
3
4
5
6
7
8
9
10
Dua konsekuensi dari urutan tersebut bersifat mutlak:
- Semua hal yang ingin diregistrasikan plugin harus dilakukan di
register(). Registry resource, page, widget, dan navigation dibangun segera setelah provider selesai, kemudian route diregistrasikan.$panel->resources([...])yang dipanggil dariboot()memang mengubah object Panel tetapi tidak mencapai registry, route, maupun navigation. Tidak ada error; fitur hanya tidak muncul. register()berjalan saat service provider boot untuk setiap request. Bahkan request favicon ikut membayar pekerjaan di dalamnya. Jangan melakukan query, resolve route, atau membaca current user. Belum ada user pada fase tersebut, dan query yang dipaksakan akan menjadi database hit pada setiap asset request di production.
Fase 2: boot()
ResolvePanel merupakan middleware terakhir dalam route group Panel:
// PandaPanel\Http\Middleware\ResolvePanel
$this->manager->setCurrentPanel($panel);
abort_unless($panel->isAccessibleTo($request->user()), 403);
// After the access check, never before: a user who is refused the
// panel must not be able to trigger its boot work.
$panel->boot();2
3
4
5
6
7
8
Ketika boot() dipanggil, Panel sudah menjadi current Panel, helper panel() dapat menjawabnya, user sudah authenticated dan authorized untuk Panel, serta route sudah terdaftar.
Panel::boot() menjalankan plugin terlebih dahulu lalu callback milik Panel:
public function boot(): void
{
foreach ($this->plugins as $plugin) {
$plugin->boot($this);
}
foreach ($this->bootCallbacks as $callback) {
$callback($this);
}
}2
3
4
5
6
7
8
9
10
Urutan ini menjadi jaminan penting: callback bootUsing() milik aplikasi mendapat keputusan terakhir dan dapat mengubah kembali apa yang dilakukan plugin.
use PandaPanel\Core\Panel;
$panel
->plugins([new ReportingPlugin])
->bootUsing(static function (Panel $panel): void {
// Runs after every plugin's boot(). This wins.
$panel->cssHooks(['page' => 'no-report-banner']);
});2
3
4
5
6
7
8
Apa yang masih dapat diubah secara efektif pada setiap fase
| Method Panel | register() | boot() |
|---|---|---|
resources(), pages(), widgets() | ya | tidak berpengaruh — registry sudah dibangun |
discoverResources(), discoverPages(), discoverWidgets() | ya | tidak berpengaruh — discovery sudah selesai |
navigationGroups() | ya | secara efektif tidak — urutan group sudah ditetapkan pada registry saat registration; hanya parent mapping yang dibaca ulang per request |
renderHook() | ya | ya |
cssHooks(), colors() | ya | ya |
assets() | ya | ya |
userMenuItems() | ya | ya |
configureActions() | ya | ya |
brandName(), brandLogo(), favicon(), icon() | ya | ya |
middleware(), authMiddleware() | ya | tidak berpengaruh — route sudah diregistrasikan |
bootUsing() | ya | terlambat untuk request saat ini |
Aturan di balik tabel: nilai yang hanya dibaca sekali saat registration sudah final ketika boot() berjalan. Nilai yang dibaca per request dari object Panel masih dapat diubah pada boot().
boot() berjalan per request, sehingga harus idempotent
Panel::boot() tidak memiliki once-guard. Pada model PHP request klasik hal ini tidak menjadi masalah karena container dan object Panel dibangun ulang untuk setiap request. Namun pada Octane, queue worker, atau test yang menjalankan beberapa request, instance Panel yang sama dapat bertahan dan boot() dipanggil kembali.
Method Panel memiliki perilaku berbeda ketika dipanggil berulang:
| Method | Pemanggilan berulang |
|---|---|
resources(), pages(), widgets(), navigationGroups(), discover*() | deduplicated — array_unique saat merge |
renderHook() | menambahkan — component dapat terinject dua kali |
cssHooks() | menambahkan — string class terus bertambah setiap request |
userMenuItems() | menambahkan — menu entry dapat berulang |
brandName(), favicon(), colors(), configureActions() | overwrite, sehingga aman |
Karena itu boot() yang melakukan append perlu guard:
use PandaPanel\Core\Panel;
use PandaPanel\Enums\RenderHook;
private bool $booted = false;
public function boot(Panel $panel): void
{
if ($this->booted) {
return;
}
$this->booted = true;
$panel->renderHook(RenderHook::PageStart, 'Panels/Admin/Hooks/ReportBanner');
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Flag berada pada instance plugin dan instance plugin bersifat per Panel, sehingga dua Panel yang menggunakan class plugin sama tetap boot secara independen.
Guard tersebut tidak tepat jika pekerjaan bergantung pada user atau request saat ini. Hook per-user memang harus dievaluasi per request. Dalam kondisi seperti itu jangan memakai once-guard; gunakan operasi yang idempotent. cssHooks() yang append satu class per request akan terus tumbuh, sedangkan configureActions() yang mengganti closure per request tetap stabil.
Gagal secara eksplisit
Kedua fase melempar exception daripada melakukan silent degradation. PanelRegistrationException meng-extend RuntimeException dan sengaja fatal: ini developer error yang harus gagal pada application boot dan panel:cache, bukan berubah menjadi Panel yang setengah bekerja.
Exception dari register() memutus application boot, termasuk route yang mungkin digunakan untuk menunjukkan error. Exception dari boot() hanya memutus request yang masuk ke Panel tersebut.
Caching
panel:cache menulis manifest class yang dimiliki setiap Panel — hanya nama class resource, page, dan widget. Object plugin tidak pernah diserialisasi. Konsekuensinya:
register()tetap berjalan setiap application boot, baik manifest tersedia maupun tidak. Manifest menggantikan filesystem scan, bukan panel provider.- Discovery path yang ditambahkan plugin dipindai ketika manifest dibangun dan hasilnya di-cache seperti discovery biasa. Directory resource plugin tidak menimbulkan filesystem scan per request di production.
- Perubahan pada class yang diregistrasikan plugin membutuhkan rebuild manifest menggunakan
php artisan panel:cacheatauoptimize, yang sudah mencakup command tersebut.
Hal yang perlu diperhatikan
boot()tidak berjalan untuk user yang ditolak oleh Panel.abort_unlessberada di atasnya. Plugin tidak dapat memakaiboot()sebagai audit log untuk attempted access.boot()tidak berjalan pada non-panel request.ResolvePanelhanya ada di route group Panel. Console command, queue job, dan bagian aplikasi lain tidak melakukan boot Panel. Karena itu konfigurasi yang hanya diregistrasikan diboot()tidak terlihat olehpanel:plugins,panel:publish, maupun Artisan command lain.register()juga berjalan di console.php artisan migratemembangun semua panel provider.register()yang mengakses database dapat mematahkan migration pada fresh install karena table yang dicari belum dibuat.- Resource yang didaftarkan pada
boot()tidak menghasilkan error. Resource bahkan dapat muncul pada$panel->getResources(), tetapi tidak ada di registry, route, atau sidebar. Ketika resource plugin hilang dari navigation, periksa method tempat resource diregistrasikan. - Dua Panel berarti dua pemanggilan
register()pada dua instance. Per-instance state tetap terisolasi. Static state pada class plugin tetap dibagikan, termasuk antar-Panel yang mengonfigurasi plugin secara berbeda.