Resource yang Dapat Dicari
Sebuah resource bergabung ke palet perintah panel dengan mendeklarasikan atribut mana yang boleh dicocokkan oleh pencarian. Konfigurasi lain — label dan ikon grup, query, limit per resource, serta posisi grup di antara resource lain — sudah memiliki nilai default. Anda hanya perlu meng-override bagian yang memang berbeda. Halaman ini membahas cara sebuah resource mengaktifkan pencarian serta seluruh konfigurasi yang dimilikinya.
Contoh minimal yang dapat digunakan
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Resources\Users;
use App\Models\User;
use App\Panels\Admin\Resources\Users\Pages\EditUser;
use App\Panels\Admin\Resources\Users\Pages\ListUsers;
use App\Panels\Admin\Resources\Users\Pages\ViewUser;
use PandaPanel\Resources\Resource;
final class UserResource extends Resource
{
protected static string $model = User::class;
protected static ?string $navigationIcon = 'users';
/** @var list<string> */
protected static array $globalSearchAttributes = ['name', 'email'];
/**
* @return array<string, class-string>
*/
public static function pages(): array
{
return [
'index' => ListUsers::class,
'view' => ViewUser::class,
'edit' => EditUser::class,
];
}
// ... table(), form()
}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
Resource tersebut sekarang dapat dicari. UserResource::isGloballySearchable() mengembalikan true karena daftar atribut tidak kosong, dan palet mengelompokkan hasilnya di bawah Users dengan ikon users.
Mengaktifkan pencarian pada resource
/**
* @var list<string>
*/
protected static array $globalSearchAttributes = [];2
3
4
Nilai default adalah array kosong, dan array kosong berarti "tidak dapat dicari". PandaPanel\Search\GlobalSearch membacanya melalui dua accessor public:
public static function globalSearchAttributes(): array; // list<string>
public static function isGloballySearchable(): bool; // globalSearchAttributes() !== []2
isGloballySearchable() merupakan nilai turunan, bukan properti yang disimpan secara terpisah. Tidak ada switch on/off lain yang bisa bertentangan dengan daftar atribut. Meng-override globalSearchAttributes() sebagai pengganti properti juga didukung dan berguna jika daftar atribut perlu berubah secara dinamis:
/**
* @return list<string>
*/
public static function globalSearchAttributes(): array
{
return auth()->user()?->is_admin === true
? ['name', 'email', 'internal_reference']
: ['name'];
}2
3
4
5
6
7
8
9
Ada dua bagian yang membaca isGloballySearchable(): service pencarian itu sendiri dan SharePanelData, yang menonaktifkan palet sepenuhnya jika tidak ada resource pada panel yang mengaktifkan pencarian.
Berapa banyak hasil yang dapat disumbangkan resource
protected static int $globalSearchLimit = 5;
public static function globalSearchLimit(): int;2
3
Nilai default adalah lima. Ini merupakan batas per resource dan tetap dievaluasi terhadap sisa anggaran pencarian milik panel:
// Maksimal jumlah row yang diminta:
min($resource::globalSearchLimit(), $remaining)2
Jadi resource dengan limit 10 tetap hanya dapat mengembalikan 2 hasil jika limit panel hanya menyisakan 2. Lihat Konfigurasi pencarian panel untuk memahami cara anggaran pencarian digunakan.
// Resource dengan record yang mudah dibedakan biasanya tidak membutuhkan banyak hasil.
protected static int $globalSearchLimit = 3;
// Resource order, ketika term biasanya berupa nomor referensi.
protected static int $globalSearchLimit = 10;2
3
4
5
Posisi grup resource dalam hasil pencarian
protected static int $globalSearchSort = 0;
public static function globalSearchSort(): int;2
3
Resource diurutkan secara ascending berdasarkan [globalSearchSort(), slug()]. Nilai sort lebih kecil tampil lebih awal; jika nilai sort sama, slug digunakan sebagai pembeda. Dengan begitu, urutannya konsisten pada setiap request dan tidak bergantung pada urutan discovery resource.
final class UserResource extends Resource
{
protected static int $globalSearchSort = 0; // tampil pertama
}
final class OrderResource extends Resource
{
protected static int $globalSearchSort = 10; // setelah users
}2
3
4
5
6
7
8
9
Urutan sort juga menentukan siapa yang lebih dahulu menggunakan anggaran limit milik panel. Resource di awal dengan limit besar dapat menghabiskan jatah sebelum resource setelahnya sempat diproses.
Query yang dijalankan pencarian
use Illuminate\Database\Eloquent\Builder;
public static function globalSearchQuery(): Builder
{
return static::query();
}2
3
4
5
6
Pencarian dimulai dari Resource::query(), sama seperti lookup lain di framework. Artinya:
- eager load pada
$withmilik resource ikut diterapkan, sehingga detail yang membaca relasi tidak menghasilkan satu query tambahan per row; - tenant scope ikut diterapkan karena
query()menjalankanapplyTenantScope(); modifyQueryUsing()per panel ikut diterapkan karenaquery()menjalankanResourceConfigurationmilik panel;- override
query()pada resource juga berlaku, sehingga record yang tidak dapat ditampilkan di daftar juga tidak dapat ditemukan oleh palet.
Override method ini jika pencarian harus menggunakan subset data yang lebih sempit daripada halaman resource:
use Illuminate\Database\Eloquent\Builder;
public static function globalSearchQuery(): Builder
{
return static::query()
->whereNotNull('published_at')
->latest('published_at');
}2
3
4
5
6
7
8
Term pencarian diterapkan setelah builder dikembalikan, dalam satu where terkelompok berisi kondisi atribut yang digabungkan dengan OR. Constraint milik Anda tidak akan diperluas oleh pencarian karena grouping menjaga bentuk A AND (B OR C) agar tidak berubah menjadi A OR B OR C.
Ada dua hal yang tidak dapat dilakukan di sini: Anda tidak dapat membaca term pencarian karena term bukan parameter method dan akan di-escape oleh caller untuk LIKE sebelum diterapkan; Anda juga tidak dapat mengubah limit karena limit diterapkan oleh caller.
Bentuk sebuah grup hasil
GlobalSearch membuat satu array untuk setiap resource yang menghasilkan minimal satu hasil:
[
'resource' => $resource::slug(), // 'users'
'label' => $resource::pluralLabel(), // 'Users'
'icon' => $resource::navigationIcon(), // 'users' atau null
'results' => [ /* GlobalSearchResult::toArray() */ ],
]2
3
4
5
6
| Key | Sumber | Catatan |
|---|---|---|
resource | Resource::slug() | slug pada panel ini; jika resource didaftarkan menggunakan slug lain, grup menggunakan slug tersebut |
label | Resource::pluralLabel() | fallback ke defaultPluralLabel() dan mengikuti konfigurasi pluralLabel() per panel |
icon | Resource::navigationIcon() | menggunakan $navigationIcon milik class resource; di-resolve menjadi komponen melalui icon registry |
results | satu entry per record | berisi title, url, dan details |
Resource yang tidak menghasilkan apa pun akan dilewati sepenuhnya, bukan dikirim sebagai grup dengan results kosong.
Kondisi ketika resource dilewati
Resource tidak akan dicari jika salah satu kondisi berikut terpenuhi:
| Kondisi | Konsekuensi |
|---|---|
Tidak mewarisi PandaPanel\Resources\Resource | dilewati — pencarian memanggil static method yang hanya dijamin oleh base class tersebut |
isGloballySearchable() bernilai false | dilewati dan tidak menjalankan query |
canViewAny() bernilai false | dilewati sebelum query apa pun dibuat |
| Anggaran limit panel sudah habis | loop sudah berhenti; resource berikutnya tidak diproses |
canViewAny() berjalan melalui Resource::authorize() dan PandaPanel\Support\PolicyGate. Karena itu, panel yang mengaktifkan strictAuthorization() akan melempar PanelAuthorizationException jika model tidak memiliki policy yang sesuai — sama seperti perilaku authorization di bagian panel lainnya.
Menguji apakah resource dapat dicari
use PandaPanel\Core\PanelManager;
use PandaPanel\Search\GlobalSearch;
it('searches only resources that declared attributes', function (): void {
expect(UserResource::isGloballySearchable())->toBeTrue()
->and(UserResource::globalSearchAttributes())->toBe(['name', 'email']);
});
it('finds a record by any declared attribute', function (): void {
app(PanelManager::class)->setCurrentPanel(panel('admin'));
$groups = app(GlobalSearch::class)->for(panel('admin'), 'Lovelace');
expect($groups[0]['resource'])->toBe('users')
->and($groups[0]['results'][0]['title'])->toBe('Ada Lovelace');
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Melalui HTTP, pengujiannya cukup dengan satu request:
$groups = $this->actingAs($admin)
->getJson('/admin/search?q=Lovelace')
->json('groups');2
3
Hal yang perlu diperhatikan
- Nested resource tidak dapat menggunakan query default.
Resource::query()pada resource yang memiliki$parentResourcedimulai dari relasi parent dan memanggilParentRecord::require(), yang akan melemparPanelRegistrationExceptionjika tidak ada parent terikat — sedangkan request pencarian tidak mengikat parent. Solusinya adalah tidak memasukkan nested resource ke palet, atau meng-overrideglobalSearchQuery()agar dimulai langsung dari model sekaligus meng-overrideglobalSearchResultUrl()untuk menyediakan parent. Lihat URL hasil pencarian. - Ikon grup mengabaikan ikon per panel.
ResourceConfiguration::navigationIcon()mengubah ikon sidebar, bukan ikon grup pencarian.GlobalSearchmemanggilResource::navigationIcon()yang mengembalikan properti class resource. Label dan slug tetap mengikuti konfigurasi per panel. $globalSearchLimitberlaku per resource;limitmilik panel berlaku per pencarian. Jika sebuah resource menghasilkan lebih sedikit dari limit-nya sendiri, biasanya sisa anggaran sudah digunakan oleh resource yang diproses lebih awal.- Limit besar pada resource di awal dapat menghabiskan jatah grup berikutnya. Urutan sort bukan hanya masalah presentasi; urutan juga memengaruhi distribusi hasil.
- Mengaktifkan global search tidak menambahkan kotak pencarian pada tabel. Keduanya tidak saling berkaitan. Table search dikonfigurasi per kolom menggunakan
->searchable(). Lihat Pencarian tabel. globalSearchQuery()dijalankan pada setiap input yang lolos debounce. Pastikan query tetap efisien dan dapat memanfaatkan index sejauh memungkinkan; pencarian akan menambahkan kondisiLIKE %escaped-term%di atas query tersebut.