多租户
简介
多租户是指单个应用实例同时服务多个客户。每个客户拥有自己的数据和访问规则,防止彼此查看或修改对方的数据。这是 SaaS 应用中的常见模式。用户通常属于用户组(常称为团队或组织)。记录归该组所有,用户可以同时是多个组的成员。这适合用户需要协作处理数据的应用。
多租户是非常敏感的话题。理解多租户的安全影响以及如何正确实现至关重要。若实现不完整或不正确,属于一个租户的数据可能暴露给另一个租户。Filament 提供一套工具帮助你在应用中实现多租户,但如何使用它们取决于你自己。
WARNING
Filament 不对应用的安全性作任何保证。确保应用安全是你的责任。更多信息请参阅 安全 一节。
简单的一对多租户
「多租户」一词含义很广,在不同语境下可能指不同事物。Filament 的多租户系统意味着用户属于多个租户(组织、团队、公司等),并可在它们之间切换。
假设你有数据库列 users.team_id,可以使用 全局作用域 将所有记录限定为与用户相同的 team_id:
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
protected static function booted(): void
{
static::addGlobalScope('team', function (Builder $query) {
if (auth()->hasUser()) {
$query->where('team_id', auth()->user()->team_id);
// or with a `team` relationship defined:
$query->whereBelongsTo(auth()->user()->team);
}
});
}
}要在创建记录时自动设置 team_id,可以创建一个 观察者:
class PostObserver
{
public function creating(Post $post): void
{
if (auth()->hasUser()) {
$post->team_id = auth()->user()->team_id;
// or with a `team` relationship defined:
$post->team()->associate(auth()->user()->team);
}
}
}设置多租户
要设置多租户,需要在 配置 中指定「租户」(如团队或组织)模型:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenant(Team::class);
}你还需要告知 Filament 用户属于哪些租户。可以在 App\Models\User 模型上实现 HasTenants 接口:
<?php
namespace App\Models;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Collection;
class User extends Authenticatable implements FilamentUser, HasTenants
{
// ...
public function teams(): BelongsToMany
{
return $this->belongsToMany(Team::class);
}
public function getTenants(Panel $panel): Collection
{
return $this->teams;
}
public function canAccessTenant(Model $tenant): bool
{
return $this->teams()->whereKey($tenant)->exists();
}
}本例中,用户属于多个团队,因此有 teams() 关系。getTenants() 方法返回用户所属的团队。Filament 用它列出用户可访问的租户。


出于安全考虑,你还需要实现 HasTenants 接口的 canAccessTenant() 方法,以防止用户通过猜测租户 ID 并将其放入 URL 来访问其他租户的数据。
你通常还希望用户能够 注册新团队。
添加租户注册页
注册页允许用户创建新租户。
登录后访问应用时,若用户还没有租户,会被重定向到此页面。
要设置注册页,需要创建一个扩展 Filament\Pages\Tenancy\RegisterTenant 的新页面类。这是一个全页 Livewire 组件。你可以把它放在任意位置,例如 app/Filament/Pages/Tenancy/RegisterTeam.php:
namespace App\Filament\Pages\Tenancy;
use App\Models\Team;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Tenancy\RegisterTenant;
use Filament\Schemas\Schema;
class RegisterTeam extends RegisterTenant
{
public static function getLabel(): string
{
return 'Register team';
}
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name'),
// ...
]);
}
protected function handleRegistration(array $data): Team
{
$team = Team::create($data);
$team->members()->attach(auth()->user());
return $team;
}
}你可以向 form() 方法添加任意 表单组件,并在 handleRegistration() 方法中创建团队。
接下来需要告知 Filament 使用此页面。可以在 配置 中完成:
use App\Filament\Pages\Tenancy\RegisterTeam;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantRegistration(RegisterTeam::class);
}

