make:panel-relation-manager
Menghasilkan Relation Manager untuk sebuah Resource — yaitu table berisi related records yang muncul pada halaman view atau edit sebuah record — dan secara opsional dapat sekaligus menghasilkan halaman terpisah untuk relation tersebut.
Gunakan command ini ketika sebuah record memiliki daftar terkait, misalnya:
- user memiliki posts;
- post memiliki tags;
- project memiliki tasks.
php artisan make:panel-relation-manager posts --panel=Admin --resource=UsersOutput:
INFO Add PostsRelationManager::class to UserResource::relationManagers(). Nothing is registered until you do.
INFO Created [app/Panels/Admin/Resources/Users/RelationManagers/PostsRelationManager.php]2
Setelah itu tambahkan manager ke Resource:
// app/Panels/Admin/Resources/Users/UserResource.php
use App\Panels\Admin\Resources\Users\RelationManagers\PostsRelationManager;
/**
* @return list<class-string<\PandaPanel\Resources\RelationManager>>
*/
public static function relationManagers(): array
{
return [PostsRelationManager::class];
}2
3
4
5
6
7
8
9
10
11
Signature
make:panel-relation-manager
{name : The relation name on the owner model, such as posts}
{--panel= : The panel it belongs to}
{--resource= : The resource that owns the relation}
{--type=has-many : has-many or belongs-to-many}
{--soft-deletes : Offer the trashed filter and the restore actions}
{--page : Also generate a ManageRelatedRecords page for it}
{--force}2
3
4
5
6
7
8
| Argument / option | Default | Efek |
|---|---|---|
name | required | Nama relation method pada owner model. Diubah menjadi camelCase untuk $relationship dan StudlyCase untuk class name. |
--panel= | required | Panel tempat Relation Manager dibuat, dalam StudlyCase. |
--resource= | required | Resource pemilik relation. Disingularisasi dan diubah menjadi StudlyCase, sehingga Users dan User sama-sama menunjuk ke UserResource di Resources/Users/. |
--type= | has-many | has-many atau belongs-to-many. Nilai lain membuat command gagal dan tidak menulis file. |
--soft-deletes | off | Menambahkan TrashedFilter, RestoreAction, dan ForceDeleteAction. |
--page | off | Juga membuat ManageRelatedRecords page. |
--force | off | Menimpa file yang sudah ada. |
php artisan make:panel-relation-manager posts --panel=Admin --resource=Users
php artisan make:panel-relation-manager tags --panel=Admin --resource=Posts --type=belongs-to-many
php artisan make:panel-relation-manager posts --panel=Admin --resource=Users --soft-deletes --page2
3
Mengapa --type Harus Eksplisit
Bentuk relation menentukan operasi yang masuk akal, dan generator tidak bisa mengetahui bentuk relation hanya dari namanya.
hasMany child biasanya:
- dibuat;
- diedit;
- dihapus.
belongsToMany record biasanya:
- di-attach;
- di-detach;
- dapat memiliki pivot/join row dengan column sendiri.
Memilih type yang salah menghasilkan Relation Manager yang menawarkan operasi yang tidak cocok dengan relation. Error baru akan terlihat ketika action tersebut digunakan, sehingga lebih baik type relation dinyatakan eksplisit sejak generate.
--type=has-many — Default
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Users\RelationManagers;
use App\Panels\Admin\Resources\Users\UserResource;
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Actions\Relations\DeleteRelatedAction;
use PandaPanel\Actions\Relations\EditRelatedAction;
use PandaPanel\Forms\FormSchema;
use PandaPanel\Resources\RelationManager;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
/**
* The `posts` relation of Users.
*
* `query()` is the scope: every read and every write goes through the owner's
* relation, so a record belonging to another owner is simply not reachable
* here. Name this manager in `UserResource::relationManagers()` — a
* manager the resource does not declare cannot be addressed by a request.
*/
final class PostsRelationManager extends RelationManager
{
protected static string $relationship = 'posts';
protected static ?string $recordTitleAttribute = 'name';
public static function table(TableSchema $table, Model $owner): TableSchema
{
return $table
->columns([
TextColumn::make('id')
->label('ID')
->sortable()
->toggleable(false),
])
->filters([
//
])
->recordActions([
EditRelatedAction::make(UserResource::class, self::class, $owner),
DeleteRelatedAction::make(self::class, $owner),
])
->bulkActions([
//
])
->emptyState(
heading: 'Nothing here yet',
description: 'Related records will appear here once there are some.',
);
}
public static function form(FormSchema $schema, Model $owner): FormSchema
{
return $schema->schema([
//
]);
}
}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
57
58
59
60
61
Sebelum Relation Manager benar-benar berguna, ada dua hal yang perlu Anda isi:
- table awal hanya memiliki column
id; form()belum memiliki field, sehingga create/edit belum menulis data apa pun.
--type=belongs-to-many
Ada tiga perubahan utama akibat adanya join/pivot row:
use PandaPanel\Actions\Relations\DetachAction;
use PandaPanel\Actions\Relations\DetachBulkAction;
->recordActions([
EditRelatedAction::make(PostResource::class, self::class, $owner),
DetachAction::make(self::class, $owner),
DeleteRelatedAction::make(self::class, $owner),
])
->bulkActions([
DetachBulkAction::make(self::class, $owner),
])2
3
4
5
6
7
8
9
10
11
Dan satu method tambahan:
/**
* The pivot columns an attach or an edit may write. Only fields declared
* here are validated and persisted to the join row.
*/
public static function pivotForm(FormSchema $schema, Model $owner): FormSchema
{
return $schema->schema([
//
]);
}2
3
4
5
6
7
8
9
10
Perbedaan semantiknya penting:
- Detach menghapus join row tetapi tidak menghapus kedua record utama.
- Delete menghapus related record itu sendiri.
Keduanya digenerate karena sama-sama valid pada many-to-many relation. Action mana yang boleh digunakan oleh user tetap menjadi keputusan policy/authorization Anda.
--soft-deletes
php artisan make:panel-relation-manager posts --panel=Admin --resource=Users --soft-deletesGenerator menambahkan:
use PandaPanel\Actions\Relations\ForceDeleteAction;
use PandaPanel\Actions\Relations\RestoreAction;
use PandaPanel\Tables\Filters\TrashedFilter;
->filters([
TrashedFilter::make('trashed'),
])
->recordActions([
EditRelatedAction::make(UserResource::class, self::class, $owner),
RestoreAction::make(self::class, $owner),
ForceDeleteAction::make(self::class, $owner),
DeleteRelatedAction::make(self::class, $owner),
])2
3
4
5
6
7
8
9
10
11
12
13
TrashedFilter diperlukan agar deleted record benar-benar dapat muncul di table. Tanpa filter tersebut, Restore dan Force Delete menjadi action yang tidak pernah bisa dijangkau.
Deklarasi yang Harus Anda Tambahkan Sendiri
Berbeda dari make:panel-resource, generator ini tidak menuliskan property yang mengaktifkan Soft Delete behavior.
Tambahkan sendiri:
final class PostsRelationManager extends RelationManager
{
protected static string $relationship = 'posts';
protected static bool $softDeletes = true;
}2
3
4
5
6
Default RelationManager::$softDeletes adalah false.
usesSoftDeletes() membaca property ini sebelum framework mau me-resolve trashed record. Jika property tidak ditambahkan:
TrashedFiltermasih bisa menampilkan deleted row;- tetapi klik Restore pada row tersebut dapat menghasilkan 404 karena lookup tidak memasukkan trashed record.
Property ini sengaja eksplisit, bukan dideteksi otomatis. Related model yang menggunakan SoftDeletes untuk kebutuhan lain tidak otomatis membuat Relation Manager menampilkan restore flow yang tidak pernah Anda maksudkan.
usesSoftDeletes() juga memeriksa bahwa related model memang menggunakan trait SoftDeletes.
--page
php artisan make:panel-relation-manager posts --panel=Admin --resource=Users --pageOutput:
INFO Created [app/Panels/Admin/Resources/Users/RelationManagers/PostsRelationManager.php]
INFO Created [app/Panels/Admin/Resources/Users/Pages/ManageUsersPosts.php]2
Generated page:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Users\Pages;
use App\Panels\Admin\Resources\Users\RelationManagers\PostsRelationManager;
use App\Panels\Admin\Resources\Users\UserResource;
use PandaPanel\Resources\Pages\ManageRelatedRecords;
/**
* The `posts` relation on a page of its own.
*
* Register it in `UserResource::pages()` under the relation's key:
*
* 'posts' => ManageUsersPosts::class,
*
* It routes to `{record}/posts` and joins the record's
* sub-navigation automatically.
*/
final class ManageUsersPosts extends ManageRelatedRecords
{
protected static string $resource = UserResource::class;
protected static string $relationManager = PostsRelationManager::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
Dengan --page, Anda membutuhkan dua registration, bukan satu:
- manager di
relationManagers(); - page di
pages().
Contoh:
public static function pages(): array
{
return [
'index' => ListUsers::class,
'create' => CreateUser::class,
'view' => ViewUser::class,
'edit' => EditUser::class,
'posts' => ManageUsersPosts::class,
];
}2
3
4
5
6
7
8
9
10
Key pada pages() menjadi URL segment:
'posts'
→ {record}/posts2
Posisi sub-navigation ditentukan oleh key() milik Relation Manager, yaitu kebab-case dari $relationship.
Contoh relation:
orderItemsmenghasilkan sub-navigation key:
order-itemssementara URL segment tetap mengikuti key yang Anda tulis di pages().
Gunakan inline Relation Manager untuk daftar kecil yang masuk akal ditampilkan bersama record.
Gunakan dedicated Relation Page bila user akan:
- melakukan pagination;
- search;
- filtering;
- atau bekerja cukup lama dengan relation tersebut sehingga membutuhkan URL sendiri.
Yang Tidak Pernah Digenerate
| Item | Alasan |
|---|---|
Entry relationManagers() | Registration harus menjadi edit eksplisit; command mencetak line yang perlu ditambahkan. |
Entry pages() untuk --page | Sama; key route adalah keputusan Anda. |
| Relation method pada model | Generator tidak membaca model. $relationship harus menunjuk relation yang benar atau request pertama akan error. |
| Table columns dan form fields lengkap | Hanya disediakan id column dan empty schema sebagai starting point. |
protected static bool $softDeletes | Harus ditambahkan sendiri ketika menggunakan --soft-deletes. |
Custom Stubs
php artisan vendor:publish --tag=panda-panel-stubs| Stub | Ditulis ke | Placeholder |
|---|---|---|
stubs/panel/relation-manager.stub | {Plural}/RelationManagers/{Class}RelationManager.php | panel, plural, resource, class, relationship, imports, filters, recordActions, bulkActions, pivotForm |
stubs/panel/relation-page.stub | {Plural}/Pages/Manage{Plural}{Class}.php | panel, plural, resource, class, relationship |
Exit Codes
| Hasil | Code |
|---|---|
| Minimal satu file dibuat | 0 |
| Semua file sudah ada dan dilewati | 1 |
--panel atau --resource tidak diberikan | 1, dengan The --panel and --resource options are both required. |
--type tidak dikenal | 1, dengan Unknown relation type [x]. Valid types are: has-many, belongs-to-many. |
Hal yang Perlu Diperhatikan
- Tidak ada Relation Manager yang aktif sampai Resource diedit. Manager yang tidak dideklarasikan Resource tidak bisa di-address oleh request dan akan berakhir sebagai 404.
--soft-deletestidak menulis$softDeletes. Tambahkanprotected static bool $softDeletes = true;sendiri.--resourceadalah nama Resource, bukan path.Users,User, danuserssemuanya menargetResources/Users/UserResource.- Resource dengan directory convention berbeda perlu memperbaiki namespace hasil generate secara manual.
- Generator tidak memverifikasi Resource pemilik benar-benar ada. Typo tetap menghasilkan manager dengan import ke class yang tidak ada.
- Tidak ada
--type=morph-many. Generator hanya menerimahas-manydanbelongs-to-many. Polymorphic relation dapat bekerja runtime, tetapi gunakan outputhas-manysebagai dasar lalu sesuaikan action-nya. recordTitleAttributedefault'name'. Model tanpa columnnameakan menghasilkan title kosong pada modal/confirmation sampai Anda menggantinya.