Skip to main content
Version: 3.0

管理关联

选择合适的工具

Filament 提供了许多方法用来在应用中管理关联。该使用哪个特性取决于你管理的关联类型,以及你想要使用哪种 UI。

关联管理器 - 资源表单下的交互式表格

兼容 HasManyHasManyThroughBelongsToManyMorphManyMorphToMany 关联。

关联管理器是一个允许管理员在不离开资源编辑页或者查看页的情况下,罗列、创建、编辑、删除、关联和取消关联相关记录的交互式表格。

Select & checkbox list - 从现有记录中选择或创建新记录

兼容 BelongsToMorphToBelongsToMany 关联。

使用 select,用户可以从现有记录列表中选择。你也可以添加按钮,使之能让你在模态框中添加新记录,而不必离开当前页面。

当在 Select 中使用 BelongsToMany 关联时,你将可以选中多个选项。记录会在你提交表单时,自动添加到透视表中。如果需要,你可以将多选下拉列表换成简单的复选项列表。两个组件的工作原理是一样的。

Repeaters - 在所有者的表单内 CRUD 多个关联记录

兼容 HasManyMorphMany 关联。

Repeaters是标准的表单组件,可以无限地渲染一套可重复的字段。它们可以和一个关联挂钩,这样记录旧可以从关联表中自动读取、创建、更新及删除。它们位于主表单 schema 的内部,可用在资源页的内部。也可以嵌套在 Action 模态框中。

以用户体验来说,该方案只适用于关联模型的字段有限。否则,表单可能会十分长。

布局表单组件 - 将表单字段保存到单个关联中

兼容 BelongsToHasOneMorphOne 关联。

所有的布局组件(GridSection, Fieldset 等)都有一个 relationship() 方法。使用该方法时,在该布局内的所有字段会被保存到关联模型中,而非所有者的模型:

use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;

Fieldset::make('Metadata')
->relationship('metadata')
->schema([
TextInput::make('title'),
Textarea::make('description'),
FileUpload::make('image'),
])

本例中,titledescriptionimage 会自动从 metadata 关联中加载,并且在表单提交时保存。如果 metadata 记录不存在,则会自动创建。

创建关联管理器

你可以使用 make:filament-relation-manager 命令,创建关联管理器:

php artisan make:filament-relation-manager CategoryResource posts title
  • CategoryResource 是(父级)模型的资源类名。
  • posts 是你要管理的关联的名称。
  • title 是用来识别 post 的属性名。

这个命令将会创建 CategoryResource/RelationManagers/PostsRelationManager.php 文件。它包含了一个类,让你可以为关联管理器定义表单表格

use Filament\Forms;
use Filament\Forms\Form;
use Filament\Tables;
use Filament\Tables\Table;

public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('title')->required(),
// ...
]);
}

public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title'),
// ...
]);
}

你必须在资源的 getRelations() 方法中注册这些新关联管理器:

public static function getRelations(): array
{
return [
RelationManagers\PostsRelationManager::class,
];
}

当完成资源管理器的表格和表单定义后,可以访问资源的编辑页查看页,在 Action 中查看。

只读模式

关联管理器通常展示在资源的编辑或者查看页面。在查看页面上,Filament 自动隐藏所有修改关联的所有 Action,如创建、编辑及删除。我们称之为"只读模式",因为默认情况下查看页面保留了只读行为。不过,你可以在关联管理器类上重写 isReadOnly() 方法,使之始终返回 false,来禁用该行为:

public function isReadOnly(): bool
{
return false;
}

此外,如果讨厌该功能,你可以在面板配置中一次性禁用所有关联管理器的只读功能:

use Filament\Panel;

public function panel(Panel $panel): Panel
{
return $panel
// ...
->readOnlyRelationManagersOnResourceViewPagesByDefault(false);
}

非常规反转关联名

对于那些未遵循 Laravel 命名规范的反转关联,你可以在表格中使用 $inverseRelationship() 方法:

use Filament\Tables;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title'),
// ...
])
->inverseRelationship('section'); // Since the inverse related model is `Category`, this is normally `category`, not `section`.
}