自定义租户注册页
你可以重写基础注册页类上的任意方法,使其按你的需求工作。甚至可以重写 $view 属性,使用你选择的自定义视图。
添加租户个人资料页
个人资料页允许用户编辑租户信息。
要设置个人资料页,需要创建一个扩展 Filament\Pages\Tenancy\EditTenantProfile 的新页面类。这是一个全页 Livewire 组件。你可以把它放在任意位置,例如 app/Filament/Pages/Tenancy/EditTeamProfile.php:
namespace App\Filament\Pages\Tenancy;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Tenancy\EditTenantProfile;
use Filament\Schemas\Schema;
class EditTeamProfile extends EditTenantProfile
{
public static function getLabel(): string
{
return 'Team profile';
}
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name'),
// ...
]);
}
}你可以向 form() 方法添加任意 表单组件。它们会直接保存到租户模型。
接下来需要告知 Filament 使用此页面。可以在 配置 中完成:
use App\Filament\Pages\Tenancy\EditTeamProfile;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantProfile(EditTeamProfile::class);
}

自定义租户个人资料页
你可以重写基础个人资料页类上的任意方法,使其按你的需求工作。甚至可以重写 $view 属性,使用你选择的自定义视图。
访问当前租户
在应用中的任意位置,都可以使用 Filament::getTenant() 访问当前请求的租户模型:
use Filament\Facades\Filament;
$tenant = Filament::getTenant();账单
使用 Laravel Spark
Filament 提供与 Laravel Spark 的账单集成。用户可以开通订阅并管理账单信息。
要安装该集成,请先 安装 Spark 并为你的租户模型完成配置。
然后可以用 Composer 安装 Spark 的 Filament 账单提供方:
composer require filament/spark-billing-provider在 配置 中,将 Spark 设为 tenantBillingProvider():
use Filament\Billing\Providers\SparkBillingProvider;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantBillingProvider(new SparkBillingProvider());
}现在就完成了!用户可以通过点击租户菜单中的链接来管理账单。
要求订阅
若要使用应用的任何部分都需要订阅,可以使用 requiresTenantSubscription() 配置方法:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->requiresTenantSubscription();
}现在,若用户没有有效订阅,会被重定向到账单页。
为特定资源和页面要求订阅
public static function isTenantSubscriptionRequired(Panel $panel): bool
{
return true;
}若使用了 requiresTenantSubscription() 配置方法,则可从此方法返回 false,作为例外允许访问该资源或页面。
编写自定义账单集成
账单集成编写起来相当简单。你只需要一个实现 Filament\Billing\Providers\Contracts\Provider 接口的类。该接口有两个方法。
getRouteAction() 用于获取用户访问账单页时应运行的路由 action。可以是回调函数、控制器名称或 Livewire 组件——任何在 Laravel 中对 Route::get() 有效的内容。例如,你可以用回调函数简单重定向到自己的账单页。
getSubscribedMiddleware() 返回用于检查租户是否有有效订阅的中间件名称。若没有有效订阅,该中间件应将用户重定向到账单页。
下面是一个账单提供方示例,路由 action 使用回调函数,订阅检查使用中间件:
use App\Http\Middleware\RedirectIfUserNotSubscribed;
use Filament\Billing\Providers\Contracts\BillingProvider;
use Illuminate\Http\RedirectResponse;
class ExampleBillingProvider implements BillingProvider
{
public function getRouteAction(): string
{
return function (): RedirectResponse {
return redirect('https://billing.example.com');
};
}
public function getSubscribedMiddleware(): string
{
return RedirectIfUserNotSubscribed::class;
}
}自定义账单路由 slug
你可以在 配置 中使用 tenantBillingRouteSlug() 方法自定义账单路由使用的 URL slug:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantBillingRouteSlug('billing');
}自定义租户菜单
租户切换菜单位于管理布局中,可完全自定义。
use App\Filament\Pages\Settings;
use Filament\Actions\Action;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenuItems([
Action::make('settings')
->url(fn (): string => Settings::getUrl())
->icon('heroicon-m-cog-8-tooth'),
// ...
]);
}对租户菜单项分组
默认情况下,所有租户菜单项渲染在同一个列表中。若要分成不同分组,可向 tenantMenuItems() 传入数组的数组。每个数组渲染为一组,组间以分隔线隔开:
use App\Filament\Pages\Members;
use App\Filament\Pages\Settings;
use Filament\Actions\Action;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenuItems([
[
Action::make('settings')
->url(fn (): string => Settings::getUrl())
->icon('heroicon-m-cog-8-tooth'),
Action::make('members')
->url(fn (): string => Members::getUrl())
->icon('heroicon-m-user-group'),
],
[
Action::make('documentation')
->url('https://filamentphp.com/docs')
->icon('heroicon-m-book-open'),
],
]);
}

