5 · Resource pertama
Tujuan: /admin/products menampilkan daftar produk, dan Anda bisa membuat, melihat, serta menyunting satu produk.
Resource adalah satu model Eloquent yang ditampilkan di dalam satu panel: tabel, form, halaman, dan satu query yang dibaca semuanya. Apa pun yang bukan model — laporan, layar pengaturan — dibuat sebagai halaman mandiri.
Lakukan ini
1. Model dan tabelnya
php artisan make:model Product -mIsi migrasinya:
// database/migrations/xxxx_xx_xx_xxxxxx_create_products_table.php
public function up(): void
{
Schema::create('products', function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('sku')->unique();
$table->decimal('price', 10, 2)->default(0);
$table->string('status')->default('draft'); // draft | published | archived
$table->text('description')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamps();
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
Lalu modelnya:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
final class Product extends Model
{
use HasFactory;
protected $fillable = ['name', 'sku', 'price', 'status', 'description', 'published_at'];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'price' => 'decimal:2',
'published_at' => 'datetime',
];
}
}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
php artisan migrate2. Resource-nya
php artisan make:panel-resource Product --panel=AdminINFO Created [app/Panels/Admin/Resources/Products/ProductResource.php]
INFO Created [app/Panels/Admin/Resources/Products/Pages/ListProducts.php]
INFO Created [app/Panels/Admin/Resources/Products/Pages/CreateProduct.php]
INFO Created [app/Panels/Admin/Resources/Products/Pages/ViewProduct.php]
INFO Created [app/Panels/Admin/Resources/Products/Pages/EditProduct.php]
INFO Created [app/Panels/Admin/Resources/Products/Tables/ProductsTable.php]
INFO Created [app/Panels/Admin/Resources/Products/Forms/ProductForm.php]2
3
4
5
6
7
Tidak ada yang perlu didaftarkan. discoverResources(app_path('Panels/Admin/Resources')) dari langkah 3 sudah mencakup direktori itu, jadi /admin/products menjawab pada request berikutnya.
Nama direktori jamak, nama kelas tunggal. Tabel dan form tinggal di kelasnya sendiri karena keduanya akan tumbuh — resource yang form()-nya delapan puluh baris adalah berkas yang konfigurasi navigasinya mustahil dicari.
3. Policy-nya
php artisan make:policy ProductPolicy --model=Product<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Product;
use App\Models\User;
final class ProductPolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function view(User $user, Product $product): bool
{
return true;
}
public function create(User $user): bool
{
return true;
}
public function update(User $user, Product $product): bool
{
return true;
}
public function delete(User $user, Product $product): bool
{
return true;
}
public function deleteAny(User $user): bool
{
return true;
}
}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
Tanpa ini, resource-nya menjawab 403
Bukan 404, dan bukan daftar kosong — 403. Setiap layar panel mendelegasikan keputusan ke Gate milik Laravel; gate-nya ditanya, dan tanpa policy jawabannya tidak. Itu default yang disengaja: panel yang menampilkan semua record hanya karena belum ada yang menulis aturan akan jauh lebih buruk. Di mode development, panel mencatat model mana yang belum punya policy sekaligus menyebut perintah make:policy.
return true di atas adalah policy tutorial. Langkah 8 menggantinya dengan yang sungguhan.
Apa yang ditulis generator
Resource-nya justru berkas yang paling kecil. Ia menyebut model, navigasi, dan peta halaman, lalu mendelegasikan kedua skemanya:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Products;
use App\Models\Product;
use App\Panels\Admin\Resources\Products\Forms\ProductForm;
use App\Panels\Admin\Resources\Products\Pages\CreateProduct;
use App\Panels\Admin\Resources\Products\Pages\EditProduct;
use App\Panels\Admin\Resources\Products\Pages\ListProducts;
use App\Panels\Admin\Resources\Products\Pages\ViewProduct;
use App\Panels\Admin\Resources\Products\Tables\ProductsTable;
use PandaPanel\Forms\FormSchema;
use PandaPanel\Resources\Resource;
use PandaPanel\Tables\TableSchema;
final class ProductResource extends Resource
{
protected static string $model = Product::class;
protected static ?string $navigationIcon = 'folder';
protected static int $navigationSort = 0;
public static function table(TableSchema $table): TableSchema
{
return ProductsTable::configure($table);
}
public static function form(FormSchema $schema): FormSchema
{
return ProductForm::configure($schema);
}
/**
* @return array<string, class-string>
*/
public static function pages(): array
{
return [
'index' => ListProducts::class,
'create' => CreateProduct::class,
'view' => ViewProduct::class,
'edit' => EditProduct::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
41
42
43
44
45
46
47
48
Empat hal yang wajib dideklarasikan resource
| Anggota | Kenapa wajib |
|---|---|
$model | Semuanya berawal dari sini: query, slug, label, dan pencarian policy |
table() | Halaman index tidak punya apa pun untuk ditampilkan tanpa kolom |
form() | Resource tanpa form sebaiknya menyatakannya secara eksplisit, bukan mewarisi halaman create yang diam-diam tidak menyimpan apa pun |
pages() | Peta halaman inilah yang di-routing; resource tanpa halaman tidak punya URL |
Kunci halaman bukan sekadar label
Kunci-kunci itu menjadi akhiran nama route, dan empat kunci standarnya punya bentuk URL tetap. Semua kunci bersifat opsional, dan framework berhenti menawarkan tautan ke halaman yang tidak pernah dideklarasikan — hapus 'create' dan tombol New tidak dirender; hapus 'edit' dan halaman view tidak menampilkan tombol Edit.
| Kunci | URL | Nama route |
|---|---|---|
index | /admin/products | panel.admin.resources.products.index |
create | /admin/products/create | …products.create |
view | /admin/products/{record} | …products.view |
edit | /admin/products/{record}/edit | …products.edit |
Rapikan navigasinya
Dua baris di resource, keduanya opsional, keduanya layak diisi sekarang:
final class ProductResource extends Resource
{
protected static string $model = Product::class;
protected static ?string $navigationIcon = 'package';
protected static ?string $navigationLabel = 'Produk';
protected static string|BackedEnum|null $navigationGroup = 'Katalog';
protected static int $navigationSort = 10;
// ...
}2
3
4
5
6
7
8
9
10
11
Tambahkan use BackedEnum; di bagian atas untuk baris ketiga. Grup navigasi hanya perlu dideklarasikan di panel kalau Anda peduli pada urutannya:
// app/Panels/Admin/AdminPanelProvider.php
->navigationGroups(['Katalog', 'Sistem'])2
Key ikon yang tidak ada di registry tidak merender apa-apa
Ikon di-resolve lewat registry saat build, bukan saat runtime. Setelah memakai key baru, jalankan php artisan panel:icons.
Pastikan berhasil
php artisan route:list --name=panel.admin.resources
php artisan panel:cache # sekarang semestinya melaporkan 1 panel, 1 resource
php artisan panel:clear2
3
Lalu buka /admin/products. Anda mendapat tabel kosong dengan tombol New product. Buat satu produk, dan produk itu muncul di daftar — dengan kolom seadanya bawaan generator, yang akan diperbaiki di langkah 6.
Kalau gagal
| Gejala | Penyebab | Perbaikan |
|---|---|---|
403 di /admin/products | Belum ada policy, atau viewAny() mengembalikan false | Buat ProductPolicy |
404 di /admin/products | Kelasnya di luar path discovery, atau panel:cache sudah basi | php artisan panel:clear |
| URL jalan tapi tidak ada entri sidebar | viewAny() menolak — navigasi menyembunyikan yang tidak boleh dilihat | Periksa policy-nya |
PanelSchemaException: model not set | $model belum diisi | Tambahkan protected static string $model = Product::class; |
| Resource muncul di panel yang salah | --panel= menyebut direktori lain | Pindahkan kelasnya, atau generate ulang |
Kenapa panel:cache penting di sini
Setelah panel:cache berjalan, discovery berhenti berjalan — resource yang ditambahkan sesudahnya tidak punya route, tidak punya entri navigasi, dan tidak memunculkan error. Manifest-nya menyimpan sidik jari path discovery dan memberi peringatan di mode development ketika sudah basi. php artisan panel:clear adalah perbaikannya, dan itu hal pertama yang patut dicoba setiap kali kelas baru tidak muncul.
Berikutnya
Resource-nya berfungsi. Tapi belum nyaman dipakai.
→ 6 · Merapikan form dan tabel
Baca juga
- Creating resources — setiap properti dan override
- make:panel-resource — setiap flag dan efeknya
- Resource authorization — metode policy yang diminta tiap halaman
- Directory convention