处理软删除

默认情况下,你不能在关联管理器中和已删除的数据进行交互。如果你想要在关联管理器中添加恢复数据、强制删除和过滤垃圾数据等功能,可以在生成关联管理器的时候使用 --soft-deletes 标志:

php artisan make:filament-relation-manager CategoryResource posts title --soft-deletes

查看更多软删除的相关内容,请点击此处

展示关联记录

关联记录会在一个表格中展示。整个关联管理器都是基于这个表格,包括新建编辑附加/分离记录关联/取消关联和删除记录等操作(Action)。

按照列表页中的文档,你可以在关联管理器类中使用所有的自定义功能:

此外,你也可以使用表格构造器的其他所有特性,来自定义关联管理器。

使用中间属性展示列表

Listing with pivot attributes

对于 BelongsToManyMorphToMany 关联,你也可以添加中间表属性。比如,如果你的资源 UserResource 有一个关联管理器 TeamsRelationManager,而且你想要将 role 中间属性添加到表中,你可以:

use Filament\Tables;

public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name'),
Tables\Columns\TextColumn::make('role'),
]);
}

请确保所有的中间属性都在关联反向关联的 withPivot() 方法中罗列出来。

创建关联记录

使用中间属性新建记录

对于 BelongsToManyMorphToMany 关联,你也可以添加中间表属性。比如,如果你的资源 UserResource 有一个关联管理器 TeamsRelationManager,你想要在新建表单中添加 role 中间属性,你可以使用:

use Filament\Forms;
use Filament\Forms\Form;

public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('role')->required(),
// ...
]);
}

请确保所有的中间属性都在关联反向关联的 withPivot() 方法中罗列出来。

自定义 CreateAction

要了解如何自定义 CreateAction,包括更改表单数据、修改通知及添加生命周期钩子,请查阅 Action 文档

编辑关联记录

使用中间属性编辑

对于 BelongsToManyMorphToMany 关联,你也可以编辑中间表属性。比如,如果你的资源 UserResource 有一个关联管理器 TeamsRelationManager,你想要在编辑表单中添加 role 中间属性,你可以:

use Filament\Forms;
use Filament\Forms\Form;

public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('role')->required(),
// ...
]);
}

请确保所有的中间属性都在关联反向关联的 withPivot() 方法中罗列出来。

自定义 EditAction

要了解如何自定义 EditAction,包括更改表单数据、修改通知及添加生命周期钩子,请查阅 Action 文档

附加和分离记录

Filament 可以为 BelongsToManyMorphToMany 关联附加和分离记录。

在生成关联管理器时,你可以传入 --attach 标志,用来在表格中添加 AttachAction, DetachActionDetachBulkAction :

php artisan make:filament-relation-manager CategoryResource posts title --attach

此外,如果你的资源已经生成,你也可以将 $table 数组添加到 actions 中:

use Filament\Tables;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->headerActions([
// ...
Tables\Actions\AttachAction::make(),
])
->actions([
// ...
Tables\Actions\DetachAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
// ...
Tables\Actions\DetachBulkAction::make(),
]),
]);
}

预加载附加模态框的下拉列表选项

默认情况下,当你搜索记录进行附加时,选项会通过 AJAX 从数据库中加载。如果你希望表单加载时就直接预载这些选项,你可以使用 AttachActionpreloadRecordSelect() 方法:

use Filament\Tables\Actions\AttachAction;

AttachAction::make()
->preloadRecordSelect()

使用中间属性附加记录

当你使用 附加(Attach) 按钮来附加记录时,你可能希望使用自定义表单来将中间属性添加到关联中:

use Filament\Forms;
use Filament\Tables\Actions\AttachAction;

AttachAction::make()
->form(fn (AttachAction $action): array => [
$action->getRecordSelect(),
Forms\Components\TextInput::make('role')->required(),
])

本例中,$action->getRecordSelect() 返回下拉列表字段,用以选择记录附加。role 文本输入框被保存到中间表的 role 字段中。

请确保所有中间表的字段都在 关联 反向关联 的 withPivot 方法中罗列出来。

设置附加选项的范围

