<? phpukraine ДОКУМЕНТАЦІЯ
⌕Пошук по платформі
Документація українською

Переклад офіційної документації українською. Кожен розділ показує стан готовності: недоперекладене позначене відкрито, а не приховане.

ВЕРСІЯ
АКТУАЛЬНА Актуальний реліз. Переклад наздоганяє оригінал, розділи з низьким відсотком позначені у змісті.
КОРИСТУВАЧІ Й ТЕСТИ · ПЕРЕКЛАДЕНО оновлено 23 вересня 2026

Мультитенантність · Filament

Мультитенантність - це підхід, коли один екземпляр застосунку обслуговує багатьох клієнтів. Кожен клієнт має власні дані й правила доступу, які не дають йому переглядати чи змінювати чужі дані. Такий патерн поширений у SaaS-застосунках. Користувачі часто належать до груп користувачів (їх зазвичай називають командами або організаціями). Записи належать групі, а користувач може бути учасником кількох груп. Це підходить застосункам, де користувачі спільно працюють над даними.

Мультитенантність - дуже чутлива тема. Треба розуміти наслідки для безпеки й те, як правильно її реалізувати. Якщо реалізація часткова або хибна, дані одного тенанта можуть стати доступними іншому. Filament дає набір інструментів, які допомагають реалізувати мультитенантність у вашому застосунку, але користуватися ними правильно - ваша відповідальність.

Filament не дає жодних гарантій щодо безпеки вашого застосунку. Забезпечити його безпеку - ваша відповідальність. Докладніше див. розділ безпека.

Проста мультитенантність «один до багатьох»

Термін «мультитенантність» широкий і в різних контекстах означає різне. Система тенантів у Filament припускає, що користувач належить до багатьох тенантів (організацій, команд, компаній тощо) і може перемикатися між ними.

Якщо ваш випадок простіший і звʼязок «багато до багатьох» вам не потрібен, налаштовувати тенантність у Filament не треба. Замість цього можна скористатися observers і глобальними скоупами.

Скажімо, у вас є колонка в базі 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);
                // або, якщо визначено звʼязок `team`:
                $query->whereBelongsTo(auth()->user()->team);
            }
        });
    }
}

Щоб team_id автоматично проставлявся на записі під час створення, створіть observer:

class PostObserver
{
    public function creating(Post $post): void
    {
        if (auth()->hasUser()) {
            $post->team_id = auth()->user()->team_id;
            // або, якщо визначено звʼязок `team`:
            $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, до яких тенантів належить користувач. Для цього реалізуйте інтерфейс HasTenants на моделі App\Models\User:

<?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 використовує це, щоб показати список доступних користувачеві тенантів.

Задля безпеки треба також реалізувати метод canAccessTenant() інтерфейсу HasTenants, щоб користувачі не могли дістатися до даних інших тенантів, вгадавши 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 можна перевизначити, щоб використати власне представлення (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 можна перевизначити, щоб використати власне представлення (view).

Доступ до поточного тенанта

Будь-де в застосунку модель тенанта для поточного запиту доступна через Filament::getTenant():

use Filament\Facades\Filament;

$tenant = Filament::getTenant();

Білінг

Використання Laravel Spark

Filament має інтеграцію білінгу з Laravel Spark. Ваші користувачі можуть оформлювати підписки й керувати платіжними даними.

Щоб встановити інтеграцію, спершу встановіть Spark і налаштуйте його для вашої моделі тенанта.

Тепер можна встановити провайдер білінгу Filament для Spark через Composer:

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

Тепер користувачів без активної підписки перенаправлятиме на сторінку білінгу.

Вимога підписки для конкретних ресурсів і сторінок

Іноді підписка потрібна лише для окремих ресурсів і власних сторінок у застосунку. Для цього поверніть true з методу isTenantSubscriptionRequired() у класі ресурсу або сторінки:

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

Якщо ви використовуєте метод конфігурації requiresTenantSubscription(), то з цього методу можна повернути false, щоб як виняток дозволити доступ до ресурсу чи сторінки.

Написання власної інтеграції білінгу

Інтеграції білінгу пишуться доволі просто. Потрібен лише клас, який реалізує інтерфейс Filament\Billing\Providers\Contracts\Provider. У цього інтерфейсу два методи.

getRouteAction() повертає дію маршруту, яка виконається, коли користувач відкриє сторінку білінгу. Це може бути функція-колбек, назва контролера або Livewire-компонент - усе, що звично працює з Route::get() у Laravel. Наприклад, через колбек можна зробити простий редирект на власну сторінку білінгу.

getSubscribedMiddleware() повертає назву middleware, який перевіряє, чи має тенант активну підписку. Цей middleware має перенаправляти користувача на сторінку білінгу, якщо активної підписки немає.

Приклад провайдера білінгу, що використовує колбек для дії маршруту та middleware для перевірки підписки:

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

Кастомізація слага маршруту білінгу

Слаг URL для маршруту білінгу налаштовується методом tenantBillingRouteSlug() у конфігурації:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenantBillingRouteSlug('billing');
}

Кастомізація меню тенанта

Меню перемикання тенантів є в макеті адмінки. Воно повністю налаштовуване.

Кожен пункт меню - це дія, тож налаштовується так само. Щоб зареєструвати нові пункти, передайте дії в метод 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'),
            // ...
        ]);
}

