Konfigurasi Resource per Panel
PandaPanel\Resources\ResourceConfiguration memungkinkan satu class Resource diregistrasikan pada satu Panel dengan slug, label, posisi sidebar, atau query yang berbeda. Gunakan fitur ini ketika model yang sama muncul pada dua Panel tetapi memiliki arti yang sedikit berbeda di masing-masing Panel. Tanpanya, satu shared Resource class harus memiliki konfigurasi yang sama di semua tempat atau Anda terpaksa membuat subclass hanya untuk mengganti label.
Kasus minimal
use Illuminate\Database\Eloquent\Builder;
use PandaPanel\Resources\ResourceConfiguration;
$panel->resources([
ResourceConfiguration::for(UserResource::class)
->slug('people')
->pluralLabel('People')
->navigationLabel('Directory')
->navigationGroup('Company')
->modifyQueryUsing(static fn (Builder $query): Builder => $query->where('is_admin', false)),
]);2
3
4
5
6
7
8
9
10
11
UserResource sekarang berada di /admin/users pada Admin Panel dan /directory/people pada Panel ini. Label, group, dan jangkauan record dapat berbeda, sedangkan class Resource-nya sendiri tidak diubah. Pada contoh di atas, Panel kedua bahkan tidak dapat menjangkau administrator record sama sekali.
Tempat konfigurasi dideklarasikan
Panel::resources() menerima class name dan ResourceConfiguration di dalam array yang sama, sehingga satu Panel dapat mencampur keduanya:
use PandaPanel\Core\Panel;
use PandaPanel\Resources\ResourceConfiguration;
public function panel(Panel $panel): Panel
{
return $panel
->path('directory')
->resources([
PostResource::class,
ResourceConfiguration::for(UserResource::class)->slug('people'),
]);
}2
3
4
5
6
7
8
9
10
11
12
Resource hasil discovery diregistrasikan tanpa konfigurasi tambahan. Jika sebuah class juga ditemukan discovery tetapi perlu dikonfigurasi, deklarasikan class tersebut secara eksplisit menggunakan ResourceConfiguration. Class yang sudah dikonfigurasi tidak akan sekaligus mengklaim default slug-nya.
Seluruh method
ResourceConfiguration::for() adalah satu-satunya constructor. Seluruh method lainnya bersifat fluent dan mengembalikan self.
public static function for(string $resource): self;
public function slug(string $slug): self;
public function label(string $label): self;
public function pluralLabel(string $pluralLabel): self;
public function navigationLabel(string $navigationLabel): self;
public function navigationGroup(?string $navigationGroup): self;
public function navigationIcon(?string $navigationIcon): self;
public function navigationSort(int $navigationSort): self;
public function registerNavigation(bool $register = true): self;
public function modifyQueryUsing(Closure $callback): self;2
3
4
5
6
7
8
9
10
11
| Method | Yang di-override | Fallback |
|---|---|---|
slug() | URL segment dan route name | Resource::defaultSlug() |
label() | Singular label | Resource::defaultLabel() |
pluralLabel() | Plural label | Resource::defaultPluralLabel() |
navigationLabel() | Teks pada sidebar | $navigationLabel, lalu plural label |
navigationGroup() | Group pada sidebar | $navigationGroup pada class |
navigationIcon() | Icon pada sidebar | $navigationIcon pada class |
navigationSort() | Urutan di dalam group | $navigationSort pada class |
registerNavigation() | Apakah navigation entry dibuat | $shouldRegisterNavigation pada class |
modifyQueryUsing() | Mempersempit Resource::query() | Tidak ada tambahan narrowing |
Setiap konfigurasi fallback ke value milik class Resource. Karena itu sebuah Panel hanya perlu menyebut hal yang memang berbeda. navigationGroup() dan navigationIcon() menerima null, sehingga Panel dapat menghapus group atau icon yang sebelumnya dideklarasikan class:
ResourceConfiguration::for(UserResource::class)
->navigationGroup(null) // ungrouped here, whatever the class says
->navigationIcon(null);2
3
Perhatikan bahwa navigationGroup() pada ResourceConfiguration hanya menerima string. Class Resource boleh menggunakan backed enum untuk group, tetapi configuration menggunakan resolved string dari enum tersebut.
Mempersempit query
use Illuminate\Database\Eloquent\Builder;
ResourceConfiguration::for(UserResource::class)
->modifyQueryUsing(static fn (Builder $query): Builder => $query->where('is_admin', false));2
3
4
Callback menerima query Resource setelah query() milik Resource sendiri selesai dijalankan, kemudian mengembalikannya dalam bentuk yang lebih sempit. Karena semua pembacaan record melewati query(), narrowing ini berlaku pada list, view page, edit page, delete, bulk operation, Action lookup, dan global search.
Record yang tidak dapat dijangkau Panel ini menghasilkan 404, bukan hanya row yang tersembunyi dari list.
app(PanelManager::class)->setCurrentPanel($directory);
UserResource::resolveRecord($admin->getKey()); // ModelNotFoundException2
3
Jaminan inilah yang layak diuji. Guessed URL pada Panel yang dipersempit ditolak oleh rule yang sama dengan rule yang memperpendek list.
Menanyakan konfigurasi berdasarkan Panel
Empat static method pada Resource menjawab berdasarkan Panel, bukan hanya berdasarkan class:
use PandaPanel\Core\Panel;
public static function slug(): string; // the current panel's
public static function slugIn(?Panel $panel): string; // a named panel's
public static function label(): string;
public static function pluralLabel(): string;
public static function configurationIn(?Panel $panel): ?ResourceConfiguration;2
3
4
5
6
7
UserResource::slugIn(panel('admin')); // 'users'
UserResource::slugIn(panel('directory')); // 'people'
UserResource::slug(); // whichever panel this request is in2
3
Di luar context Panel tidak ada konfigurasi Panel yang dapat ditanyakan. Karena itu class default menjadi jawabannya: configurationIn(null) mengembalikan null dan slug() fallback ke defaultSlug().
Value asli milik class selalu dapat dibaca, apa pun override yang diberikan Panel:
public static function defaultSlug(): string;
public static function defaultLabel(): string;
public static function defaultPluralLabel(): string;2
3
URL lintas Panel
UserResource::url(); // the current panel
UserResource::url(panel: 'admin'); // /admin/users
UserResource::url(panel: $directoryPanel); // /directory/people
UserResource::url('edit', $user, 'admin'); // /admin/users/1/edit2
3
4
Resource::url() selalu dibangun melalui route name, sedangkan route name mengikuti slug efektif milik Panel: panel.directory.resources.people.index. Meminta URL Resource pada Panel yang tidak meregistrasikan Resource tersebut melempar exception. Dengan demikian isolasi antar-Panel dapat dibuktikan, bukan hanya kebetulan terjadi.
UserResource::url(panel: 'app');
// PanelRegistrationException: ... is not registered in the panel [app]2
Kondisi yang ditolak registry
Panel meng-key Resource berdasarkan slug dan akan gagal secara eksplisit daripada memilih salah satu secara diam-diam.
// Two classes on one slug: which one does /shared belong to?
$panel->resources([
ResourceConfiguration::for(UserResource::class)->slug('shared'),
ResourceConfiguration::for(AccountResource::class)->slug('shared'),
]);
// PanelRegistrationException: ... is used by both ...2
3
4
5
6
// One class twice in one panel: which slug would Resource::url() mean?
$panel->resources([
ResourceConfiguration::for(UserResource::class)->slug('staff'),
UserResource::class,
]);
// One registration survives — the configured one. Slugs: ['staff']2
3
4
5
6
Kasus kedua sengaja bukan error, tetapi juga tidak menghasilkan dua registration. Class yang dikonfigurasi tidak sekaligus mengklaim default slug. Dengan demikian Panel yang mengonfigurasi sebuah class dan juga membiarkan discovery menemukannya tetap memiliki tepat satu entry.
Jika satu model memang perlu muncul dua kali dalam Panel yang sama — misalnya "Active users" dan "Archived users" — buat dua class Resource yang menggunakan model yang sama. Meregisrasikan class yang sama dua kali akan membuat Resource::url() tidak memiliki cara untuk menentukan registration mana yang dimaksud.
Catatan penting
- Registry-lah yang memiliki effective slug, bukan class. Route registration menanyakan registry karena saat application boot belum ada current Panel yang dapat ditanyakan pada Resource class.
- Route name berubah bersama slug. Contohnya
panel.directory.resources.people.index, bukan...users.... Code yang hardcode route name untuk class yang memiliki slug berbeda pada dua Panel akan rusak pada salah satunya;Resource::url(panel: ...)tetap aman. modifyQueryUsing()dikombinasikan denganquery()milik Resource, bukan menggantikannya. Namun Resource yang meng-overridequery()dan lupa memanggilparent::query()dapat secara diam-diam membuang narrowing milik Panel.- Callback berjalan untuk setiap query dan setiap request. Gunakan untuk constraint query; pekerjaan yang mahal akan dibayar pada setiap pembacaan record.
- Konfigurasi bersifat per Panel, bukan per user. Rule yang bergantung pada siapa user-nya harus berada di
query()atau policy. ResourceConfigurationbersifatfinal. Class ini hanya membawa konfigurasi field yang tersedia; behavior lain seharusnya berada pada Resource class.