你可能想要对 AttachAction 中可用的选项进行限制:

use Filament\Tables\Actions\AttachAction;
use Illuminate\Database\Eloquent\Builder;

AttachAction::make()
->recordSelectOptionsQuery(fn (Builder $query) => $query->whereBelongsTo(auth()->user()))

通过多个字段搜索选项

默认情况下,AttachAction 的可用选项会在表格的 recordTitleAttribute() 中搜索。如果你想搜索多个字段,你可以使用 recordSelectSearchColumns() 方法:

use Filament\Tables\Actions\AttachAction;

AttachAction::make()
->recordSelectSearchColumns(['title', 'description'])

自定义附加模态框的 Select 字段

你可以传入一个函数到 recordSelect() 方法,自定义附加之中使用的 Select 字段对象:

use Filament\Forms\Components\Select;
use Filament\Tables\Actions\AttachAction;

AttachAction::make()
->recordSelect(
fn (Select $select) => $select->placeholder('Select a post'),
)

处理重复数据

默认情况下,你不能多次附加同一个记录。要使之可行,你需要先在中间表设置一个主键 id 字段。

同时,要确保 关联 反向关联 的 withPivot() 方法中使用了 id 属性。

最后,请在关联管理器中添加 $allowsDuplicates 属性:

protected bool $allowsDuplicates = true;

关联和取消关联记录

Filament 可以为 HasManyMorphMany 关系关联和取消关联记录。

在生成关联管理器时,你可以传入 --associate 标志,这样就能将 AssociateActionDissociateActionDissociateBulkAction 同时添加到表格中:

php artisan make:filament-relation-manager CategoryResource posts title --associate

此外,如果你已经生成了资源,你也可以将这些 actions 添加到 $table 数组中:

use Filament\Tables;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->headerActions([
// ...
Tables\Actions\AssociateAction::make(),
])
->actions([
// ...
Tables\Actions\DissociateAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
// ...
Tables\Actions\DissociateBulkAction::make(),
]),
]);
}

预加载关联模态框的 Select 选项

默认情况下,由于你通过搜索记录关联,选项会通过 AJAX 从数据库中加载。如果你希望在表单加载时就预加载这些选项,可以使用 AssociateActionpreloadRecordSelect() 方法:

use Filament\Tables\Actions\AssociateAction;

AssociateAction::make()
->preloadRecordSelect()

设置选项范围以供关联

你可能想要对 AssociateAction 中可用的选项进行限制:

use Filament\Tables\Actions\AssociateAction;
use Illuminate\Database\Eloquent\Builder;

AssociateAction::make()
->recordSelectOptionsQuery(fn (Builder $query) => $query->whereBelongsTo(auth()->user()))

通过多个字段搜索关联选项

默认情况下,AssociateAction 的可用选项会在表格的 recordTitleAttribute() 中搜索。如果你想搜索多个字段,你可以使用 recordSelectSearchColumns() 方法:

use Filament\Tables\Actions\AssociateAction;

AssociateAction::make()
->recordSelectSearchColumns(['title', 'description'])

自定义关联模态框的 Select 字段

你可以传入一个函数到 recordSelect() 方法,自定义关联期间使用的 Select 字段对象:

use Filament\Forms\Components\Select;
use Filament\Tables\Actions\AssociateAction;

AssociateAction::make()
->recordSelect(
fn (Select $select) => $select->placeholder('Select a post'),
)

查看关联记录

生成关联管理器时,你可以使用 --view 标记,同时将 ViewAction 添加到表格中:

php artisan make:filament-relation-manager CategoryResource posts title --view

此外,如果你已经生成了关联管理器,可以将 ViewAction 添加到 $table->actions() 数组中:

use Filament\Tables;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->actions([
Tables\Actions\ViewAction::make(),
// ...
]);
}

删除关联记录

默认情况下,你不能在关联管理器中和已删除的记录交互。如果你想在关联管理器中添加恢复,强制删除和过滤废弃删除记录这样的功能,请在生成关联管理器时使用 --soft-deletes 标志:

php artisan make:filament-relation-manager CategoryResource posts title --soft-deletes