Групування пунктів меню тенанта

Типово всі пункти меню тенанта рендеряться одним списком. Якщо ви хочете розділити їх на окремі групи, передайте в метод 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'),
            ],
        ]);
}

Пункт register типово додається до останньої групи. Щоб розмістити його самостійно, зареєструйте його явно в будь-якій групі за ключем масиву register. Оскільки типове 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 і передайте функцію, яка налаштовує обʼєкт дії:

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 і передайте функцію, яка налаштовує обʼєкт дії:

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 і передайте функцію, яка налаштовує обʼєкт дії:

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'))
    // або
    ->hidden(fn (): bool => ! auth()->user()->can('manage-team'))

Надсилання POST-запиту з пункту меню тенанта

Щоб надіслати POST-запит із пункту меню тенанта, передайте 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() у класі моделі тенанта.

Кастомізація назви звʼязку володіння

Назву звʼязку володіння можна змінити одразу для всіх ресурсів через аргумент ownershipRelationship методу конфігурації tenant(). У цьому прикладі класи моделей ресурсів мають визначений звʼязок 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';

    // ...
}

Налаштування атрибута слага

Коли тенант - це команда, у URL може бути зручніше мати слаг, а не ID команди. Це робиться аргументом slugAttribute методу конфігурації tenant():

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

Застосування middleware до маршрутів, що враховують тенанта

Додатковий middleware для всіх маршрутів, що враховують тенанта, задається передачею масиву класів middleware у метод tenantMiddleware() у файлі конфігурації панелі:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenantMiddleware([
            // ...
        ]);
}

Типово middleware виконується під час першого завантаження сторінки, але не на наступних Livewire AJAX-запитах. Якщо ви хочете виконувати middleware на кожному запиті, зробіть його постійним, передавши true другим аргументом у метод tenantMiddleware():

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenantMiddleware([
            // ...
        ], isPersistent: true);
}

Додавання префікса маршруту тенанта

Типово структура URL ставить ID або слаг тенанта одразу після шляху панелі. Якщо ви хочете додати перед ним ще один сегмент URL, скористайтеся методом tenantRoutePrefix():

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

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->path('admin')
        ->tenant(Team::class)
        ->tenantRoutePrefix('team');
}

Раніше структура URL для тенанта 1 була /admin/1. Тепер це /admin/team/1.

Використання домену для ідентифікації тенанта

З тенантами може знадобитися маршрутизація за доменом або піддоменом на кшталт team1.example.com/posts замість префікса маршруту /team1/posts. Це робиться методом tenantDomain() разом із методом конфігурації tenant(). Аргумент tenant відповідає атрибуту слага моделі тенанта:

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.

Коли параметр використовується для всього домену (tenantDomain('{tenant:domain}')), Filament зареєструє глобальний шаблон параметра маршруту [a-z0-9.\-]+ для всіх параметрів tenant у застосунку. Причина в тому, що Laravel типово не дозволяє символ . у параметрах маршруту. Це може конфліктувати з іншими панелями, що використовують тенантність, або з іншими частинами вашого застосунку, де є параметр маршруту tenant.

Вимкнення тенантності для ресурсу

Типово всі ресурси в панелі з тенантністю обмежуються поточним тенантом. Якщо у вас є ресурси, спільні для тенантів, вимкніть для них тенантність, задавши статичній властивості $isScopedToTenant значення false у класі ресурсу:

protected static bool $isScopedToTenant = false;

Вимкнення тенантності для всіх ресурсів

Якщо ви хочете вмикати тенантність для кожного ресурсу окремо, а не вимикати, викличте Resource::scopeToTenant(false) всередині методу boot() сервіс-провайдера або в middleware:

use Filament\Resources\Resource;

Resource::scopeToTenant(false);

Тепер тенантність для кожного ресурсу вмикається заданням статичній властивості $isScopedToTenant значення true у класі ресурсу:

protected static bool $isScopedToTenant = true;

Безпека тенантності

Треба розуміти наслідки мультитенантності для безпеки й те, як правильно її реалізувати. Якщо реалізація часткова або хибна, дані одного тенанта можуть стати доступними іншому. Filament дає набір інструментів, які допомагають реалізувати мультитенантність у вашому застосунку, але користуватися ними правильно - ваша відповідальність. Filament не дає жодних гарантій щодо безпеки вашого застосунку. Забезпечити його безпеку - ваша відповідальність.

