NOTE

You are currently viewing the documentation for Filament 4.x, which is a previous version of Filament.

Looking for the current stable version? Visit the 5.x documentation.

用户

多租户

介绍

多租户(Multi-tenancy)是一个应用的单个实例为多个客户服务的概念。每个客户都有他们自己的数据和访问规则,以防止他们查看或修改彼此的数据。这是 SaaS 应用中的常见模式。用户通常属于用户组(通常称为团队或组织)。记录归用户组所有,用户可以是多个组的成员。这适用于用户需要协作处理数据的应用。

多租户是一个非常敏感的话题。了解多租户的安全影响以及如何正确实现它非常重要。如果部分或错误地实现,属于一个租户的数据可能会暴露给另一个租户。Filament 提供了一组工具来帮助你在应用中实现多租户,但如何使用它们取决于你。

NOTE

Filament 不对你的应用的安全性提供任何保证。你有责任确保你的应用是安全的。有关详细信息,请参阅安全部分。

一对多租户

术语”多租户”是宽泛的,在不同语境下可能有不同意思。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);
        }
    }
}

设置租户

要设置租户,你需要在配置中指定”租户”(比如,团队 team 或组织 organization)模型:

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 安装 Filament 的 Spark billing provider:

composer require filament/spark-billing-provider

配置中,将 tenantBillingProvider() 设置为 Spark:

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();
}

现在,如果用户没有激活订阅,将会被重定向到账单页面。

要求对特定资源或页面进行订阅

有时,你只希望应用中的特定资源页面需要订阅。你可以在资源或页面类的 isTenantSubscriptionRequired() 方法中返回 true 来实现该功能。

public static function isTenantSubscriptionRequired(Panel $panel): bool
{
    return true;
}

你如果使用 requiresTenantSubscription() 配置方法,那么你可以从该方法中返回 false 以允许作为异常访问资源或者页面。

编写自定义账单集成

账单集成很容易编写。你只需实现 Filament\Billing\Providers\Contracts\Provider 接口。该接口有两个方法。

getRouteAction 用于获取用户访问账单页面时需要运行的路由动作。它可以是回调函数、或者控制器名、或者 Livewire 组件 - 任何当使用 Route::get() 时可以正常工作的行为。比如,你可以使用回调函数重定向到账单页面。

getSubscribedMiddleware() 返回检测租户是否激活订阅的中间件名。如果用户未激活订阅该中间件应该将用户重定向到账单页面。

下例是账单提供者,它为路由操作使用回调函数、为订阅中间件使用中间件:

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');
}

自定义租户菜单

租户切换菜单是管理员(admin)布局的特色。它完全是可定制的。

每个菜单项是一个 Action,可用通过同样的方式自定义。要注册新菜单项到租户菜单中,你可以传递 Action 到配置tenantMenuItems() 方法中:

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'),
            // ...
        ]);
}

自定义注册链接

要自定义租户菜单上的注册链接,请使用 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 链接

要在租户按钮上自定义 Profile 链接,请使用 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'),
            // ...
        ]);
}

自定义账单链接

要自定义租户菜单上的账单链接,请先使用 billing 作为数组键,并传入一个自定义 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 请求

你可用通过将 URL 传入到 url() 方法,同时调用 postToUrl(),从租户菜单项中发送 POST HTTP 请求:

use Filament\Actions\Action;

Action::make('lockSession')
    ->url(fn (): string => route('lock-session'))
    ->postToUrl()

隐藏租户菜单

使用 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() 关联。

自定义 ownership 关联名

你可以在 tenant() 配置方法中使用 ownershipRelationship 参数,自定义跨所有资源使用的 ownership 的关联名。本例中,资源模型类定义了一个 owner 关联:

use App\Models\Team;
use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenant(Team::class, ownershipRelationship: 'owner');
}

此外,你可以在资源类上设置 $tenantOwnershipRelationshipName 静态属性,然后可以使用该属性自定义仅用于该资源的所有权(ownership)关联。在这个例子中,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 属性

使用像团队这样的租户时,你可能想添加 slug 字段而不是使用团队 ID 作为 URL。你可以在 tenant() 配置方法中的 slugAttribute 参数实现该目的:

use App\Models\Team;
use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenant(Team::class, slugAttribute: 'slug');
}

配置 name 属性

默认情况下,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 会将用户重定向到 getTenant() 方法返回的第一个租户。

有时,你可能希望对此进行修改。比如,你可能会保持哪个团队是最后活跃的,并将用户重定向到那个团队。

要自定义该内容,你可以在用户上实现 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() 方法,将其他的中间件应用到所有租户感知(tenant-aware)的路由:

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 结构会将 tenant 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 这样的路由前缀。你可以使用 tenantDomain() 方法以及 tenant() 配置方法来实现该功能。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.comsubdomain.example.com

NOTE

当使用用于整个域名(tenantDomain('{tenant:domain}'))的参数时,Filament 将在应用中为所有 tenant 参数注册一个 [a-z0-9.\-]+全局路由模式。这是因为 Laravel 默认不允许路由中使用 . 字符。这可能会与其他使用租户的面板或者其他使用 tenant 路由参数的部分发送冲突。

