Referensi Plugins
Plugin adalah kumpulan konfigurasi Panel yang dapat digunakan kembali.
Semua yang dilakukan Plugin tetap melalui public API milik Panel.
Contohnya:
- mendaftarkan Resource;
- mendaftarkan Page;
- mendaftarkan Widget;
- menambahkan asset;
- menambahkan render hook;
- menambahkan navigation group;
- mengubah konfigurasi Panel.
Secara konsep, Plugin melakukan hal yang sama seperti:
PanelProvidertetapi dalam bentuk reusable package atau reusable module.
Tidak ada configuration surface kedua yang khusus untuk Plugin.
Prinsipnya:
Plugin tidak boleh dapat melakukan sesuatu yang bahkan tidak dapat dilakukan Panel melalui public API-nya sendiri.
Pendekatan ini membuat behavior Plugin tetap:
- dapat diprediksi;
- dapat direview;
- dapat diuji;
- dan mengikuti API framework yang sama dengan application.
Namespaces
| Class | Kegunaan |
|---|---|
PandaPanel\Contracts\PanelPlugin | Contract utama yang digunakan framework untuk me-resolve Plugin |
PandaPanel\Plugins\Plugin | Base class praktis untuk membuat Plugin |
PandaPanel\Plugins\PluginMetadata | Metadata yang menjelaskan Plugin |
PandaPanel\Plugins\PluginCompatibility | Melakukan pemeriksaan requiresPanel |
Contoh Plugin Lengkap
<?php
namespace App\Panels\Plugins;
use PandaPanel\Core\Panel;
use PandaPanel\Plugins\Plugin;
final class ReportingPlugin extends Plugin
{
private ?string $group =
'Reporting';
public static function make(): self
{
return new self;
}
public function group(
?string $group
): self {
$this->group = $group;
return $this;
}
public function getGroup(): ?string
{
return $this->group;
}
public function register(
Panel $panel
): void {
if (
$this->group !== null
) {
$panel
->navigationGroups([
$this->group,
]);
}
$panel->resources([
RevenueResource::class,
]);
}
public function boot(
Panel $panel
): void {
$panel->cssHooks([
'page' =>
'reporting-page',
]);
}
}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
49
50
51
52
53
54
55
56
Kemudian gunakan pada Panel:
public function panel(
Panel $panel
): Panel {
return $panel
->path('admin')
->plugins([
ReportingPlugin::make()
->group(
'Analytics'
),
]);
}2
3
4
5
6
7
8
9
10
11
12
Lihat Plugin yang terpasang:
php artisan panel:pluginsPlugin yang configurable merupakan salah satu tujuan utama desain ini.
Plugin yang sama dapat dipasang pada dua Panel dengan configuration yang berbeda.
Contohnya:
Admin Panel
└── ReportingPlugin
group = Analytics
Operations Panel
└── ReportingPlugin
group = Reports2
3
4
5
6
7
Tidak perlu membuat class Plugin baru hanya karena cara Plugin tersebut ditampilkan berbeda.
Contract PanelPlugin
Namespace:
PandaPanel\ContractsContract:
interface PanelPlugin
{
public function id(): string;
public function register(
Panel $panel
): void;
public function boot(
Panel $panel
): void;
public function metadata():
PluginMetadata;
/**
* @return array<string, string>
* absolute source
* =>
* absolute destination
*/
public function publishes(): array;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Framework tidak bergantung pada concrete class:
PluginSemua lookup dan lifecycle menggunakan:
PanelPluginTermasuk:
- Plugin registration;
- Plugin lookup;
- Plugin boot;
panel:publish.
Karena itu Plugin yang didistribusikan sebagai package independen sebaiknya mengimplementasikan:
PanelPluginsecara langsung atau melalui base class yang sesuai.
Package tidak sebaiknya bergantung pada class yang hanya ada di application tertentu.
Contohnya, package sebaiknya tidak:
extends App\Panels\Plugins\BasePluginkarena package tersebut menjadi tightly coupled terhadap application.
Tiga Fase Plugin
Plugin memiliki tiga lifecycle phase utama:
| Fase | Kapan Dijalankan | Cocok untuk |
|---|---|---|
register() | ketika Panel sedang dikonfigurasi | Resource, Page, Widget, navigation group, settings |
boot() | setelah Panel di-resolve untuk request | logic yang membutuhkan container, user, URL, tenant, atau request context |
publishes() | tidak otomatis; hanya ketika panel:publish dijalankan | file yang ingin disalin ke application |
register()
register() berjalan selama application boot.
Artinya method ini dapat berjalan pada setiap request, termasuk request yang bahkan tidak masuk ke Panel.
Karena itu jangan melakukan pekerjaan mahal di sini.
Contoh yang sebaiknya dihindari:
public function register(
Panel $panel
): void {
$count =
Order::query()->count();
$user =
auth()->user();
$url =
route('something');
// ...
}2
3
4
5
6
7
8
9
10
11
12
13
14
Operation seperti:
- database query;
- membaca authenticated user;
- resolve current URL;
- resolve tenant;
- service request-scoped;
tidak sebaiknya dilakukan di register().
Jika tidak, request untuk:
- asset;
- API lain;
- route non-Panel;
tetap membayar biaya tersebut.
boot()
boot() berjalan setelah Panel di-resolve untuk current request.
Dan hanya berjalan jika request memang mencapai Panel tersebut.
Ini tempat yang lebih tepat untuk logic yang membutuhkan:
- current user;
- tenant;
- container service;
- URL;
- request state.
Contoh:
public function boot(
Panel $panel
): void {
$panel->cssHooks([
'page' =>
auth()->user()?->is_admin
? 'admin-page'
: 'user-page',
]);
}2
3
4
5
6
7
8
9
10
Urutan Boot
Plugin di-boot sebelum callback:
Panel::bootUsing()Secara konsep:
Panel resolved
↓
Plugin boot()
↓
Plugin boot()
↓
Panel bootUsing()2
3
4
5
6
7
Dengan demikian application selalu memiliki kesempatan terakhir untuk mengoverride konfigurasi Plugin.
Prinsipnya:
Plugin memberikan default atau contribution, tetapi application tetap memiliki keputusan akhir.
Kesalahan Umum: register() dan boot() Tertukar
Ini adalah salah satu bug Plugin yang paling umum.
Contohnya register() melakukan query:
public function register(
Panel $panel
): void {
Order::query()->count();
}2
3
4
5
Di development mungkin terlihat tidak bermasalah.
Namun di production, query tersebut dapat terjadi pada:
- setiap request;
- bahkan request asset;
- bahkan request yang tidak menggunakan Panel.
Gunakan boot() jika operation membutuhkan runtime request context.
Contoh Lifecycle
$panel =
Panel::make('plug')
->path('plug')
->plugins([
ReportingPlugin::make(),
]);2
3
4
5
6
Setelah registration:
$panel->getResources();sudah mengandung:
RevenueResource::classkarena:
register()sudah berjalan.
Tetapi:
$panel->getCssHooks();belum memiliki:
pagekarena boot() belum dijalankan.
Setelah:
$panel->boot();maka:
$panel
->getCssHooks()['page'];2
menghasilkan:
reporting-pagePlugin
Base class:
abstract class Plugin
implements PanelPlugin
{
public function id(): string;
public function boot(
Panel $panel
): void;
public function metadata():
PluginMetadata;
public function publishes(): array;
public static function in(
?Panel $panel
): ?static;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Base class menyediakan implementasi untuk empat dari lima method contract.
Method yang tetap abstract:
register()karena Plugin yang tidak mendaftarkan atau mengubah apa pun bukan Plugin yang berguna.
id()
Default Plugin id dibuat dari nama class.
Contoh:
ReportingPluginmenjadi:
reportingSuffix:
Plugindihapus, lalu nama diubah menjadi kebab-style identifier.
Plugin id harus stabil antar-versi.
Contohnya:
reportingsebaiknya tetap sama dari:
v1
v2
v32
3
Application menggunakan id untuk pertanyaan seperti:
$panel->hasPlugin(
'reporting'
);2
3
Id mengidentifikasi Plugin, bukan release version-nya.
Plugin::in()
Method:
Plugin::in()merupakan kebalikan dari:
Panel::plugin()Gunakan ketika Resource/Page milik Plugin ingin membaca konfigurasi instance Plugin yang dipasang pada current Panel.
Contoh:
$currency =
BillingPlugin::in(
panel()
)?->currency()
?? 'usd';2
3
4
5
Jika Panel tersebut tidak memiliki Plugin:
nulldikembalikan.
Bukan exception.
Hal tersebut merupakan kondisi normal.
Misalnya satu Resource digunakan pada:
Admin Panel
→ BillingPlugin tersedia
Operations Panel
→ BillingPlugin tidak tersedia2
3
4
5
Resource yang shared tidak seharusnya gagal hanya karena salah satu Panel tidak memasang Plugin.
Mengapa Lookup Berdasarkan Class
in() mencari Plugin berdasarkan class.
Bukan dengan membuat instance baru kemudian membaca:
id()Alasannya Plugin dapat memiliki constructor yang membutuhkan configuration.
Contoh:
new BillingPlugin(
currency: 'idr'
);2
3
Framework tidak dapat membuat instance baru hanya untuk bertanya:
Apa id-mu?karena configuration constructor tersebut tidak diketahui.
PluginMetadata
Class:
final readonly class PluginMetadata
{
public function __construct(
public string $name,
public ?string $package = null,
public ?string $requiresPanel = null,
public ?string $url = null,
);
public function version():
?string;
public function toArray(): array;
// {
// name,
// package,
// version,
// requiresPanel,
// url
// }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Contoh:
public function metadata():
PluginMetadata
{
return new PluginMetadata(
name: 'Billing',
package:
'acme/panda-billing',
requiresPanel:
'^1.2',
url:
'https://example.com/docs/billing',
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Plugin Version Tidak Ditulis Manual
Method:
version()membaca version dari Composer installed-package metadata.
Plugin tidak perlu menulis:
version: '1.4.1'secara manual.
Alasannya manual version string mudah terlupakan.
Contoh masalah:
Composer installed:
1.4.1
Metadata manual:
1.2.02
3
4
5
Informasi seperti itu lebih buruk daripada tidak memberikan version sama sekali.
Plugin Tanpa Package
Jika:
package = nullmaka:
version()mengembalikan:
nullIni benar untuk Plugin yang hidup langsung di application.
Plugin tersebut mengikuti version project, bukan package tersendiri.
Jika package name diberikan tetapi Composer tidak mengenalnya:
version()juga menghasilkan:
nullKesalahan package name di metadata dianggap documentation/configuration issue.
Bukan alasan untuk menggagalkan seluruh application boot.
PluginCompatibility
API:
public static function assert(
PanelPlugin $plugin,
string $panelId,
?string $installed = null
): void;2
3
4
5
Compatibility diperiksa oleh:
Panel::plugins()sebelum:
register()dijalankan.
Urutannya:
Plugin
↓
Read metadata
↓
Compatibility check
↓
Compatible?
├── no
│ ↓
│ exception
│
└── yes
↓
store Plugin
↓
register()2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dengan begitu Plugin yang incompatible tidak pernah sempat mengubah Panel.
requiresPanel
Plugin dapat mendeklarasikan:
requiresPanel:
'^2.0'2
Jika version PandaBear yang terinstall tidak memenuhi constraint, framework melempar:
PanelRegistrationException::incompatiblePlugin()Contoh:
The [Billing] plugin ([billing], registered on the [admin] panel) requires
panda-panel ^2.0, and 1.4.1 is installed. Upgrade the plugin, or pin this
framework to a version it supports.2
3
Pesan tersebut lebih informatif dibanding kegagalan seperti:
Call to undefined method Panel::whatever()yang baru muncul ketika Plugin memanggil API framework yang sudah berubah.
Mengapa Compatibility Check Penting
Tanpa compatibility check:
Plugin membutuhkan PandaBear 2.x
↓
Application menggunakan 1.x
↓
Plugin register()
↓
memanggil method baru
↓
undefined method2
3
4
5
6
7
8
9
Developer hanya melihat framework error.
Tidak jelas Plugin mana yang menyebabkan masalah.
Dengan compatibility check:
Plugin name
Plugin id
Panel id
required version
installed version2
3
4
5
semuanya tersedia dalam error.
Kondisi Compatibility Check Dilewati
Pemeriksaan dilewati jika:
- Plugin tidak mendeklarasikan constraint;
- PandaBear tidak diinstall sebagai Composer package;
- installed version merupakan branch alias seperti:
dev-main;no-version-set.
Kondisi tersebut berarti framework tidak memiliki version stabil yang dapat dibandingkan secara bermakna.
Testing Compatibility
Anda dapat memberikan installed version secara manual.
Contoh:
PluginCompatibility::assert(
$plugin,
'admin',
installed: '1.4.1'
);2
3
4
5
Dengan begitu test tidak bergantung pada version PandaBear yang benar-benar sedang terinstall di environment test.
Plugin Registration API
public function plugins(
array $plugins
): self;
// array<array-key, PanelPlugin>
public function getPlugins(): array;
// array<string, PanelPlugin>
public function hasPlugin(
string $id
): bool;
public function plugin(
string $id
): ?PanelPlugin;2
3
4
5
6
7
8
9
10
11
12
13
14
15
Urutan plugins()
Untuk setiap Plugin dalam array:
1. assert compatibility
2. store Plugin berdasarkan id
3. register()2
3
Urutan tersebut dilakukan satu per satu.
Duplicate Plugin ID
Jika dua Plugin memiliki id yang sama:
PanelRegistrationException::duplicatePlugin()dilempar.
Contoh:
Plugin A
id = billing
Plugin B
id = billing2
3
4
5
Tidak boleh ada dua jawaban untuk:
$panel->plugin(
'billing'
);2
3
Karena application juga menggunakan:
hasPlugin('billing')untuk menentukan feature availability.
Duplicate ditolak saat registration, bukan dibiarkan muncul sebagai Resource atau Page ganda.
Membaca Plugin dari Panel
Contoh:
$panel->hasPlugin(
'reporting'
);
// true2
3
4
Mendapatkan instance:
$panel->plugin(
'reporting'
);2
3
Membaca configuration instance:
ReportingPlugin::in(
$panel
)?->getGroup();2
3
Hasilnya adalah configuration yang benar-benar digunakan ketika Plugin tersebut dipasang.
Publishing Assets
Plugin package dapat membawa Vue component sendiri.
Namun component registry PandaBear menggunakan:
import.meta.globterhadap application tree.
Contohnya:
resources/js/pages/**Ini merupakan build-time allowlist.
Component yang masih berada di:
vendor/acme/plugin/resources/...tidak otomatis ikut masuk registry application.
Karena itu Plugin harus publish component ke application tree.
Mengapa Asset Dipublish
Setelah dipublish, component menjadi bagian nyata dari application:
repository
build
application tree2
3
Developer dapat:
- melihat source;
- mengedit;
- debug;
- customize.
Ini merupakan design feature, bukan workaround.
Prinsipnya:
Component yang tidak dapat dilihat source-nya juga lebih sulit di-debug dan di-customize.
publishes()
Contoh:
public function publishes(): array
{
return [
__DIR__
. '/../resources/js/Widgets'
=>
resource_path(
'js/pages/Panels/Admin/Widgets'
),
__DIR__
. '/../resources/js/Fields/Signature.vue'
=>
resource_path(
'js/pages/Panels/Admin/Fields/Signature.vue'
),
];
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Mapping:
absolute source
→
absolute destination2
3
Source dapat berupa:
- directory;
- single file.
Publish Semua Plugin
php artisan panel:publishCommand tersebut mempublish asset dari seluruh Plugin pada seluruh Panel.
Publish Satu Plugin
php artisan panel:publish reportingPlugin dipilih berdasarkan id:
reportingForce Overwrite
php artisan panel:publish --forceTanpa --force, existing file tidak ditimpa.
Dengan --force, file yang sudah ada dapat dioverwrite.
Source yang Tidak Ada
Jika source path pada:
publishes()tidak ditemukan, command memberikan warning.
Command tidak langsung gagal total.
Hal ini memungkinkan satu missing optional asset dilaporkan tanpa membuat seluruh publishing process berhenti.
panel:plugins
Lihat Plugin yang terpasang:
php artisan panel:pluginsFilter berdasarkan Panel:
php artisan panel:plugins \
--panel=admin2
Contoh output:
Panel ID Name Package Version Requires
admin reporting Reporting in this application unknown any
admin billing Billing acme/panda-billing 1.4.1 ^1.22
3
Command ini sangat berguna untuk bug report karena langsung menunjukkan:
- Panel;
- Plugin id;
- nama Plugin;
- package;
- version;
- compatibility constraint.
Plugin Application vs Package
Plugin tanpa Composer package ditampilkan sebagai:
in this applicationPlugin yang menyebut package tetapi Composer tidak dapat menemukan package tersebut ditampilkan:
unknownKeduanya sengaja dibedakan.
Karena:
Plugin lokal applicationdan:
Metadata package salahadalah dua kondisi berbeda.
Testing Plugin
Plugin dapat diuji tanpa HTTP request.
Contoh:
use PandaPanel\Core\Panel;
it(
'registers its resource',
function (): void {
$panel =
Panel::make('plug')
->path('plug')
->plugins([
ReportingPlugin::make(),
]);
expect(
$panel->getResources()
)
->toContain(
RevenueResource::class
)
->and(
$panel
->getNavigationGroups()
)
->toContain(
'Reporting'
);
}
);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
Testing Configuration
Contoh:
it(
'is configurable',
function (): void {
$panel =
Panel::make(
'plug-bare'
)
->path(
'plug-bare'
)
->plugins([
ReportingPlugin::make()
->group(null),
]);
expect(
$panel
->getNavigationGroups()
)
->not
->toContain(
'Reporting'
);
}
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Testing register() Tanpa HTTP
Panel yang dibuat melalui:
Panel::make()tidak perlu:
- HTTP request;
- route;
- Panel registration global.
Karena itu register() Plugin dapat diuji secara terisolasi.
Untuk menguji fase kedua:
$panel->boot();Contoh Testing Lifecycle
$panel =
Panel::make('plug')
->path('plug')
->plugins([
ReportingPlugin::make(),
]);2
3
4
5
6
Pada titik ini:
register()
→ sudah berjalan
boot()
→ belum2
3
4
5
Assert:
expect(
$panel->getResources()
)->toContain(
RevenueResource::class
);2
3
4
5
Kemudian:
$panel->boot();baru assert konfigurasi boot():
expect(
$panel
->getCssHooks()['page']
)->toBe(
'reporting-page'
);2
3
4
5
6
Hal yang Perlu Diperhatikan
Plugin Tidak Dapat Melakukan Lebih dari Panel
Ini bukan keterbatasan.
Ini adalah design boundary.
Plugin hanya menggunakan public API Panel.
Contohnya:
Panel::resources()
Panel::pages()
Panel::widgets()
Panel::assets()
Panel::renderHook()
Panel::navigationGroups()2
3
4
5
6
Jika suatu behavior tidak tersedia pada Panel API, Plugin juga tidak memiliki hidden API khusus untuk melakukannya.
Keuntungannya:
- behavior Plugin dapat diaudit;
- documentation surface tetap satu;
- tidak ada secret Plugin API;
- application dapat mereplikasi behavior Plugin secara manual jika dibutuhkan.
register() Berjalan pada Setiap Request
register() merupakan bagian dari application boot.
Karena itu:
Jangan menaruh operation mahal di
register().
Contoh yang buruk:
public function register(
Panel $panel
): void {
ExternalApi::fetch();
Order::query()->count();
auth()->user();
}2
3
4
5
6
7
8
9
Gunakan boot() untuk operation request-sensitive.
Plugin Boot Sebelum Panel Callback
Urutan:
Plugin boot
↓
Panel bootUsing2
3
Application dapat mengoverride perubahan Plugin.
Contohnya Plugin:
public function boot(
Panel $panel
): void {
$panel->cssHooks([
'page' => 'plugin-page',
]);
}2
3
4
5
6
7
Kemudian application:
$panel->bootUsing(
static function (
Panel $panel
): void {
$panel->cssHooks([
'page' =>
'application-page',
]);
}
);2
3
4
5
6
7
8
9
10
Final configuration dapat mengikuti application.
Compatibility Sebelum Registration
Plugin yang incompatible tidak pernah mencapai:
register()Artinya tidak mungkin terjadi kondisi:
Plugin mengubah setengah konfigurasi
↓
baru compatibility gagal2
3
Framework memastikan compatibility terlebih dahulu.
Plugin ID adalah Stable Identifier
Jangan mengubah:
reportingmenjadi:
reporting-v2hanya karena major release berubah.
Plugin id bukan version.
Version tersedia melalui metadata/Composer.
Id digunakan oleh application untuk:
hasPlugin()
plugin()
Plugin::in()2
3
4
5
dan harus stabil.
publishes() Tidak Pernah Berjalan Otomatis
Plugin installation tidak diam-diam menyalin file ke application.
File hanya disalin ketika developer menjalankan:
php artisan panel:publishIni disengaja.
Application harus mengetahui kapan source tree-nya diubah.
Ringkasan Plugin Lifecycle
Application boot
↓
PanelProvider
↓
Panel::plugins()
↓
PluginCompatibility::assert()
↓
compatible?
├── no
│ ↓
│ PanelRegistrationException
│
└── yes
↓
Store Plugin by id
↓
Plugin::register()
↓
Resources
Pages
Widgets
Navigation
Settings
──────── REQUEST MASUK PANEL ────────
ResolvePanel
↓
Panel::boot()
↓
Plugin::boot()
↓
Panel::bootUsing()
↓
Controller/Page2
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
Ringkasan register() vs boot()
register()
──────────
application boot
semua request
bahkan non-panel request
Gunakan untuk:
✓ Resource
✓ Page
✓ Widget
✓ navigation
✓ static configuration
Hindari:
✗ database query
✗ current user
✗ current tenant
✗ request URL
✗ expensive I/O
boot()
──────
hanya setelah Panel resolved
Gunakan untuk:
✓ authenticated user
✓ current tenant
✓ request-aware config
✓ runtime container services
✓ URL-aware configuration2
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
Ringkasan Compatibility
Plugin
↓
metadata()
↓
requiresPanel?
↓
installed framework version
↓
Semver comparison
↓
compatible?
├── yes
│ ↓
│ register()
│
└── no
↓
incompatiblePlugin()2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Ringkasan Plugin Lookup
Panel
├── reporting
│ └── ReportingPlugin instance
│
├── billing
│ └── BillingPlugin instance
│
└── audit
└── AuditPlugin instance2
3
4
5
6
7
8
9
Lookup:
$panel->hasPlugin(
'billing'
);2
3
atau:
$panel->plugin(
'billing'
);2
3
Dari class Plugin:
BillingPlugin::in(
$panel
);2
3
Ringkasan Publishing Assets
Plugin Package
└── resources/js
├── Widgets
└── Fields
↓
publishes()
↓
php artisan panel:publish
↓
Application
└── resources/js/pages/Panels/...
↓
Vite build
↓
import.meta.glob registry
↓
Vue component tersedia2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Prinsip Utama Plugins
PandaBear memperlakukan Plugin sebagai reusable Panel configuration, bukan framework di dalam framework.
Plugin:
tidak memiliki route system sendiri
tidak memiliki registry sendiri
tidak memiliki hidden configuration API
tidak memiliki frontend loader khusus2
3
4
Sebaliknya Plugin menggunakan:
Panel Public API
↓
Resources
Pages
Widgets
Navigation
Assets
Hooks
Settings2
3
4
5
6
7
8
9
Dengan desain tersebut:
Plugin hanyalah konfigurasi Panel yang dapat digunakan kembali, dikemas dalam sebuah object yang memiliki lifecycle, metadata, compatibility check, dan optional publishable assets.
Prinsip lainnya:
register()digunakan untuk deklarasi yang stabil,boot()digunakan untuk runtime context, dan application selalu memiliki keputusan terakhir atas Plugin yang dipasangnya.