Membuat Resource
Resource adalah satu model Eloquent yang dipresentasikan di dalam satu Panel: table, form, page, dan query yang menjadi jalur pembacaan seluruh bagian tersebut. Gunakan Resource ketika sebuah model perlu di-list, dibuat, dilihat, atau diedit melalui Panel. Sesuatu yang bukan model — misalnya report, settings screen, atau dashboard — sebaiknya dibuat sebagai standalone page.
Resource minimal
Generator dapat membuat seluruh file dasarnya:
php artisan make:panel-resource Post --panel=AdminCommand tersebut membuat app/Panels/Admin/Resources/Posts/PostResource.php beserta page, table, dan form miliknya. Jika ditulis manual, bentuk Resource terkecil yang sudah dapat berjalan adalah:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Posts;
use App\Models\Post;
use App\Panels\Admin\Resources\Posts\Pages\ListPosts;
use PandaPanel\Forms\Components\TextInput;
use PandaPanel\Forms\FormSchema;
use PandaPanel\Resources\Resource;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
final class PostResource extends Resource
{
protected static string $model = Post::class;
public static function table(TableSchema $table): TableSchema
{
return $table->columns([
TextColumn::make('title')->searchable()->sortable(),
]);
}
public static function form(FormSchema $schema): FormSchema
{
return $schema->schema([
TextInput::make('title')->required()->maxLength(255),
]);
}
/**
* @return array<string, class-string>
*/
public static function pages(): array
{
return ['index' => ListPosts::class];
}
}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
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Posts\Pages;
use App\Panels\Admin\Resources\Posts\PostResource;
use PandaPanel\Resources\Pages\ListRecords;
final class ListPosts extends ListRecords
{
protected static string $resource = PostResource::class;
}2
3
4
5
6
7
8
9
10
11
12
13
Jika Panel sudah melakukan discovery terhadap app_path('Panels/Admin/Resources'), itu sudah cukup: Resource diregistrasikan, /admin/posts memiliki route, dan sidebar menampilkan entry-nya.
Deklarasi yang wajib dimiliki Resource
PandaPanel\Resources\Resource menyisakan tepat empat member yang harus ditentukan subclass.
| Member | Signature | Mengapa wajib |
|---|---|---|
$model | protected static string $model | Semuanya dimulai dari model: query, slug, label, dan policy lookup |
table() | abstract public static function table(TableSchema $table): TableSchema | Index tidak memiliki sesuatu untuk ditampilkan tanpa column |
form() | abstract public static function form(FormSchema $schema): FormSchema | Resource tanpa form seharusnya menyatakannya secara eksplisit, bukan mewarisi create page yang diam-diam menyimpan nothing |
pages() | abstract public static function pages(): array | Page map-lah yang diregistrasikan menjadi route; Resource tanpa page tidak memiliki URL |
$model dibaca melalui Resource::getModel(). Jika property tidak pernah di-set, method melempar PandaPanel\Exceptions\PanelSchemaException yang menyebut class Resource Anda. Error bawaan PHP seperti "must not be accessed before initialization" justru menyebut base class, sehingga framework melakukan check sendiri agar pesan error mengarah langsung ke class yang perlu diperbaiki.
use App\Panels\Admin\Resources\Posts\PostResource;
PostResource::getModel(); // 'App\Models\Post'2
3
Pages
pages() memetakan page key ke page class. Key tersebut bukan sekadar label: key menjadi suffix route name dan empat key standar memiliki route shape yang sudah ditentukan.
use App\Panels\Admin\Resources\Posts\Pages\CreatePost;
use App\Panels\Admin\Resources\Posts\Pages\EditPost;
use App\Panels\Admin\Resources\Posts\Pages\ListPosts;
use App\Panels\Admin\Resources\Posts\Pages\ViewPost;
/**
* @return array<string, class-string>
*/
public static function pages(): array
{
return [
'index' => ListPosts::class,
'create' => CreatePost::class,
'view' => ViewPost::class,
'edit' => EditPost::class,
];
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Setiap key bersifat opsional. Resource yang hanya memiliki index dapat menjadi read-only list. Resource dengan index dan edit tidak memiliki detail page. Framework juga berhenti menawarkan link ke page yang memang tidak pernah dideklarasikan: ListRecords tidak merender tombol "New" tanpa key create, dan ViewRecord tidak merender tombol "Edit" tanpa key edit. Key selain empat key standar dianggap custom page. Lihat CRUD pages dan Resource pages.
Menjaga schema tetap di luar Resource
Generator menaruh table dan form pada class terpisah lalu Resource melakukan delegasi. Pola ini membuat Resource tetap mudah dibaca ketika table sudah memiliki belasan column:
use App\Panels\Admin\Resources\Posts\Forms\PostForm;
use App\Panels\Admin\Resources\Posts\Tables\PostsTable;
public static function table(TableSchema $table): TableSchema
{
return PostsTable::configure($table);
}
public static function form(FormSchema $schema): FormSchema
{
return PostForm::configure($schema);
}2
3
4
5
6
7
8
9
10
11
12
Class tersebut hanyalah plain PHP dengan static method configure(). Framework tidak mewajibkan pola ini; generator hanya memilih struktur tersebut untuk menjaga keterbacaan. Lihat Directory convention.
Deklarasi opsional
Seluruh property berikut adalah static property milik Resource dan sudah memiliki default yang berfungsi.
| Property | Type | Default | Dibahas di |
|---|---|---|---|
$slug | ?string | Bentuk plural kebab-case dari basename model | URLs and routes |
$label | ?string | Headline dari basename model | Labels and navigation |
$pluralLabel | ?string | Str::plural() dari label | Labels and navigation |
$recordTitleAttribute | ?string | 'name' | Model binding |
$navigationLabel | ?string | Plural label | Labels and navigation |
$navigationIcon | ?string | null | Labels and navigation |
$activeNavigationIcon | ?string | $navigationIcon | Labels and navigation |
$navigationGroup | string|BackedEnum|null | null | Labels and navigation |
$navigationSort | int | 0 | Labels and navigation |
$shouldRegisterNavigation | bool | true | Labels and navigation |
$cluster | ?class-string<Cluster> | null | Clusters |
$subNavigationPosition | ?SubNavigationPosition | null, berarti mengikuti setting Panel | Sub-navigation |
$with | list<string> | [] | Queries |
$softDeletes | bool | false | Soft deletes |
$singular | bool | false | Singular resources |
$parentResource | ?class-string<Resource> | null | Nested resources |
$parentRelationship | ?string | Camel case dari default slug | Nested resources |
$tenantRelationship | ?string | null | Queries |
$globalSearchAttributes | list<string> | [] | Global search |
$globalSearchLimit | int | 5 | Global search |
$globalSearchSort | int | 0 | Global search |
Contoh yang menggunakan beberapa deklarasi sekaligus:
use BackedEnum;
use PandaPanel\Resources\Resource;
final class UserResource extends Resource
{
protected static string $model = User::class;
protected static ?string $slug = 'users';
protected static ?string $navigationLabel = 'Users';
protected static ?string $navigationIcon = 'users';
protected static string|BackedEnum|null $navigationGroup = 'User Management';
protected static int $navigationSort = 10;
/** @var list<string> */
protected static array $globalSearchAttributes = ['name', 'email'];
/** @var list<string> */
protected static array $with = ['passkeys'];
// ...
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Override opsional
Di luar tiga abstract method, berikut method Resource yang paling umum di-override. Semuanya memiliki default yang tetap berfungsi.
use Illuminate\Database\Eloquent\Builder;
use PandaPanel\Infolists\InfolistSchema;
use PandaPanel\Integrations\Integrations;
use PandaPanel\Resources\RelationManager;
public static function query(): Builder; // the central query
public static function infolist(InfolistSchema $schema): InfolistSchema; // the view page's presentation
public static function relationManagers(): array; // list<class-string<RelationManager>>
public static function integrations(Integrations $integrations): Integrations;2
3
4
5
6
7
8
9
infolist() secara default mengembalikan schema tanpa perubahan dan view page fallback membentuk read-only entries dari form. Artinya menggunakan infolist adalah peningkatan presentation yang Anda pilih sendiri. relationManagers() default-nya []; menyebut manager pada method inilah satu-satunya registration yang diterimanya. integrations() juga disabled sampai Resource memanggil $integrations->isEnabled(true), karena mengaktifkannya memberi user yang dapat menjangkau screen kemampuan membuat server mengirim outbound request.
public static function infolist(InfolistSchema $schema): InfolistSchema
{
return PostInfolist::configure($schema);
}
/**
* @return list<class-string<RelationManager>>
*/
public static function relationManagers(): array
{
return [CommentsRelationManager::class];
}2
3
4
5
6
7
8
9
10
11
12
Mendaftarkan Resource
Ada dua cara dan keduanya dapat digabungkan tanpa menghasilkan duplicate registration.
use PandaPanel\Core\Panel;
// Discovery: every concrete Resource under the path.
$panel->discoverResources(app_path('Panels/Admin/Resources'));
// Explicit, for a class that lives somewhere else.
$panel->resources([PostResource::class]);2
3
4
5
6
7
Discovery menentukan class name melalui PSR-4 prefix Composer, bukan dengan membaca atau mengeksekusi file. Abstract class dan class yang bukan Resource dilewati, lalu hasil diurutkan agar manifest dari dua mesin tetap konsisten. Lihat Discovery.
Panel meng-key Resource berdasarkan slug. Dua class berbeda yang mengklaim slug sama menghasilkan PanelRegistrationException saat registration. Satu class yang benar-benar diregistrasikan dua kali di Panel yang sama juga akan membuat URL menjadi ambigu; gunakan per-panel configuration ketika yang diperlukan hanya slug atau label yang berbeda pada Panel tertentu.
Generator
php artisan make:panel-resource Post --panel=Admin
php artisan make:panel-resource Post --panel=Admin --model="App\\Domain\\Blog\\Post"
php artisan make:panel-resource Post --panel=Admin --simple
php artisan make:panel-resource Post --panel=Admin --no-view
php artisan make:panel-resource Post --panel=Admin --soft-deletes2
3
4
5
| Option | Efek |
|---|---|
--panel= | Wajib. Menentukan directory Panel dan namespace tempat file ditulis |
--model= | Class model. Default App\Models\{Name} |
--simple | Hanya membuat list page, cocok untuk modal-based editing |
--no-view | Tidak membuat view page maupun ViewAction yang seharusnya mengarah ke sana |
--soft-deletes | Mendeklarasikan $softDeletes, lalu menambahkan TrashedFilter beserta restore dan force-delete action |
--force | Menimpa file yang sudah ada |
Setiap flag benar-benar mengubah output. Tidak ada file yang ditimpa tanpa --force, dan command melaporkan file yang dibuat maupun file yang dibiarkan tetap ada. Detailnya ada pada make:panel-resource.
Catatan penting
- Model bukan optional dan jika terlupa akan gagal pada pemanggilan pertama.
getModel()melempar exception yang menyebut class Anda dan property yang harus ditambahkan. query()adalah satu-satunya entry point. List, view, edit, delete, bulk, Action lookup, dan global search semuanya melakukan resolve melalui query tersebut. Karena itu record di luar query menghasilkan 404, bukan hanya filtered row. Resource yang meng-override method ini harus memanggilparent::query().- Resource tidak otomatis ter-authorize hanya karena sudah diregistrasikan. Setiap page memeriksa policy dan sidebar menyembunyikan entry yang tidak boleh dilihat user. Lihat Authorization.
- Meregistrasikan Resource pada Panel kedua tidak otomatis memperlebar jangkauan data, tetapi class tersebut kini memiliki konteks slug lain. Gunakan
Resource::url(panel: $other)untuk meminta URL pada Panel yang tepat. php artisan panel:cachemembekukan daftar class, bukan data. Setelah menambahkan Resource, manifest harus dibangun ulang. Lihat Caching.