另外,你可以在现有关联管理器中添加软删除功能:

use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;

public function table(Table $table): Table
{
return $table
->modifyQueryUsing(fn (Builder $query) => $query->withoutGlobalScopes([
SoftDeletingScope::class,
]))
->columns([
// ...
])
->filters([
Tables\Filters\TrashedFilter::make(),
// ...
])
->actions([
Tables\Actions\DeleteAction::make(),
Tables\Actions\ForceDeleteAction::make(),
Tables\Actions\RestoreAction::make(),
// ...
])
->bulkActions([
BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
// ...
]),
]);
}

自定义 DeleteAction

要学会如何自定义 DeleteAction,包括更改表单数据、修改通知及添加生命周期钩子,请查阅 Action 文档

访问关联拥有者的记录

关联管理器是 Livewire 组件。当它们首次加载时,拥有者的记录(作为父级模型 —— 即主资源模型 —— 的 Eloquent 记录) 将保存到属性上。你可以这样读取该属性:

$this->getOwnerRecord()

不过,在静态方法,如 form()table() 中,无法访问 $this。因此,你可以使用回调函数访问 $livewire 实例:

use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;

public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Select::make('store_id')
->options(function (RelationManager $livewire): array {
return $livewire->getOwnerRecord()->stores()
->pluck('name', 'id')
->toArray();
}),
// ...
]);
}

Filament 中的所有方法都接收回调,你可以借此访问 $livewire->ownerRecord

关联管理器分组

你可以将关联管理器分组到一个标签页中。在 RelationGroup 对象中带上标签包裹多个关联管理器可以实现分组:

use Filament\Resources\RelationManagers\RelationGroup;

public static function getRelations(): array
{
return [
// ...
RelationGroup::make('Contacts', [
RelationManagers\IndividualsRelationManager::class,
RelationManagers\OrganizationsRelationManager::class,
]),
// ...
];
}

条件性显示关联管理器

默认情况下,如果关联模型策略的 viewAny() 方法返回的是 true,关联管理器将为可见。

你可以使用 canViewForRecord() 方法,决定关联管理器是否应该让特定的所有者记录及页面可见:

use Illuminate\Database\Eloquent\Model;

public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
return $ownerRecord->status === Status::Draft;
}

将关联管理器选项卡与资源表单结合

在编辑或者查看页类中,重写 hasCombinedRelationManagerTabsWithForm() 方法:

public function hasCombinedRelationManagerTabsWithContent(): bool
{
return true;
}

在关联管理器中共享资源表单及表格

您可能决定希望资源表单及表格与关系管理器的表单及表格相同,然后希望重用以前编写的代码。这很容易,只需在关联管理器中调用资源的 form()table() 方法:

use App\Filament\Resources\Blog\PostResource;
use Filament\Forms\Form;
use Filament\Tables\Table;

public function form(Form $form): Form
{
return PostResource::form($form);
}

public function table(Table $table): Table
{
return PostResource::table($table);
}

在关联管理器中隐藏共享表单组件

如果你在资源中与关联管理器共享一个表单组件,而希望在关联管理器隐藏该组件。当你想在关联管理器中隐藏一个用于所有者记录的 Select 字段时,这一功能特别有用;因为 Filament 无论如何都会对此进行处理。为此,你可以使用 hiddenOn() 方法,传入关联管理器的名称:

use App\Filament\Resources\Blog\PostResource\RelationManagers\CommentsRelationManager;
use Filament\Forms\Components\Select;

Select::make('post_id')
->relationship('post', 'title')
->hiddenOn(CommentsRelationManager::class)

在关联管理器中隐藏共享表格列

如果你在资源中与关联管理器共享一个表格字段,而希望在关联管理器隐藏将其隐藏。当你想在关联管理器中隐藏一个关联所有者记录的字段时,这一功能特别有用;因为所有者记录已经在关联管理器上方罗列出来了。为此,你可以使用 hiddenOn() 方法,传入关联管理器的名称:

use App\Filament\Resources\Blog\PostResource\RelationManagers\CommentsRelationManager;
use Filament\Tables\Columns\TextColumn;