Нижче перелічено можливості, які Filament надає для реалізації мультитенантності у вашому застосунку:

  • Автоматичне глобальне обмеження (scoping) запитів Eloquent-моделей для ресурсів, що враховують тенанта і належать до панелі з увімкненою тенантністю. Запит, яким вибираються записи ресурсу, автоматично обмежується поточним тенантом. Цей запит використовується для рендерингу таблиці списку ресурсу, а також для отримання записів із поточного URL під час редагування чи перегляду запису. Тож якщо користувач спробує переглянути запис, який не належить поточному тенанту, він отримає помилку 404.

    • Ресурс, що враховує тенанта, має існувати в панелі з увімкненою тенантністю, щоб до моделі ресурсу застосувався глобальний скоуп. Якщо ви хочете обмежувати запити моделі, для якої немає відповідного ресурсу, потрібно використати middleware для застосування додаткових глобальних скоупів для тієї моделі.
    • Глобальні скоупи застосовуються після того, як тенанта визначено із запиту. Це відбувається в стеку middleware запитів панелі. Якщо ви робите запит до того, як тенанта визначено, наприклад із раннього middleware в стеку або в сервіс-провайдері, запит не буде обмежено поточним тенантом. Щоб гарантувати, що middleware виконається після визначення поточного тенанта, зареєструйте його як tenant middleware.
    • Відповідно до попереднього пункту, запити, зроблені поза панеллю з увімкненою тенантністю, не мають доступу до поточного тенанта, тож не обмежуються. Якщо маєте сумніви, перевірте, чи ваші запити справді обмежені, перед розгортанням застосунку.
    • Якщо потрібно вимкнути глобальний скоуп тенантності для конкретного запиту, скористайтеся методом withoutGlobalScope(filament()->getTenancyScopeName()) на запиті.
    • Якщо якийсь із ваших запитів вимикає всі глобальні скоупи, глобальний скоуп тенантності теж буде вимкнено. З цим методом треба бути обережним, бо він може призвести до витоку даних. Якщо потрібно вимкнути всі глобальні скоупи, крім скоупа тенантності, скористайтеся методом withoutGlobalScopes(), передавши масив тих глобальних скоупів, які хочете вимкнути.
  • Автоматична привʼязка щойно створених Eloquent-моделей до поточного тенанта. Коли для ресурсу, що враховує тенанта, створюється новий запис, тенант автоматично привʼязується до нього. Тобто запис належатиме поточному тенанту, бо колонка зовнішнього ключа автоматично отримує ID тенанта. Це робиться так: Filament реєструє слухач подій creating і created на Eloquent-моделі ресурсу.

    • Ресурс, що враховує тенанта, має існувати в панелі з увімкненою тенантністю, щоб автоматична привʼязка відбулася. Якщо ви хочете автоматичної привʼязки для моделі, для якої немає відповідного ресурсу, потрібно зареєструвати слухач події creating для тієї моделі й привʼязати до неї filament()->getTenant().
    • Події виконуються після того, як тенанта визначено із запиту. Це відбувається в стеку middleware запитів панелі. Якщо ви створюєте модель до того, як тенанта визначено, наприклад із раннього middleware в стеку або в сервіс-провайдері, її не буде привʼязано до поточного тенанта. Щоб гарантувати, що middleware виконається після визначення поточного тенанта, зареєструйте його як tenant middleware.
    • Відповідно до попереднього пункту, моделі, створені поза панеллю з увімкненою тенантністю, не мають доступу до поточного тенанта, тож не привʼязуються. Якщо маєте сумніви, перевірте, чи ваші моделі справді привʼязані, перед розгортанням застосунку.
    • Якщо потрібно вимкнути автоматичну привʼязку для конкретної моделі, можна тимчасово приглушити події на час її створення. Якщо якийсь ваш код уже так робить або назавжди знімає слухачів подій, перевірте, чи це не ламає функціонал тенантності.

Валідація unique і exists

Правила валідації Laravel unique і exists типово не використовують Eloquent-моделі для запиту до бази, тож не застосовують жодних глобальних скоупів, визначених на моделі, зокрема й для мультитенантності. Тому валідація не пройде навіть тоді, коли запис із таким самим значенням є в іншого тенанта й позначений як мʼяко видалений.

Якщо ви хочете повного розділення даних між двома тенантами, використовуйте методи scopedUnique() або scopedExists(): вони замінюють реалізації unique і exists з Laravel на такі, що звертаються до бази через модель і застосовують усі визначені на ній глобальні скоупи, зокрема й для мультитенантності:

use Filament\Forms\Components\TextInput;

TextInput::make('email')
    ->scopedUnique()
    // або
    ->scopedExists()

Докладніше див. документацію з валідації про unique() і exists().

Використання middleware, що враховує тенанта, для додаткових глобальних скоупів

Оскільки автоматично до поточного тенанта обмежуються лише моделі, ресурси яких існують у панелі, може бути корисно застосувати додаткове обмеження за тенантом до інших Eloquent-моделей, поки вони використовуються у вашій панелі. Тоді можна не думати про обмеження запитів поточним тенантом: воно застосовуватиметься автоматично. Для цього створіть новий клас middleware, наприклад 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);
    }
}

Тепер можна зареєструвати цей middleware для всіх маршрутів, що враховують тенанта, і зробити його постійним, щоб він виконувався на всіх Livewire AJAX-запитах:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->tenantMiddleware([
            ApplyTenantScopes::class,
        ], isPersistent: true);
}
Стан перекладу

Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.

47%
Готовності
0
У роботі
17
Ще не перекладено
Глосарій термінів