禁用某个资源的租户

默认情况下,面板中的所有资源将租约范围设置为当前租户。如果你的资源要在不同租户之间共享,你可以通过在资源类上将 $isScopedToTenant 静态属性设置成 false 禁用租约:

protected static bool $isScopedToTenant = false;

禁用所有资源的租赁

如果你想让每个资源的租赁使用 opt-in 而非 opt-out,你可以在服务提供者的 boot() 方法或中间件中调用 Resource::scopeToTenant(false)

use Filament\Resources\Resource;

Resource::scopeToTenant(false);

现在,通过将资源类上的 $isScopedToTenant 静态属性设置为 true 时,你可以选择性加入(opt-in)到租户:

protected static bool $isScopedToTenant = true;

租户安全

理解多租户的安全含义以及如何合理地实现是很重要的。如果部分或错误地实现,属于一个租户的数据可能会暴露给另一个租户。Filament 提供了一组工具来帮助你在应用中实现多租户,但如何使用这些工具取决于你。Filament 不对你的应用的安全性提供任何保证。你有责任确保你的应用是安全的。

以下是 Filament 提供的帮你在应用中实现多租户的一系列特性:

  • 自动全局作用域的 Eloquent 模型查询,适用于[租户感知] 资源,这些资源属于已启用租户的面板。用于获取资源记录的查询会自动限定在当前租户内。此查询用于渲染资源的列表表格,也用于在编辑或查看记录时从当前 URL 解析记录。这意味着如果用户尝试查看不属于当前租户的记录,他们将收到 404 错误。

    • 租户感知资源必须存在于面板中,且资源模型需启用租约,以应用全局查询范围设置。如果你想让没有对应资源的模型使用该查询范围设置,你必须使用中间件让该模型应用额外的全局查询范围
    • 全局查询范围会在请求的租户被识别后应用。这在面板请求的中间件堆栈中发生。如果你在租户被识别前发起查询,比如在堆栈早期的中间件或者服务提供者中进行查询,那么该查询将不会将查询范围应用到当前租户。要保证中间件在当前租户被识别后才运行,你应该将其注册为租户中间件
    • 如上所述,面板之外发起的查询即使启用了租赁也没有访问当前租户的权限,因为没有应用查询范围设置。如果不确定,请在部署之前检查查询是否恰当地使用了查询范围设置。
    • 如果你想让某个特定查询禁用全局查询范围限定,你可以在该查询上使用 withoutGlobalScope(filament()->getTenancyScopeName())
    • 如果某个查询禁用了全局查询范围设置,租户全局范围设置也会同时被禁用。因此使用该方法时请慎重,因为它可能会导致数据泄漏。如果你需要禁用全局查询范围限定而不禁用租户全局查询范围设置,你可以使用 withoutGlobalScopes() 方法,并传入你想要禁用的全局查询范围设置数组。
  • 新建的 Eloquent 模型会自动关联当前租户。当租户感知资源新增一条记录时,租户会自动关联该记录。也就是说该纪律属于当前租户,因为其外键自动设置为租户 ID。Filament 为资源模型上的 creatingcreated 事件注册了一个事件监听器,来完成上述操作。如果你想让某个模型禁用此功能,你可以在该模型上使用 withoutTenancy() 方法。

    • 租户感知资源必须存在于面板中,且资源模型需启用租约,以让自动关联发生。如果你希望自动关联的模型没有对应的资源,你必须为该模型的 creating 事件注册一个中间件,并将其与 filament()->getTenant() 关联。
    • 事件在请求的租户被识别后运行。这在面板请求的中间件堆栈中发生。如果你在租户被识别前创建模型,比如比如在堆栈早期的中间件或者服务提供者中创建,那么它不会关联当前租户。要确保中间件在当前租户被识别后才运行,你应该将其注册为租户中间件
    • 如上所述,在面板之外创建的模型,即使启用了租赁,也没有访问当前租户权限。如果不确定,请在部署之前检查模型是否被恰当关联到。
    • 如果你想禁用某个特定模型的自动关联,你可以在创建时临时静默事件。如果你的代码临时禁用了关联或者永久移除了事件监听者,你应该检查确定不会影响此租户功能。

uniqueexists 验证

Laravel 的 uniqueexists 验证规则默认不使用 Eloquent 模型去查询数据库,因此,它不会使用定义在模型上的全局查询范围限定(global scope),包括多租户。因此,即使在不同租户中有一个使用同一个值的软删除记录,验证也会失败。

如果你想让两个租户有完全的数据分离,你应该使用 scopedUnique()scopedExists() 方法,它们将 Laravel 的 uniqueexists 实现替换为使用模型查询数据库的实现,应用模型上定义的任何全局查询范围限定,包括多租户:

use Filament\Forms\Components\TextInput;

TextInput::make('email')
    ->scopedUnique()
    // or
    ->scopedExists()

更多信息,请查阅验证文档中的 unique()exists()

使用租户感知中间件设置额外的全局查询范围

由于面板中存在资源的某型才会自动将查询范围设置为当前租户,因此,当你面板中用到时,设置额外的租户查询范围到其他 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);
}
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion

Previous
多因素认证