Discovery
Discovery menemukan class Resource, Page, dan Widget yang dimiliki sebuah Panel dengan memindai directory yang dideklarasikan Panel, me-resolve setiap file menjadi nama class melalui map PSR-4 milik Composer, lalu mempertahankan class yang mengimplementasikan contract yang sesuai. Mekanisme inilah yang membuat Panel Provider cukup menyebut tiga directory, tanpa harus mendaftarkan setiap class satu per satu. Gunakan dokumentasi ini ketika class yang Anda buat tidak muncul di Panel.
Mengaktifkan Discovery
use PandaPanel\Core\Panel;
use PandaPanel\Core\PanelProvider;
final class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->path('admin')
->auth()
->discoverResources(app_path('Panels/Admin/Resources'))
->discoverPages(app_path('Panels/Admin/Pages'))
->discoverWidgets(app_path('Panels/Admin/Widgets'));
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Setiap concrete class di bawah path tersebut yang mengimplementasikan contract yang sesuai akan diregistrasikan. Untuk melihat apa saja yang ditemukan:
use PandaPanel\Facades\PandaPanel;
PandaPanel::resources('admin')->all(); // list<class-string>
PandaPanel::pages('admin')->all();
PandaPanel::widgets('admin')->all();2
3
4
5
php artisan panel:cache # prints the counts it foundTiga Method Discovery
Ketiganya bersifat variadic dan akumulatif — memanggil method yang sama dua kali akan menambahkan path, bukan mengganti path sebelumnya. Behavior ini memungkinkan module menambahkan class ke Panel tanpa harus mengubah konfigurasi inti.
| Method | Signature | Contract yang Dibutuhkan |
|---|---|---|
discoverResources | discoverResources(string ...$paths): self | PandaPanel\Contracts\ResourceContract |
discoverPages | discoverPages(string ...$paths): self | PandaPanel\Contracts\PageContract |
discoverWidgets | discoverWidgets(string ...$paths): self | PandaPanel\Contracts\WidgetContract |
$panel
->discoverResources(app_path('Panels/Admin/Resources'))
->discoverResources(base_path('modules/billing/src/Resources'));
$panel->getResourceDiscoveryPaths(); // both, in order, deduplicated2
3
4
5
Reader yang tersedia:
getResourceDiscoveryPaths();getPageDiscoveryPaths();getWidgetDiscoveryPaths().
Masing-masing mengembalikan list<string>.
Path harus berupa absolute directory. Pemindaian dilakukan secara recursive, sehingga Resource di Resources/Users/UserResource.php tetap ditemukan tanpa perlu mendaftarkan Resources/Users secara terpisah.
Class yang Memenuhi Syarat
PandaPanel\Discovery\PanelDiscoverer menerapkan tiga aturan berikut secara berurutan:
- Extension file harus
.php. ClassResolver::forPath()harus menghasilkan nama class danclass_exists()harus mengonfirmasinya.- Reflection harus memastikan class tersebut bukan abstract, bukan interface, dan
implementsInterface($contract)bernilai benar.
Selain itu akan dilewati secara diam-diam, bukan membuat application gagal boot. Alasannya, directory Resource secara sah dapat berisi base class, object form, object table, enum, dan class pendukung lainnya.
Sebagai contoh, directory Resources/Users dapat berisi satu Resource, empat Page, Form, Table, Infolist, Exporter, dan Importer, tetapi discovery Resource hanya mengembalikan satu class:
use PandaPanel\Discovery\PanelDiscoverer;
app(PanelDiscoverer::class)->resources(panel('admin'));
// [App\Panels\Admin\Resources\Users\UserResource::class]2
3
4
| Method | Signature |
|---|---|
resources | resources(Panel $panel): list<class-string<ResourceContract>> |
pages | pages(Panel $panel): list<class-string<PageContract>> |
widgets | widgets(Panel $panel): list<class-string<WidgetContract>> |
Hasil discovery di-deduplicate lalu diurutkan berdasarkan nama class. Karena itu dua machine dengan urutan filesystem berbeda tetap menghasilkan list yang sama, yang pada akhirnya membuat cached manifest identik byte-per-byte antar-run.
Path yang bukan directory tidak menyumbang apa pun. Kondisi tersebut bukan error karena Panel boleh mendeklarasikan directory yang belum dibuat oleh sebuah module.
Me-resolve File Menjadi Class
PandaPanel\Discovery\ClassResolver mengubah path menjadi class yang dideklarasikan file tersebut menggunakan prefix PSR-4 yang terdaftar di Composer:
use PandaPanel\Discovery\ClassResolver;
ClassResolver::forPath(app_path('Panels/Admin/Resources/Users/UserResource.php'));
// 'App\Panels\Admin\Resources\Users\UserResource'
ClassResolver::forPath('/tmp/outside-every-psr4-root/Thing.php');
// null2
3
4
5
6
7
public static function forPath(string $path): ?stringMembaca file untuk mencari class yang dideklarasikan berarti discovery harus mengeksekusi atau melakukan tokenisasi arbitrary source code. Autoloader Composer sebenarnya sudah mengetahui mapping tersebut, sehingga PandaBear menggunakan informasi itu.
Dua konsekuensinya:
- Prefix diurutkan dari namespace terpanjang terlebih dahulu, sehingga nested PSR-4 root menang atas parent root.
nullberarti path berada di luar seluruh PSR-4 root yang terdaftar — yang juga berarti tidak ada autoloader yang dapat memuat class tersebut.
Prefix dibaca satu kali per process lalu di-memoize.
Menggabungkan Discovery dengan Registrasi Eksplisit
Registrasi eksplisit tetap didukung dan digabungkan dengan hasil discovery. Class yang didaftarkan melalui kedua cara hanya muncul satu kali karena registry menggunakan slug dan id sebagai key.
use App\Panels\Admin\Resources\Users\UserResource;
$panel
->resources([UserResource::class])
->discoverResources(app_path('Panels/Admin/Resources'));
PandaPanel::resources($panel)->all();
// [App\Panels\Admin\Resources\Users\UserResource::class] — once2
3
4
5
6
7
8
Cara ini dapat digunakan ketika Panel perlu mengambil class yang berada di luar tree-nya, misalnya Resource dari package atau Resource yang digunakan bersama oleh dua Panel.
PandaPanel\Core\PanelManager::buildRegistries() melakukan proses merge tersebut. Resource configuration diregistrasikan terlebih dahulu agar class yang sudah dikonfigurasi untuk Panel tertentu tidak ikut diregistrasikan lagi dalam bentuk default dan mengklaim slug bawaannya.
Hal yang Tidak Dilakukan Discovery
- Discovery tidak mencari Panel. Panel Provider harus dicantumkan di
config/panda-panel.php. Daftar tersebut adalah tempat semua Panel dalam application terlihat sekaligus, dan penambahan Panel harus merupakan perubahan konfigurasi yang disengaja, bukan side effect filesystem. Urutan pada config tidak menentukan prioritas; Panel diproses berdasarkan id. - Discovery tidak mencari Relation Manager. Resource harus menyebutnya melalui
relationManagers(). Relation Manager yang tidak didaftarkan di sana tidak dapat diakses oleh request yang menyebut manager tersebut. - Discovery tidak mencari Vue component. Component di-resolve melalui registry
import.meta.globsaat build-time. Lihat Component Registries. - Discovery tidak mendaftarkan route.
PanelRouteRegistrarmembaca registry setelah proses discovery selesai.
Kapan Discovery Dijalankan
Discovery berjalan satu kali per Panel saat PandaPanel::register(), yang dipanggil pada boot() service provider. PandaPanel\Cache\PanelManifest::for() menjadi titik pemisah antara discovery langsung dan cached manifest:
public function for(Panel $panel): array
{
$cached = $this->load()[$panel->getId()] ?? null;
if ($cached !== null) {
return $cached;
}
return [
'resources' => $this->discoverer->resources($panel),
'pages' => $this->discoverer->pages($panel),
'widgets' => $this->discoverer->widgets($panel),
];
}2
3
4
5
6
7
8
9
10
11
12
13
14
Jika bootstrap/cache/panels.php tersedia, discovery tidak dijalankan sama sekali: tidak ada filesystem scan, reflection, atau discovery work per request. Lihat Caching.
Hal yang Perlu Diperhatikan
- Cached manifest membekukan daftar class. Class yang ditambahkan setelah
php artisan panel:cachetidak akan ada di Panel — tidak ada route, sidebar entry, maupun error. Di development framework menulis warning ketika fingerprint tidak lagi cocok. Solusinya adalahphp artisan panel:clear. - Class di luar seluruh PSR-4 root tidak terlihat.
ClassResolvermengembalikannulllalu file dilewati tanpa pesan. Jika Resource dari package tidak ditemukan, periksa namespace padacomposer.jsondan pastikancomposer dump-autoloadsudah dijalankan. - Abstract base class dilewati, termasuk base class framework.
PandaPanel\Resources\ResourcemengimplementasikanResourceContract; tanpa pengecekan concrete class, base framework tersebut ikut terdeteksi. - Trait dan interface di directory yang sama juga dilewati, tetapi concrete class yang kebetulan mengimplementasikan contract akan tetap ditemukan. Jika Anda menyimpan shared concrete base di directory Resource, jadikan class tersebut
abstract. - Discovery bersifat per-Panel dan hanya menggunakan path milik Panel tersebut. Dua Panel yang diarahkan ke directory yang sama akan mendapatkan class yang sama. Panel yang diarahkan ke directory milik Panel lain juga akan mengadopsi class di sana secara diam-diam; kadang disengaja, tetapi lebih sering merupakan typo.
- Collision slug menggagalkan boot. Dua Resource dengan slug yang sama melempar
PanelRegistrationException::duplicateResourceSlug(). Page dengan slug yang sudah digunakan Resource melemparslugCollidesWithResource(). Discovery membuat collision lebih mudah terjadi sehingga framework menolaknya secara eksplisit. - Resource yang hilang dari sidebar lebih sering disebabkan policy daripada discovery.
Gate::allows()menolak ketika policy tidak ada, dan hasilnya terlihat sama seperti policy yang memang mengembalikan false. Pada development,PandaPanel\Support\MissingPolicyNoticemenulis alasan sekali per model. Pada environment apa pun,strictAuthorization()mengubah kondisi tersebut menjadi exception.