Konsep Plugin
Plugin adalah kumpulan konfigurasi Panel yang dapat digunakan kembali dan dapat dikonfigurasi. Plugin melakukan tepat apa yang dapat dilakukan oleh panel provider, tidak lebih. Batas ini sengaja dibuat agar plugin tidak memiliki kemampuan yang bahkan tidak dimiliki Panel. Gunakan plugin ketika kumpulan resource, page, widget, atau dekorasi shell yang sama perlu dipasang pada lebih dari satu Panel, atau ketika kumpulan fitur tersebut dikirim sebagai package tersendiri.
Contoh minimal yang berfungsi
<?php
namespace App\Panels\Plugins;
use App\Panels\Admin\Resources\Reports\ReportResource;
use PandaPanel\Core\Panel;
use PandaPanel\Plugins\Plugin;
final class ReportingPlugin extends Plugin
{
public static function make(): self
{
return new self;
}
public function register(Panel $panel): void
{
$panel
->navigationGroups(['Insights'])
->resources([ReportResource::class]);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Pasang plugin pada sebuah Panel:
use App\Panels\Plugins\ReportingPlugin;
use PandaPanel\Core\Panel;
use PandaPanel\Core\PanelProvider;
final class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->path('admin')
->auth()
->plugins([
ReportingPlugin::make(),
]);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Itu sudah merupakan plugin yang lengkap. register() adalah satu-satunya method yang tidak memiliki implementasi default.
Tidak ada configuration surface kedua
Semua hal yang dilakukan plugin dilakukan melalui API publik milik PandaPanel\Core\Panel — method yang sama dengan yang dipanggil oleh panel provider. Tidak ada registry khusus plugin, hook khusus plugin, atau channel metadata yang hanya tersedia untuk plugin.
Batas ini disengaja, bukan kekurangan. Plugin dapat mendaftarkan resource, page, widget, discovery path, navigation group, render hook, CSS hook, asset, color, middleware, dan action configurator karena Panel juga dapat melakukannya. Plugin tidak dapat melakukan sesuatu yang tidak dapat ditulis langsung oleh panel provider. Artinya setiap efek plugin tetap dapat dibaca pada object Panel dan dapat diubah kembali oleh aplikasi. Lihat Panel API Reference untuk seluruh API Panel yang dapat digunakan plugin.
ID
Secara default, Plugin::id() mengambil basename class, menghapus suffix Plugin, lalu mengubahnya menjadi kebab-case:
| Class | ID |
|---|---|
ReportingPlugin | reporting |
AcmeBillingPlugin | acme-billing |
Reporting | reporting |
BillingPlugin | billing |
Override ketika nama class bukan ID yang ingin digunakan:
public function id(): string
{
return 'acme-billing';
}2
3
4
ID harus unik di dalam satu Panel. Jika dua plugin menggunakan ID yang sama, plugins() melempar PandaPanel\Exceptions\PanelRegistrationException sebelum register() milik keduanya dijalankan:
Two plugins claim the id [reporting] in panel [admin]. A plugin id is how a
panel is asked whether it has one, so it has to be unique.2
Tanpa exception tersebut, pertanyaan seperti hasPlugin('reporting') akan memiliki dua jawaban dan masalahnya baru terlihat kemudian, misalnya resource muncul dua kali di sidebar.
Tiga fase
| Fase | Kapan berjalan | Apa yang diletakkan di sana |
|---|---|---|
register(Panel $panel) | di dalam Panel::plugins(), saat Panel sedang dibangun | resource, page, widget, navigation group, discovery path, settings |
boot(Panel $panel) | pada Panel::boot(), per request, setelah access check Panel | apa pun yang membutuhkan container, request, authenticated user, atau URL |
publishes() | tidak pernah otomatis — hanya ketika panel:publish dijalankan | file yang disalin plugin ke aplikasi |
Menukar tanggung jawab dua fase pertama adalah bug plugin yang paling umum dan sering kali tidak langsung terlihat. register() yang melakukan query dapat terlihat baik-baik saja saat development, tetapi menjadi database hit pada setiap request production, termasuk request yang tidak pernah menyentuh Panel. Register dan Boot menjelaskan call site dan jaminan urutannya secara lengkap.
Menanyakan plugin yang dimiliki Panel
Panel menyimpan plugin dengan key berupa ID dan dapat ditanya tentang plugin yang dipasang:
// `panel($id)` throws for an unknown id; its declared return type is still
// nullable, so narrow it once rather than at every call.
$panel = panel('admin');
$panel?->hasPlugin('reporting'); // bool
$panel?->plugin('reporting'); // ?PanelPlugin — the configured instance
$panel?->getPlugins(); // array<string, PanelPlugin>, keyed by id2
3
4
5
6
7
Reverse lookup tersedia pada base class dan biasanya digunakan resource milik plugin untuk membaca konfigurasi plugin sebagaimana dipasang pada Panel:
use App\Panels\Plugins\ReportingPlugin;
$currency = ReportingPlugin::in(panel())?->getCurrency() ?? 'usd';2
3
in() mengembalikan null ketika Panel tidak memiliki plugin tersebut, bukan melempar exception. Resource yang dipakai bersama oleh dua Panel tetapi pluginnya hanya dipasang pada salah satunya adalah konfigurasi yang valid. Lookup dilakukan berdasarkan class, bukan ID, karena membaca id() dengan membuat instance plugin baru tidak selalu mungkin — constructor plugin dapat membutuhkan konfigurasi wajib.
Dapat dikonfigurasi — inilah nilai utamanya
Plugin tanpa konfigurasi sering kali hanya menjadi class yang sebenarnya dapat ditulis langsung oleh aplikasi. Nilai plugin menjadi lebih jelas ketika satu plugin dapat memiliki bentuk berbeda pada dua Panel:
use App\Panels\Plugins\ReportingPlugin;
// The admin panel gets everything.
$admin->plugins([
ReportingPlugin::make()->withCharts()->group('Insights'),
]);
// The customer panel gets the resource and no charts, ungrouped.
$app->plugins([
ReportingPlugin::make()->withCharts(false)->group(null),
]);2
3
4
5
6
7
8
9
10
11
Gunakan fluent setter yang mengembalikan $this, simpan state-nya, lalu baca kembali di register(). Framework tidak mewajibkan make(); new ReportingPlugin bekerja dengan cara yang sama. Constructor yang menerima konfigurasi wajib adalah pilihan yang baik ketika suatu setting tidak boleh opsional.
Apa yang dapat dikirim oleh plugin
| Komponen | Cara memasangnya |
|---|---|
| Resource, page, widget | $panel->resources(), pages(), widgets() di register() |
| Seluruh directory resource/page/widget | $panel->discoverResources(), discoverPages(), discoverWidgets() |
| Navigation group | $panel->navigationGroups() |
| Dekorasi shell | $panel->renderHook(), $panel->cssHooks() |
| Vite entrypoint | $panel->assets() |
| Vue component | publishes(), disalin oleh panel:publish |
| Migration, config, route | Laravel service provider milik package plugin |
Baris terakhir penting: PanelPlugin bukan service provider. Plugin yang membutuhkan migration, config file, event listener, atau route di luar Panel tetap mengirim Illuminate\Support\ServiceProvider biasa dan membiarkan Composer package discovery menemukannya. Object plugin mengonfigurasi Panel; service provider mengonfigurasi aplikasi.
Plugin didaftarkan, bukan ditemukan otomatis
Tidak ada plugin discovery dan tidak ada generator make:panel-plugin. Plugin berada pada Panel karena panel provider secara eksplisit menuliskannya dalam plugins([...]). Resource, page, dan widget boleh ditemukan dari directory; plugin tidak, karena Composer package yang terpasang tidak seharusnya diam-diam menambahkan route dan navigation ke Panel tanpa baris konfigurasi aplikasi yang menyatakan hal tersebut.
Command
php artisan panel:plugins # what is installed, on which panel, at which version
php artisan panel:plugins --panel=admin
php artisan panel:publish # copy plugin assets into the application
php artisan panel:publish reporting
php artisan panel:publish --force2
3
4
5
Keduanya dibahas di Plugin CLI.
Hal yang perlu diperhatikan
register()berjalan pada setiap application boot, baik request masuk ke Panel maupun tidak. Method ini berjalan ketika service provider boot, sehingga request biasa pun ikut membayar pekerjaan di dalamnya. Jangan melakukan query, resolve route, atau membaca current user di sini.- Vue component milik plugin tidak dapat di-resolve langsung dari package plugin. Semua component registry menggunakan
import.meta.globterhadapresources/js/pages/Panels/**milik aplikasi sebagai build-time allowlist. Plugin harus mem-publish component ke tree tersebut. Lihat Plugin Assets dan Component Registries. - Plugin yang dipasang pada dua Panel biasanya menggunakan dua instance. Setiap panel provider memanggil
plugins()dengan object plugin yang dikonfigurasi sendiri, sehingga per-panel state terisolasi secara alami. Static state pada class tidak terisolasi. Memberikan satu instance yang sama ke dua Panel berarti sengaja membagikan state. plugin()mengembalikan contract, bukan class plugin Anda. Return type-nya?PanelPlugin. GunakanYourPlugin::in($panel)jika Anda ingin mendapatkan type plugin sendiri tanpa instance check.- Callback
bootUsing()milik Panel berjalan setelah seluruhboot()plugin, sehingga aplikasi selalu memiliki keputusan terakhir atas plugin yang dipasangnya.