INFO
默认情况下,register 项会加到最后一组。若要自行放置,请在任意分组中用 register 数组键显式注册。其默认 sort() 会把它放在所在组末尾;若要放在组内其他位置,请调整 sort:
->tenantMenuItems([
// ...
[
'register' => fn (Action $action): Action => $action->sort(2),
],
])允许搜索租户
你可以在 配置 中使用 searchableTenantMenu() 方法,允许搜索租户:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->searchableTenantMenu();
}当用户列表中的租户超过 10 个时,会自动启用。你可以使用 searchableTenantMenu(false) 禁用它。
自定义注册链接
要自定义租户菜单中的 注册 链接,请用 register 数组键注册新项,并传入一个 自定义 action 对象的函数:
use Filament\Actions\Action;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenuItems([
'register' => fn (Action $action) => $action->label('Register new team'),
// ...
]);
}自定义个人资料链接
要自定义租户菜单开头的个人资料链接,请用 profile 数组键注册新项,并传入一个 自定义 action 对象的函数:
use Filament\Actions\Action;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenuItems([
'profile' => fn (Action $action) => $action->label('Edit team profile'),
// ...
]);
}自定义账单链接
要自定义租户菜单中的账单链接,请用 profile 数组键注册新项,并传入一个 自定义 action 对象的函数:
use Filament\Actions\Action
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenuItems([
'billing' => fn (Action $action) => $action->label('Manage subscription'),
// ...
]);
}有条件地隐藏租户菜单项
也可用 visible() 或 hidden() 方法有条件地隐藏租户菜单项,并传入要检查的条件。传入函数会将条件求值推迟到实际渲染菜单时:
use Filament\Actions\Action;
Action::make('settings')
->visible(fn (): bool => auth()->user()->can('manage-team'))
// or
->hidden(fn (): bool => ! auth()->user()->can('manage-team'))从租户菜单项发送 POST HTTP 请求
可从租户菜单项发送 POST HTTP 请求:把 URL 传给 url(),并使用 postToUrl():
use Filament\Actions\Action;
Action::make('lockSession')
->url(fn (): string => route('lock-session'))
->postToUrl()禁用租户切换器
默认情况下,用户可以使用租户菜单在租户之间切换。若希望保持租户菜单可见,但阻止用户切换租户,可以在 配置 中使用 tenantSwitcher() 方法:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantSwitcher(false);
}这样会保持租户菜单可见,显示当前租户名称和任何自定义菜单项,但隐藏其他租户列表。适用于希望展示租户信息但不允许切换,或应通过其他方式控制切换的场景。
隐藏租户菜单
你可以使用 tenantMenu(false) 隐藏租户菜单
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMenu(false);
}不过,这通常说明 Filament 的多租户功能不适合你的项目。若每个用户只属于一个租户,应坚持使用 简单的一对多租户。
设置头像
开箱即用时,Filament 使用 ui-avatars.com 根据用户姓名生成头像。不过,若用户模型有 avatar_url 属性,则会优先使用它。若要自定义 Filament 获取用户头像 URL 的方式,可以实现 HasAvatar 契约:
<?php
namespace App\Models;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Illuminate\Database\Eloquent\Model;
class Team extends Model implements HasAvatar
{
// ...
public function getFilamentAvatarUrl(): ?string
{
return $this->avatar_url;
}
}getFilamentAvatarUrl() 方法用于获取当前用户的头像。若该方法返回 null,Filament 会回退到 ui-avatars.com。
你可以轻松将 ui-avatars.com 换成其他服务,只需创建一个新的头像提供方。可在此了解做法。
配置租户关系
创建和列出与租户关联的记录时,Filament 需要每个资源的两个 Eloquent 关系:定义在资源模型类上的「所有权(ownership)」关系,以及租户模型类上的关系。默认情况下,Filament 会根据标准 Laravel 约定尝试猜测这些关系的名称。例如,若租户模型是 App\Models\Team,它会在资源模型类上查找 team() 关系。若资源模型类是 App\Models\Post,它会在租户模型类上查找 posts() 关系。
自定义所有权关系名称
你可以在 tenant() 配置方法上使用 ownershipRelationship 参数,一次性自定义所有资源使用的所有权关系名称。本例中,资源模型类定义了 owner 关系:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenant(Team::class, ownershipRelationship: 'owner');
}或者,你可以在资源类上设置静态属性 $tenantOwnershipRelationshipName,仅自定义该资源使用的所有权关系名称。本例中,Post 模型类定义了 owner 关系:
use Filament\Resources\Resource;
class PostResource extends Resource
{
protected static ?string $tenantOwnershipRelationshipName = 'owner';
// ...
}自定义资源关系名称
你可以在资源类上设置静态属性 $tenantRelationshipName,用于自定义获取该资源时使用的关系名称。本例中,租户模型类定义了 blogPosts 关系:
use Filament\Resources\Resource;
class PostResource extends Resource
{
protected static ?string $tenantRelationshipName = 'blogPosts';
// ...
}配置 slug 属性
使用团队一类的租户时,你可能希望在 URL 中使用 slug 字段而不是团队 ID。可以在 tenant() 配置方法上使用 slugAttribute 参数:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenant(Team::class, slugAttribute: 'slug');
}配置名称属性
默认情况下,Filament 会使用租户的 name 属性在应用中显示其名称。若要更改,可以实现 HasName 契约:
<?php
namespace App\Models;
use Filament\Models\Contracts\HasName;
use Illuminate\Database\Eloquent\Model;
class Team extends Model implements HasName
{
// ...
public function getFilamentName(): string
{
return "{$this->name} {$this->subscription_plan}";
}
}getFilamentName() 方法用于获取当前用户的名称。
设置当前租户标签
在租户切换器中,你可能希望在当前团队名称上方添加类似「Active team」的小标签。可以在租户模型上实现 HasCurrentTenantLabel 方法:
<?php
namespace App\Models;
use Filament\Models\Contracts\HasCurrentTenantLabel;
use Illuminate\Database\Eloquent\Model;
class Team extends Model implements HasCurrentTenantLabel
{
// ...
public function getCurrentTenantLabel(): string
{
return 'Active team';
}
}设置默认租户
登录时,Filament 会将用户重定向到 getTenants() 方法返回的第一个租户。
有时你可能希望更改此行为。例如,你可以存储上次活跃的团队,并将用户重定向到该团队。
要自定义此行为,可以在用户上实现 HasDefaultTenant 契约:
<?php
namespace App\Models;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasDefaultTenant;
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class User extends Model implements FilamentUser, HasDefaultTenant, HasTenants
{
// ...
public function getDefaultTenant(Panel $panel): ?Model
{
return $this->latestTeam;
}
public function latestTeam(): BelongsTo
{
return $this->belongsTo(Team::class, 'latest_team_id');
}
}为租户感知路由应用中间件
你可以通过在 面板配置文件 中将中间件类数组传给 tenantMiddleware() 方法,为所有租户感知路由应用额外中间件:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMiddleware([
// ...
]);
}默认情况下,中间件会在页面首次加载时运行,但不会在后续 Livewire AJAX 请求中运行。若要在每次请求都运行中间件,可以将 true 作为第二个参数传给 tenantMiddleware() 方法,使其持久化:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMiddleware([
// ...
], isPersistent: true);
}添加租户路由前缀
默认情况下,URL 结构会把租户 ID 或 slug 紧接在面板路径之后。若希望再加一段 URL 前缀,请使用 tenantRoutePrefix() 方法:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->path('admin')
->tenant(Team::class)
->tenantRoutePrefix('team');
}之前,租户 1 的 URL 结构是 /admin/1。现在是 /admin/team/1。
使用域名识别租户
使用租户时,你可能希望使用域名或子域名路由,例如 team1.example.com/posts,而不是像 /team1/posts 这样的路由前缀。可以在 tenant() 配置方法之外,配合使用 tenantDomain() 方法。tenant 参数对应租户模型的 slug 属性:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenant(Team::class, slugAttribute: 'slug')
->tenantDomain('{tenant:slug}.example.com');
}上例中,租户位于主应用域名的子域名上。你也可以设置为从租户解析整个域名:
use App\Models\Team;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenant(Team::class, slugAttribute: 'domain')
->tenantDomain('{tenant:domain}');
}本例中,domain 属性应包含有效的域名主机,例如 example.com 或 subdomain.example.com。
INFO
当对整个域名使用参数(tenantDomain('{tenant:domain}'))时,Filament 会为应用中所有 tenant 参数注册 全局路由参数模式 为 [a-z0-9.\-]+。这是因为 Laravel 默认不允许路由参数中出现 . 字符。这可能与其他使用多租户的面板,或应用中其他使用 tenant 路由参数的部分冲突。
为资源禁用多租户
默认情况下,启用多租户的面板中的所有资源都会限定到当前租户。若有在租户之间共享的资源,可以在资源类上将静态属性 $isScopedToTenant 设为 false 来禁用多租户:
protected static bool $isScopedToTenant = false;为所有资源禁用多租户
若希望改为对每个资源选择加入多租户,而不是选择退出,可以在 service provider 的 boot() 方法或中间件中调用 Resource::scopeToTenant(false):
use Filament\Resources\Resource;
Resource::scopeToTenant(false);现在,可以通过在资源类上将静态属性 $isScopedToTenant 设为 true,为每个资源选择加入多租户:
protected static bool $isScopedToTenant = true;多租户安全
理解多租户的安全影响以及如何正确实现至关重要。若实现不完整或不正确,属于一个租户的数据可能暴露给另一个租户。Filament 提供一套工具帮助你在应用中实现多租户,但如何使用它们取决于你自己。Filament 不对应用的安全性作任何保证。确保应用安全是你的责任。
以下是 Filament 提供的、帮助你在应用中实现多租户的功能列表:
- 已启用多租户的面板中,[租户感知](#disabling-tenancy-for-a-resource) 资源的 Eloquent 模型查询会自动应用全局作用域。用于获取资源记录的查询会自动限定到当前租户。该查询用于渲染资源列表表格,也用于在编辑或查看记录时从当前 URL 解析记录。这意味着若用户试图查看不属于当前租户的记录,将收到 404 错误。 - 必须在已启用多租户的面板中存在 [租户感知](#disabling-tenancy-for-a-resource) 资源,该资源的模型才会应用全局作用域。若要为没有对应资源的模型限定查询,必须 [使用中间件为该模型应用额外的全局作用域](#using-tenant-aware-middleware-to-apply-additional-global-scopes)。 - 全局作用域在从请求中识别出租户之后才会应用。这发生在面板请求的中间件栈期间。若在识别租户之前发起查询,例如在栈中较早的中间件或 service provider 中,该查询不会限定到当前租户。要保证中间件在识别当前租户之后运行,应将其注册为 [租户中间件](#applying-middleware-to-tenant-aware-routes)。 - 如上所述,在已启用多租户的面板之外发起的查询无法访问当前租户,因此不会被限定。若有疑问,请在部署应用前检查查询是否已正确限定。 - 若需要为特定查询禁用多租户全局作用域,可以在该查询上使用 `withoutGlobalScope(filament()->getTenancyScopeName())` 方法。 - 若任何查询禁用了所有全局作用域,多租户全局作用域也会被禁用。使用此方法时应谨慎,因为它可能导致数据泄漏。若需要禁用除多租户全局作用域之外的所有全局作用域,可以使用 `withoutGlobalScopes()` 方法并传入要禁用的全局作用域数组。
- 新创建的 Eloquent 模型会自动与当前租户关联。为 [租户感知](#disabling-tenancy-for-a-resource) 资源创建新记录时,租户会自动关联到该记录。这意味着记录将属于当前租户,外键列会自动设为租户 ID。这是通过 Filament 在资源的 Eloquent 模型上为 `creating` 和 `created` 事件注册监听器完成的。 - 必须在已启用多租户的面板中存在 [租户感知](#disabling-tenancy-for-a-resource) 资源,该资源的模型才会发生自动关联。若要为没有对应资源的模型实现自动关联,必须为该模型 [注册 `creating` 事件的监听器](https://laravel.com/docs/eloquent#events),并将 `filament()->getTenant()` 与之关联。 - 这些事件在从请求中识别出租户之后才会运行。这发生在面板请求的中间件栈期间。若在识别租户之前创建模型,例如在栈中较早的中间件或 service provider 中,它不会与当前租户关联。要保证中间件在识别当前租户之后运行,应将其注册为 [租户中间件](#applying-middleware-to-tenant-aware-routes)。 - 如上所述,在已启用多租户的面板之外创建的模型无法访问当前租户,因此不会被关联。若有疑问,请在部署应用前检查模型是否已正确关联。 - 若需要为特定模型禁用自动关联,可以在创建时临时 [静音事件](https://laravel.com/docs/eloquent#muting-events)。若现有代码正在这样做或永久移除了事件监听器,应检查这是否影响多租户功能。
unique 与 exists 校验
Laravel 的 unique 和 exists 校验规则默认不使用 Eloquent 模型查询数据库,因此不会使用模型上定义的任何全局作用域,包括多租户相关的。因此,即使其他租户中存在具有相同值的软删除记录,校验也会失败。
若希望两个租户完全数据隔离,应改用 scopedUnique() 或 scopedExists() 方法。它们用通过模型查询数据库的实现替换 Laravel 的 unique 和 exists,从而应用模型上定义的任何全局作用域,包括多租户相关的:
use Filament\Forms\Components\TextInput;
TextInput::make('email')
->scopedUnique()
// or
->scopedExists()使用租户感知中间件应用额外的全局作用域
由于只有面板中存在对应资源的模型才会自动限定到当前租户,在面板中使用其他 Eloquent 模型时,为它们应用额外的租户限定可能会很有用。这样你就不必再操心把查询限定到当前租户,而是自动应用限定。为此,可以创建一个类似 ApplyTenantScopes 的新中间件类:
php artisan make:middleware ApplyTenantScopes在 handle() 方法中,你可以应用任何所需的全局作用域:
use App\Models\Author;
use Closure;
use Filament\Facades\Filament;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class ApplyTenantScopes
{
public function handle(Request $request, Closure $next)
{
Author::addGlobalScope(
'tenant',
fn (Builder $query) => $query->whereBelongsTo(Filament::getTenant()),
);
return $next($request);
}
}现在可以为所有租户感知路由 注册此中间件,并通过使其持久化,确保它在所有 Livewire AJAX 请求中都会使用:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->tenantMiddleware([
ApplyTenantScopes::class,
], isPersistent: true);
}