TextColumn::make('post.title')
->hiddenOn(CommentsRelationManager::class)

在关联管理器中隐藏共享表格过滤器

如果你在资源中与关联管理器共享一个表格过滤器,而希望在关联管理器隐藏该过滤器。当你想在关联管理器中隐藏一个用于所有者记录的过滤器时,这一功能特别有用;因为该表格已经按照所有者记录进行过滤了。为此,你可以使用 hiddenOn() 方法,传入关联管理器的名称:

use App\Filament\Resources\Blog\PostResource\RelationManagers\CommentsRelationManager;
use Filament\Tables\Filters\SelectFilter;

SelectFilter::make('post')
->relationship('post', 'title')
->hiddenOn(CommentsRelationManager::class)

在关联管理器中重写共享配置

你在资源中的任何配置都可以在关联管理器上重写。比如,如果你想在关联管理上禁用继承的表格上的分页,而不是在资源本身上禁用:

use App\Filament\Resources\Blog\PostResource;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return PostResource::table($table)
->paginated(false);
}

如果你想添加头部 Action 在关联管理器中添加附加或者关联记录,在资源管理器上提供额外的配置或许有用:

use App\Filament\Resources\Blog\PostResource;
use Filament\Tables;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return PostResource::table($table)
->headerActions([
Tables\Actions\CreateAction::make(),
Tables\Actions\AttachAction::make(),
]);
}

自定义关联管理器 Eloquent 查询

你可以使用自己的查询约束或者影响整个关联管理器的模型范围。为此,你需要传入一个函数到表格的 modifyQueryUsing() 方法中,在函数中你可以自定义查询:

use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;

public function table(Table $table): Table
{
return $table
->modifyQueryUsing(fn (Builder $query) => $query->where('is_active', true))
->columns([
// ...
]);
}

自定义关联管理器的记录标题

关联管理器使用"记录标题属性"的概念去决定关联模型应该使用哪个属性进行识别、创建关联管理器时,该属性是 make:filament-relation-manager 命令的第三个参数:

php artisan make:filament-relation-manager CategoryResource posts title

上例中,Post 模型的 title 属性将被用于在关联管理器中识别一个 post。

这主要用在 Action 类中。比如,当你附加记录或者关联记录时,该标题会在 Select 字段中展示。当你编辑查看或者删除时,该标题会被用作模态框的标题。

某些情况下,你可能想要使用多个属性来组成一个标题。你可以使用 recordTitle() 替换 recordTitleAttribute() 配置方法,并传入一个函数将模型转换成标题:

use App\Models\Post;
use Filament\Tables\Table;

public function table(Table $table): Table
{
return $table
->recordTitle(fn (Post $record): string => "{$record->title} ({$record->id})")
->columns([
// ...
]);
}

如果你使用 recordTitle(),而且有关联 Action 或者附加 Action,你可能也想为这些 Action 指定搜索字段:

use Filament\Tables\Actions\AssociateAction;
use Filament\Tables\Actions\AttachAction;

AssociateAction::make()
->recordSelectSearchColumns(['title', 'id']);

AttachAction::make()
->recordSelectSearchColumns(['title', 'id'])

只读模式

资源管理器通常在资源的编辑页或查看页中展示。在查看页中,Filament 会自动隐藏所有用于修改关联的 Action,比如创建、编辑及删除。不过你可以重写资源管理器的 isReadOnly() 方法使之返回 false,来禁用该行为:

public function isReadOnly(): bool
{
return false;
}

传递属性到关联管理器

在资源中注册关联管理器时,你可以使用 make() 方法,传递一个 Livewire 属性数组到关联管理器中:

use App\Filament\Resources\Blog\PostResource\RelationManagers\CommentsRelationManager;

public static function getRelations(): array
{
return [
CommentsRelationManager::make([
'status' => 'approved',
]),
];
}

该属性数组与关联管理器类中的公用 Livewire 属性映射:

use Filament\Resources\RelationManagers\RelationManager;

class CommentsRelationManager extends RelationManager
{
public string $status;

// ...
}

现在,你可以 $this->status 在关联管理器中访问 status