PanelTenant
PandaPanel\Contracts\PanelTenant adalah sisi tenancy yang diimplementasikan pada model tenant: nilai apa yang mengidentifikasi tenant tersebut dan nama apa yang harus ditampilkan di layar. Contract ini bersifat opsional — PandaPanel\Tenancy\Tenancy akan memakai primary key dan atribut name sebagai fallback — dan Anda mengimplementasikannya ketika salah satu asumsi tersebut tidak tepat, atau ketika Anda ingin perilakunya dinyatakan secara eksplisit daripada ditebak.
Mengimplementasikannya
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Contracts\PanelTenant;
final class Workspace extends Model implements PanelTenant
{
public function getTenantKey(): int|string
{
return (int) $this->getKey();
}
public function getTenantName(): string
{
return (string) $this->getAttribute('name');
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use PandaPanel\Tenancy\Tenancy;
Tenancy::keyOf($workspace); // 7
Tenancy::nameOf($workspace); // 'Acme'
Tenancy::describe($workspace); // ['key' => 7, 'name' => 'Acme']2
3
4
5
Interface
namespace PandaPanel\Contracts;
interface PanelTenant
{
public function getTenantKey(): int|string;
public function getTenantName(): string;
}2
3
4
5
6
7
8
| Method | Signature | Digunakan untuk |
|---|---|---|
getTenantKey | getTenantKey(): int|string | Tenancy::keyOf(), Tenancy::key(), field key pada entry switcher, serta penanda tenant aktif |
getTenantName | getTenantName(): string | Tenancy::nameOf() dan field name pada entry switcher |
Sengaja hanya ada dua method. Segala hal yang menggambarkan apa itu tenant — team, organisasi, akun pelanggan, database — adalah urusan aplikasi. Jika framework meminta plan atau logo, framework justru akan mendeskripsikan tenant milik satu proyek tertentu, bukan konsep tenant secara umum.
getTenantKey()
Nilai yang digunakan untuk mengidentifikasi tenant. Biasanya primary key. Proyek yang routing-nya memakai acme alih-alih 41 dapat mengembalikan slug:
public function getTenantKey(): int|string
{
return $this->slug;
}2
3
4
getTenantName()
Nama yang akan ditampilkan untuk tenant — di switcher, shell panel, atau kalimat yang menjelaskan tenant pemilik sebuah record.
public function getTenantName(): string
{
return $this->trading_name ?: $this->legal_name;
}2
3
4
Fallback
Model tenant yang tidak mengimplementasikan contract tetap dapat digunakan. Tenancy akan menentukan kedua nilai tersebut sendiri:
public static function keyOf(Model $tenant): int|string
{
if ($tenant instanceof PanelTenant) {
return $tenant->getTenantKey();
}
$key = $tenant->getKey();
return is_int($key) || is_string($key) ? $key : (string) $key;
}2
3
4
5
6
7
8
9
10
public static function nameOf(Model $tenant): string
{
if ($tenant instanceof PanelTenant) {
return $tenant->getTenantName();
}
$name = $tenant->getAttribute('name');
return is_string($name) && $name !== '' ? $name : (string) self::keyOf($tenant);
}2
3
4
5
6
7
8
9
10
| Nilai | Contract diimplementasikan | Contract tidak diimplementasikan |
|---|---|---|
| key | getTenantKey() | getKey(), di-cast ke string jika bukan int maupun string |
| name | getTenantName() | atribut name, jika berupa string yang tidak kosong |
name, tetapi tanpa atribut name | — | key dalam bentuk string |
Fallback ke key, bukan string kosong, dilakukan dengan sengaja: row kosong di tenant switcher tidak membantu siapa pun, sedangkan 41 setidaknya masih mengidentifikasi tenant yang dimaksud.
use PandaPanel\Tenancy\Tenancy;
// A model with no contract and a `name` column.
Tenancy::describe($workspace); // ['key' => 7, 'name' => 'Acme']
// A model with no contract and no name.
Tenancy::describe($blank); // ['key' => 12, 'name' => '12']2
3
4
5
6
7
Tenancy::describe()
Mengubah satu tenant ke bentuk data yang persis seperti yang diterima frontend:
/** @return array{key: int|string, name: string} */
public static function describe(Model $tenant): array2
Tenancy::describe($workspace); // ['key' => 7, 'name' => 'Acme']PandaPanel\Http\Middleware\SharePanelData menyebarkan hasil tersebut dan menambahkan dua field sebelum membagikannya ke frontend:
$describe = static fn (Model $tenant): array => [
...Tenancy::describe($tenant),
'url' => $panel->getTenantUrl($tenant),
'current' => $currentKey !== null && Tenancy::keyOf($tenant) === $currentKey,
];2
3
4
5
Karena itu key digunakan untuk menandai entry aktif pada switcher dan perbandingannya memakai ===. Jika satu getTenantKey() mengembalikan '7' dan yang lain mengembalikan 7, keduanya tidak akan pernah dianggap sama. Lakukan cast secara konsisten, seperti contoh pada awal halaman ini.
Tenancy::key()
Mengembalikan key tenant aktif, atau null jika tidak ada tenancy — biasanya inilah nilai yang dibutuhkan sebuah where clause dan cukup untuk sebagian besar pemanggil:
public static function key(): int|string|nulluse PandaPanel\Tenancy\Tenancy;
Post::query()->where('workspace_id', Tenancy::key())->get();2
3
Method ini melewati keyOf(), sehingga hasilnya sama dengan nilai dari getTenantKey(). Tenant yang diidentifikasi berdasarkan slug akan mengembalikan slug di sini, yang biasanya bukan foreign key pada tabel domain lainnya. Jika Anda membutuhkan primary key asli model, ambil secara langsung:
Tenancy::current()?->getKey();Bagian yang tidak menggunakan contract ini
Resource::applyTenantScope() membangun whereHas menggunakan primary key model, bukan getTenantKey():
return $query->whereHas(
$relationship,
static fn (Builder $related): Builder => $related->whereKey($tenant->getKey()),
);2
3
4
Perilaku ini benar — relationship mengarah ke row tenant dan row dihubungkan menggunakan primary key. Artinya, getTenantKey() yang mengembalikan slug akan memengaruhi URL, entry switcher, dan Tenancy::key(), tetapi tidak mengubah cara record di-scope.
Catatan
- Contract ini opsional sebagaimana
PanelUser. Panel tanpa tenancy tidak pernah membutuhkannya, dan model tenant dengan primary key serta columnnamesudah dapat bekerja tanpa implementasi tambahan. getTenantKey()harus mengembalikanintataustring. UUID cast object, value object, atau enum harus dikonversi terlebih dahulu; return type interface memaksakan hal ini.- Mengimplementasikan contract ini tidak otomatis menjadikan sebuah model sebagai tenant.
Panel::tenant()yang menentukan model tenant; contract ini hanya mendeskripsikan satu instance tenant. Tenancy::nameOf()adalah satu-satunya tempat nama tenant dibaca. Tidak ada hookgetTenantAvatar(),getTenantLogo(), ataugetTenantPlan(). Jika switcher membutuhkan lebih dari nama, render switcher Anda sendiri berdasarkan shared prop ditambah data aplikasi — lihat Tenant Switcher.- Model tenant yang berada di connection berbeda perlu menetapkan
$connection. Pada arsitektur database-per-tenant, daftar tenant berada di central database; jika tidak diset, pembacaan dari dalam tenant context akan mencoba database tenant aktif. Lihat Satu Database per Tenant.