Skip to content
全部文档

概述

简介

账户设置表单示例账户设置表单示例

Filament 的 forms 包可让你轻松在应用中构建动态表单。它也被其他 Filament 包用于在 面板资源操作模态框表格筛选器 等场景中渲染表单。学习如何构建表单,是学习使用这些 Filament 包的基础。

本指南将介绍使用 Filament form 包构建表单的基础知识。若计划把新表单加到自己的 Livewire 组件中,应先完成那一步再回来。若是把表单加到 面板资源 或其他 Filament 包中,可以直接开始!

表单字段

表单字段类位于 Filament\Form\Components 命名空间中,放在组件的 schema 数组里。Filament 内置多种字段类型,适用于编辑不同类型的数据:

你也可以创建自定义字段,按任意方式编辑数据。

可使用静态方法 make() 创建字段,并传入其唯一名称。通常,字段名对应 Eloquent 模型上的属性名:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
表单字段表单字段

你可以使用「点语法(dot notation)」将字段绑定到数组中的键:

php
use Filament\Forms\Components\TextInput;

TextInput::make('socials.github_url')

校验字段

在 Laravel 中,校验规则通常写在数组中,如 ['required', 'max:255'],或写成组合字符串如 required|max:255。若你只在后端用简单的 form request,这样没问题。但 Filament 还能提供前端校验,让用户在发起任何后端请求之前就能修正错误。

在 Filament 中,可通过 required()maxLength() 等方法为字段添加校验规则。相比 Laravel 的校验语法,这样做还有一个好处:IDE 可以自动补全这些方法:

php
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;

TextInput::make('name')
    ->required()
    ->maxLength(255)

本例中,字段使用了 required(),并设置了 maxLength()。我们对 Laravel 大多数校验规则都提供了对应方法,你甚至可以添加自己的 自定义规则

设置字段标签

默认情况下,字段标签会根据其名称自动确定。若要覆盖字段标签,可使用 label() 方法:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->label('Full name')

TIP

除了允许静态值外,label() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

以这种方式自定义标签很有用,尤其是当你希望使用 用于本地化的翻译字符串 时:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->label(__('fields.name'))

TIP

你也可以使用 JavaScript 表达式 来决定标签内容,该表达式可以读取表单中字段的当前值。

隐藏字段标签

或许会想把标签设为空字符串来隐藏它,但不推荐这样做。即使视觉上用途很明确,空字符串标签也无法向屏幕阅读器传达字段用途。应改用 hiddenLabel() 方法,使其在视觉上隐藏,但对屏幕阅读器仍然可访问:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->hiddenLabel()
隐藏标签的表单字段隐藏标签的表单字段

可选地,你可以传入布尔值来控制是否隐藏标签:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->hiddenLabel(FeatureFlag::active())

TIP

除了允许静态值外,hiddenLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

设置字段的默认值

字段可以有默认值。默认值仅在 schema 在无数据的情况下加载时使用。在标准的 面板资源 中,默认值用于创建页,而非编辑页。要定义默认值,请使用 default() 方法:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->default('John')

TIP

除了允许静态值外,default() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

禁用字段

你可以禁用字段,以阻止用户编辑:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->disabled()
已禁用的表单字段已禁用的表单字段

可选地,你可以传入布尔值来控制是否禁用字段:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->disabled(! FeatureFlag::active())

TIP

除了允许静态值外,disabled() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

禁用字段会阻止其被保存。若希望仍保存但不可编辑,请使用 saved() 方法:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->disabled()
    ->saved()

DANGER

若选择在禁用时仍保存该字段,熟练用户仍可能通过操纵 Livewire 的 JavaScript 来修改字段值。

可选地,你可以传入布尔值来控制是否保存该字段:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->disabled()
    ->saved(FeatureFlag::active())

TIP

除了允许静态值外,saved() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

根据当前操作禁用字段

schema 的「操作(operation)」是当前对其执行的动作。若使用 面板资源,通常是 createeditview

你可以通过向 disabledOn() 方法传入操作,根据当前操作禁用字段:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->disabledOn('edit')

// is the same as

Toggle::make('is_admin')
    ->disabled(fn (string $operation): bool => $operation === 'edit')

你也可以向 disabledOn() 方法传入操作数组;若当前操作属于该数组中的任一操作,字段将被禁用:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->disabledOn(['edit', 'view'])
    
// is the same as

Toggle::make('is_admin')
    ->disabled(fn (string $operation): bool => in_array($operation, ['edit', 'view']))

WARNING

disabledOn() 方法会覆盖此前对 disabled() 方法的任何调用,反之亦然。

隐藏字段

你可以隐藏字段:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->hidden()

可选地,你可以传入布尔值来控制是否隐藏字段:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->hidden(! FeatureFlag::active())

TIP

除了允许静态值外,hidden() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

或者,你可以使用 visible() 方法来控制字段是否隐藏。在某些情况下,这可能让代码更易读:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->visible(FeatureFlag::active())

TIP

除了允许静态值外,visible() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

INFO

若同时使用了 hidden()visible(),两者都必须表明字段应可见,字段才会显示。

使用 JavaScript 隐藏字段

若需要根据用户交互隐藏字段,可使用 hidden()visible() 方法,并传入一个利用注入工具来判断是否应隐藏字段的函数:

php
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;

Select::make('role')
    ->options([
        'user' => 'User',
        'staff' => 'Staff',
    ])
    ->live()

Toggle::make('is_admin')
    ->hidden(fn (Get $get): bool => $get('role') !== 'staff')

本例中,role 字段设置了 live(),这意味着每次更改 role 字段时 schema 都会重新加载。这会使传给 hidden() 的函数重新求值;若 role 未设为 staff,则会隐藏 is_admin 字段。

然而,每次字段变化都重新加载 schema 会发起网络请求,因为无法在客户端重新运行 PHP 函数。这对性能并不理想。

或者,你可以编写 JavaScript,根据另一字段的值来隐藏字段。做法是向 hiddenJs() 方法传入 JavaScript 表达式:

php
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;

Select::make('role')
    ->options([
        'user' => 'User',
        'staff' => 'Staff',
    ])

Toggle::make('is_admin')
    ->hiddenJs(<<<'JS'
        $get('role') !== 'staff'
        JS)

虽然传给 hiddenJs() 的代码看起来很像 PHP,但它实际是 JavaScript。Filament 为 JavaScript 提供了 $get() 工具函数,行为与 PHP 版本非常相似,但不要求所依赖的字段为 live()

DANGER

传给 hiddenJs() 的任何 JavaScript 字符串都会在浏览器中执行,因此切勿将用户输入直接拼进该字符串,否则可能导致跨站脚本(XSS)漏洞。来自 $state$get() 的用户输入绝不应被当作 JavaScript 代码求值,但可作为字符串值安全使用,如上例所示。

也提供了 visibleJs() 方法,工作方式与 hiddenJs() 相同,但用于控制字段是否可见:

php
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;

Select::make('role')
    ->options([
        'user' => 'User',
        'staff' => 'Staff',
    ])

Toggle::make('is_admin')
    ->visibleJs(<<<'JS'
        $get('role') === 'staff'
        JS)

DANGER

传给 visibleJs() 的任何 JavaScript 字符串都会在浏览器中执行,因此切勿将用户输入直接拼进该字符串,否则可能导致跨站脚本(XSS)漏洞。来自 $state$get() 的用户输入绝不应被当作 JavaScript 代码求值,但可作为字符串值安全使用,如上例所示。

INFO

若同时使用了 hiddenJs()visibleJs(),两者都必须表明字段应可见,字段才会显示。

根据当前操作隐藏字段

schema 的「操作(operation)」是当前对其执行的动作。若使用 面板资源,通常是 createeditview

你可以通过向 hiddenOn() 方法传入操作,根据当前操作隐藏字段:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->hiddenOn('edit')
    
// is the same as

Toggle::make('is_admin')
    ->hidden(fn (string $operation): bool => $operation === 'edit')

你也可以向 hiddenOn() 方法传入操作数组;若当前操作属于该数组中的任一操作,字段将被隐藏:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->hiddenOn(['edit', 'view'])
    
// is the same as

Toggle::make('is_admin')
    ->hidden(fn (string $operation): bool => in_array($operation, ['edit', 'view']))

WARNING

hiddenOn() 方法会覆盖此前对 hidden() 方法的任何调用,反之亦然。

或者,你可以使用 visibleOn() 方法来控制字段是否隐藏。在某些情况下,这可能让代码更易读:

php
use Filament\Forms\Components\Toggle;

Toggle::make('is_admin')
    ->visibleOn('create')

Toggle::make('is_admin')
    ->visibleOn(['create', 'edit'])

INFO

visibleOn() 方法会覆盖此前对 visible() 方法的任何调用,反之亦然。

行内标签

字段标签可以与字段同行显示,而不是显示在上方。这对字段很多、纵向空间紧张的表单很有用。要让字段标签行内显示,请使用 inlineLabel() 方法:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->inlineLabel()
带行内标签的表单字段带行内标签的表单字段

可选地,你可以传入布尔值来控制标签是否行内显示:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->inlineLabel(FeatureFlag::active())

TIP

除了允许静态值外,inlineLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

一次在多处使用行内标签

若希望在 布局组件(如 分区(section)标签页(tab))中让所有标签都行内显示,可在该组件本身上使用 inlineLabel(),其内所有字段的标签都会行内显示:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;

Section::make('Details')
    ->inlineLabel()
    ->schema([
        TextInput::make('name'),
        TextInput::make('email')
            ->label('Email address'),
        TextInput::make('phone')
            ->label('Phone number'),
    ])
分区中带行内标签的表单字段分区中带行内标签的表单字段

你也可以在整个 schema 上使用 inlineLabel(),让所有标签都行内显示:

php
use Filament\Schemas\Schema;

public function form(Schema $schema): Schema
{
    return $schema
        ->inlineLabel()
        ->components([
            // ...
        ]);
}

在布局组件或 schema 上使用 inlineLabel() 时,仍可通过在单个字段上使用 inlineLabel(false) 退出行内标签:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;

Section::make('Details')
    ->inlineLabel()
    ->schema([
        TextInput::make('name'),
        TextInput::make('email')
            ->label('Email address'),
        TextInput::make('phone')
            ->label('Phone number')
            ->inlineLabel(false),
    ])

在 schema 加载时自动聚焦字段

大多数字段支持自动聚焦。通常,为获得最佳用户体验,应让 schema 中第一个重要字段自动聚焦。可使用 autofocus() 方法指定要自动聚焦的字段:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->autofocus()

可选地,你可以传入布尔值来控制字段是否自动聚焦:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->autofocus(FeatureFlag::active())

TIP

除了允许静态值外,autofocus() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

设置字段的占位符

许多字段在无值时可显示占位符。它会显示在 UI 中,但提交表单时不会保存。你可以使用 placeholder() 方法自定义该占位符:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->placeholder('John Doe')

TIP

除了允许静态值外,placeholder() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

带占位符的表单字段带占位符的表单字段

将字段融合为一组

FusedGroup 组件可用于将多个字段「融合」在一起。以下字段融合效果最佳:

要融合的字段会传给 FusedGroup 组件的 make() 方法:

php
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\FusedGroup;

FusedGroup::make([
    TextInput::make('city')
        ->placeholder('City'),
    Select::make('country')
        ->placeholder('Country')
        ->options([
            // ...
        ]),
])
融合的表单字段组融合的表单字段组

你可以使用 label() 方法在字段组上方添加标签:

php
use Filament\Schemas\Components\FusedGroup;

FusedGroup::make([
    // ...
])
    ->label('Location')
带标签的融合表单字段组带标签的融合表单字段组

默认情况下,每个字段独占一行。在移动设备上,这通常是最合适的体验;但在桌面上,你可以使用与 布局组件 相同的 columns() 方法,让字段横向排列:

php
use Filament\Schemas\Components\FusedGroup;

FusedGroup::make([
    // ...
])
    ->label('Location')
    ->columns(2)
分列显示的融合表单字段组分列显示的融合表单字段组

你可以通过向每个字段传入 columnSpan() 来调整网格中字段的宽度:

php
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\FusedGroup;

FusedGroup::make([
    TextInput::make('city')
        ->placeholder('City')
        ->columnSpan(2),
    Select::make('country')
        ->placeholder('Country')
        ->options([
            // ...
        ]),
])
    ->label('Location')
    ->columns(3)
自定义跨列的分列融合表单字段组自定义跨列的分列融合表单字段组

向字段添加额外内容

字段包含许多「插槽(slots)」,可在子 schema 中插入内容。插槽可接受文本、任意 schema 组件操作(actions)操作组。通常用 prime 组件 来放内容。

所有字段都可用以下插槽:

  • aboveLabel()
  • beforeLabel()
  • afterLabel()
  • belowLabel()
  • aboveContent()
  • beforeContent()
  • afterContent()
  • belowContent()
  • aboveErrorMessage()
  • belowErrorMessage()

TIP

除了允许静态值外,插槽方法也接受函数以动态计算。你可以将各种工具作为参数注入到这些函数中。

要插入纯文本,可以向这些方法传入字符串:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->belowContent('This is the user\'s full name.')
内容下方带文本的表单字段内容下方带文本的表单字段

要插入 schema 组件(通常是 prime 组件),可将该组件传给方法:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Text;
use Filament\Support\Enums\FontWeight;

TextInput::make('name')
    ->belowContent(Text::make('This is the user\'s full name.')->weight(FontWeight::Bold))
内容下方带组件的表单字段内容下方带组件的表单字段

要插入 操作操作组,可将操作或操作组传给方法:

php
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->belowContent(Action::make('generate'))
内容下方带操作的表单字段内容下方带操作的表单字段

TIP

若需要不发起网络请求、仅运行 JavaScript 的简单操作,可使用 actionJs() 方法。这对用 $get()$set() 更新表单字段值等简单交互很有用。使用 actionJs() 的操作无法打开模态框。

你可以通过向方法传入内容数组,将任意内容组合插入插槽:

php
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->belowContent([
        Icon::make(Heroicon::InformationCircle),
        'This is the user\'s full name.',
        Action::make('generate'),
    ])
内容下方带多个组件的表单字段内容下方带多个组件的表单字段

你也可以通过将内容数组传给 Schema::start()(默认)、Schema::end()Schema::between() 来对齐插槽中的内容:

php
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Flex;
use Filament\Schemas\Components\Icon;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->belowContent(Schema::end([
        Icon::make(Heroicon::InformationCircle),
        'This is the user\'s full name.',
        Action::make('generate'),
    ]))

TextInput::make('name')
    ->belowContent(Schema::between([
        Icon::make(Heroicon::InformationCircle),
        'This is the user\'s full name.',
        Action::make('generate'),
    ]))

TextInput::make('name')
    ->belowContent(Schema::between([
        Flex::make([
            Icon::make(Heroicon::InformationCircle)
                ->grow(false),
            'This is the user\'s full name.',
        ]),
        Action::make('generate'),
    ]))

TIP

如上例 Schema::between() 所示,使用 Flex 组件 将图标与文本组合在一起,使它们之间没有空隙。图标使用 grow(false) 以避免占据一半横向空间,从而让文本占用剩余空间。

内容下方带对齐组件的表单字段内容下方带对齐组件的表单字段

在字段标签上方添加额外内容

你可以使用 aboveLabel() 方法在字段标签上方插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->aboveLabel([
        Icon::make(Heroicon::Star),
        'This is the content above the field\'s label'
    ])

TIP

除了允许静态值外,aboveLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

标签上方带额外内容的表单字段标签上方带额外内容的表单字段

在字段标签之前添加额外内容

你可以使用 beforeLabel() 方法在字段标签之前插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->beforeLabel(Icon::make(Heroicon::Star))

TIP

除了允许静态值外,beforeLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

标签之前带额外内容的表单字段标签之前带额外内容的表单字段

在字段标签之后添加额外内容

你可以使用 afterLabel() 方法在字段标签之后插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->afterLabel([
        Icon::make(Heroicon::Star),
        'This is the content after the field\'s label'
    ])

TIP

除了允许静态值外,afterLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

标签之后带额外内容的表单字段标签之后带额外内容的表单字段

默认情况下,afterLabel() schema 中的内容会对齐到容器末端。若希望对齐到容器起始端,应传入包含内容的 Schema::start() 对象:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->afterLabel(Schema::start([
        Icon::make(Heroicon::Star),
        'This is the content after the field\'s label'
    ]))

TIP

除了允许静态值外,afterLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

标签之后靠起始端对齐额外内容的表单字段标签之后靠起始端对齐额外内容的表单字段

在字段标签下方添加额外内容

你可以使用 belowLabel() 方法在字段标签下方插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->belowLabel([
        Icon::make(Heroicon::Star),
        'This is the content below the field\'s label'
    ])

TIP

除了允许静态值外,belowLabel() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

标签下方带额外内容的表单字段标签下方带额外内容的表单字段

INFO

这看起来可能与 aboveContent() 方法 相同。但在使用 行内标签 时,aboveContent() 会把内容放在字段上方,而不是标签下方,因为标签与字段内容分列显示。

在字段内容上方添加额外内容

你可以使用 aboveContent() 方法在字段内容上方插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->aboveContent([
        Icon::make(Heroicon::Star),
        'This is the content above the field\'s content'
    ])

TIP

除了允许静态值外,aboveContent() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

内容上方带额外内容的表单字段内容上方带额外内容的表单字段

INFO

这看起来可能与 belowLabel() 方法 相同。但在使用 行内标签 时,belowLabel() 会把内容放在标签下方,而不是字段内容上方,因为标签与字段内容分列显示。

在字段内容之前添加额外内容

你可以使用 beforeContent() 方法在字段内容之前插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->beforeContent(Icon::make(Heroicon::Star))

TIP

除了允许静态值外,beforeContent() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

内容之前带额外内容的表单字段内容之前带额外内容的表单字段

TIP

某些字段(如 文本输入选择框日期时间选择器)提供 prefix() 方法,可在字段内容之前、紧贴字段本身插入内容。这通常比使用 beforeContent() 更合适的 UI 选择。

带前后缀的文本输入带前后缀的文本输入

在字段内容之后添加额外内容

你可以使用 afterContent() 方法在字段内容之后插入额外内容。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->afterContent(Icon::make(Heroicon::Star))

TIP

除了允许静态值外,afterContent() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

内容之后带额外内容的表单字段内容之后带额外内容的表单字段

TIP

某些字段(如 文本输入选择框日期时间选择器)提供 suffix() 方法,可在字段内容之后、紧贴字段本身插入内容。这通常比使用 beforeContent() 更合适的 UI 选择。

带前后缀的文本输入带前后缀的文本输入

在字段错误消息上方添加额外内容

你可以使用 aboveErrorMessage() 方法在字段的 错误消息 上方插入额外内容。除非显示了错误消息,否则它不可见。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->required()
    ->aboveErrorMessage([
        Icon::make(Heroicon::Star),
        'This is the content above the field\'s error message'
    ])

TIP

除了允许静态值外,aboveErrorMessage() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

错误消息上方带额外内容的表单字段错误消息上方带额外内容的表单字段

在字段错误消息下方添加额外内容

你可以使用 belowErrorMessage() 方法在字段的 错误消息 下方插入额外内容。除非显示了错误消息,否则它不可见。可以向该方法传入任意内容,如文本、schema 组件、操作或操作组:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Icon;
use Filament\Support\Icons\Heroicon;

TextInput::make('name')
    ->required()
    ->belowErrorMessage([
        Icon::make(Heroicon::Star),
        'This is the content below the field\'s error message'
    ])

TIP

除了允许静态值外,belowErrorMessage() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

错误消息下方带额外内容的表单字段错误消息下方带额外内容的表单字段

向字段添加额外 HTML 属性

你可以通过 extraAttributes() 方法向字段传入额外 HTML 属性,这些属性会合并到其外层 HTML 元素上。属性应以数组表示,键为属性名,值为属性值:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->extraAttributes(['title' => 'Text input'])

TIP

除了允许静态值外,extraAttributes() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

TIP

默认情况下,多次调用 extraAttributes() 会覆盖先前的属性。若希望改为合并属性,可向方法传入 merge: true

向字段的 input 元素添加额外 HTML 属性

某些字段底层使用 <input><select> DOM 元素,但该元素通常不是字段的最外层元素,因此 extraAttributes() 可能达不到预期效果。此时可使用 extraInputAttributes() 方法,它会将属性合并到字段 HTML 中的 <input><select> 元素上:

php
use Filament\Forms\Components\TextInput;

TextInput::make('categories')
    ->extraInputAttributes(['width' => 200])

TIP

除了允许静态值外,extraInputAttributes() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

TIP

默认情况下,多次调用 extraInputAttributes() 会覆盖先前的属性。若希望改为合并属性,可向方法传入 merge: true

向字段包装器添加额外 HTML 属性

你也可以向包围字段标签与内容的「字段包装器(field wrapper)」最外层元素传入额外 HTML 属性。若要通过 CSS 样式化标签或字段间距,这很有用,因为你可以把元素作为包装器的子元素来定位:

php
use Filament\Forms\Components\TextInput;

TextInput::make('categories')
    ->extraFieldWrapperAttributes(['class' => 'components-locked'])

TIP

除了允许静态值外,extraFieldWrapperAttributes() 方法也接受函数以动态计算。你可以将各种工具作为参数注入到该函数中。

TIP

默认情况下,多次调用 extraFieldWrapperAttributes() 会覆盖先前的属性。若希望改为合并属性,可向方法传入 merge: true

字段工具注入

绝大多数用于配置字段的方法都接受函数作为参数,而不是硬编码值:

php
use App\Models\User;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;

DatePicker::make('date_of_birth')
    ->displayFormat(function (): string {
        if (auth()->user()->country_id === 'us') {
            return 'm/d/Y';
        }

        return 'd/m/Y';
    })

Select::make('user_id')
    ->options(function (): array {
        return User::query()->pluck('name', 'id')->all();
    })

TextInput::make('middle_name')
    ->required(fn (): bool => auth()->user()->hasMiddleName())

仅此一点就解锁了许多自定义可能。

该包还能以参数形式向这些函数注入许多可用工具。所有接受函数作为参数的自定义方法都可以注入工具。

这些注入的工具要求使用特定的参数名。否则 Filament 不知道要注入什么。

注入字段的当前状态

若要访问字段的当前值(状态),请定义 $state 参数:

php
function ($state) {
    // ...
}

注入字段的原始状态

若字段会自动将其状态转换为更有用的格式,你可能希望访问原始状态。为此,请定义 $rawState 参数:

php
function ($rawState) {
    // ...
}

注入另一字段的状态

你也可以在回调中使用 $get 参数获取另一字段的状态(值):

php
use Filament\Schemas\Components\Utilities\Get;

function (Get $get) {
    $email = $get('email'); // Store the value of the `email` field in the `$email` variable.
    //...
}

TIP

除非表单字段是响应式的,否则字段值变化时 schema 不会刷新,只会在下一次会向服务器发请求的用户交互时刷新。若需要响应字段值的变化,应将其设为 live()

类型安全地获取另一字段的状态

你可以在 Get 工具上使用「类型化」方法,以类型安全的方式获取另一字段的状态:

php
use Filament\Schemas\Components\Utilities\Get;

$get->string('email');
$get->integer('age');
$get->float('price');
$get->boolean('is_admin');
$get->array('tags');
$get->date('published_at');
$get->enum('status', StatusEnum::class);
$get->filled('email'); // Returns the result of the `filled()` helper for the field.
$get->blank('email'); // Returns the result of the `blank()` helper for the field.

每个方法都假定字段状态不能为 null。若要强制可空返回类型,请传入 isNullable: true 参数:

php
use Filament\Schemas\Components\Utilities\Get;

$get->string('email', isNullable: true);

注入当前 Eloquent 记录

你可以使用 $record 参数获取当前 schema 的 Eloquent 记录:

php
use Illuminate\Database\Eloquent\Model;

function (?Model $record) {
    // ...
}

注入当前操作

若正在为面板资源或关系管理器编写 schema,并希望检查 schema 是 createedit 还是 view,请使用 $operation 参数:

php
function (string $operation) {
    // ...
}

INFO

你可以使用 $schema->operation() 方法手动设置 schema 的操作。

注入当前 Livewire 组件实例

若要访问当前 Livewire 组件实例,请定义 $livewire 参数:

php
use Livewire\Component;

function (Component $livewire) {
    // ...
}

注入当前字段实例

若要访问当前组件实例,请定义 $component 参数:

php
use Filament\Forms\Components\Field;

function (Field $component) {
    // ...
}

注入多个工具

参数通过反射动态注入,因此你可以按任意顺序组合多个参数:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Livewire\Component as Livewire;

function (Livewire $livewire, Get $get, Set $set) {
    // ...
}

从 Laravel 容器注入依赖

你可以像往常一样从 Laravel 容器注入任意内容,并与工具一起使用:

php
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Http\Request;

function (Request $request, Set $set) {
    // ...
}

使用 JavaScript 决定文本内容

允许渲染 HTML 的方法,例如 label() 以及传给 belowContent()Text::make(),也可以改用 JavaScript 计算其内容。做法是向方法传入实现了 HtmlableJsContent 对象:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\JsContent;

TextInput::make('greetingResponse')
    ->label(JsContent::make(<<<'JS'
        ($get('name') === 'John Doe') ? 'Hello, John!' : 'Hello, stranger!'
        JS
    ))

在此 JavaScript 上下文中可使用 $state$get 工具,因此你可以用它们访问该字段以及 schema 中其他字段的状态。

DANGER

传给 JsContent 的字符串会在浏览器中求值,因此切勿将用户输入拼进其中——否则会导致 XSS。通过 $state$get() 在运行时读取的值可作为表达式中的字符串值安全使用,但绝不应被当作 JavaScript 代码本身求值。

响应式基础

Livewire 是一款可让 Blade 渲染的 HTML 动态重新渲染、而无需整页刷新的工具。Filament schema 构建于 Livewire 之上,因此能够动态重新渲染,使内容在初次渲染后仍可适应变化。

默认情况下,用户操作字段时 schema 不会重新渲染。因为渲染需要与服务器往返,这是一种性能优化。但若希望在用户与字段交互后重新渲染 schema,可使用 live() 方法:

php
use Filament\Forms\Components\Select;

Select::make('status')
    ->options([
        'draft' => 'Draft',
        'reviewing' => 'Reviewing',
        'published' => 'Published',
    ])
    ->live()

本例中,当用户更改 status 字段的值时,schema 会重新渲染。这样你就可以根据 status 的新值调整 schema 中的其他字段。你也可以挂接到字段的生命周期,在字段更新时执行自定义逻辑。

失焦时响应的字段

默认情况下,字段设为 live() 后,每次与字段交互时 schema 都会重新渲染。但对某些字段(如文本输入)这可能不合适,因为用户仍在输入时就发网络请求会导致性能不佳。你可能希望仅在用户用完字段、字段失焦后再重新渲染 schema。可使用 live(onBlur: true) 方法实现:

php
use Filament\Forms\Components\TextInput;

TextInput::make('username')
    ->live(onBlur: true)

响应式字段的防抖

你可能希望在 live()live(onBlur: true) 之间取折中,即使用「防抖(debouncing)」。防抖会在用户停止输入一段时间后才发送网络请求。可使用 live(debounce: 500) 方法实现:

php
use Filament\Forms\Components\TextInput;

TextInput::make('username')
    ->live(debounce: 500) // Wait 500ms before re-rendering the schema.

本例中,500 是发送网络请求前等待的毫秒数。你可以自定义该数值,甚至使用如 '1s' 这样的字符串。

字段生命周期

schema 中的每个字段都有生命周期,即 schema 加载时、用户交互时以及提交时所经历的过程。你可以使用在各阶段运行的函数,自定义生命周期每一阶段发生的事。

字段注水(hydration)

注水(Hydration)是用数据填充字段的过程,在调用 schema 的 fill() 方法时运行。你可以使用 afterStateHydrated() 方法自定义字段注水后发生的事。

本例中,name 字段注水时总是会带上正确大小写的姓名:

php
use Closure;
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->required()
    ->afterStateHydrated(function (TextInput $component, string $state) {
        $component->state(ucwords($state));
    })

作为在注水时这样格式化字段状态的快捷方式,你可以使用 formatStateUsing() 方法:

php
use Closure;
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->formatStateUsing(fn (string $state): string => ucwords($state))

字段更新

你可以使用 afterStateUpdated() 方法自定义用户更新字段后发生的事:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->afterStateUpdated(function (?string $state, ?string $old) {
        // ...
    })

TIP

afterStateUpdated() 方法会将各种工具作为参数注入到该函数中。

TIP

在响应式字段上使用 afterStateUpdated() 时,由于会发起网络请求,交互不会感觉即时。有几种方式可以优化并避免渲染,使交互感觉更快。

设置另一字段的状态

$get 类似,你也可以在 afterStateUpdated() 中使用 $set 参数设置另一字段的值:

php
use Filament\Schemas\Components\Utilities\Set;

function (Set $set) {
    $set('title', 'Blog Post'); // Set the `title` field to `Blog Post`.
    //...
}

运行该函数时,title 字段的状态会更新,schema 会以新标题重新渲染。

默认情况下,使用 $set() 时,被设置字段的 afterStateUpdated() 方法不会被调用。若希望调用它,可传入 shouldCallUpdatedHooks: true 参数:

php
use Filament\Schemas\Components\Utilities\Set;

function (Set $set) {
    $set('title', 'Blog Post', shouldCallUpdatedHooks: true);
    //...
}

字段脱水(dehydration)

脱水(Dehydration)是从 schema 中的字段获取数据、可选地转换并返回的过程。在调用 schema 的 getState() 方法时运行,通常在表单提交时调用。

你可以使用 dehydrateStateUsing() 函数自定义脱水时状态的转换方式。本例中,name 字段脱水时总是会带上正确大小写的姓名:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->required()
    ->dehydrateStateUsing(fn (string $state): string => ucwords($state))

阻止字段被保存

你可以使用 saved(false) 完全阻止字段被保存。本例中,该字段不会出现在 getState() 返回的数组中,与该字段关联的任何关系也不会被保存:

php
use Filament\Forms\Components\TextInput;

TextInput::make('password_confirmation')
    ->password()
    ->saved(false)

若你的 schema 会自动将数据保存到数据库(例如在 资源 中),这很有用:可阻止仅用于展示目的的字段被写入数据库。

INFO

即使字段未被保存,仍会进行校验。要了解更多,请参阅 校验 部分。

字段渲染

每次更新响应式字段时,该 schema 所属的整个 Livewire 组件的 HTML 都会重新生成,并通过网络请求发送到前端。在某些情况下这可能过头了,尤其是 schema 很大且只有部分组件发生变化时。

字段部分渲染

本例中,「name」输入的值用于「email」输入的标签。「name」输入是 live() 的,因此用户在「name」中输入时,整个 schema 都会重新渲染。这并不理想,因为其实只需重新渲染「email」输入:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;

TextInput::make('name')
    ->live()
    
TextInput::make('email')
    ->label(fn (Get $get): string => filled($get('name')) ? "Email address for {$get('name')}" : 'Email address')

此时,简单调用 partiallyRenderComponentsAfterStateUpdated() 并传入要重新渲染的其他字段名,即可让 schema 仅在状态更新后重新渲染指定字段:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->live()
    ->partiallyRenderComponentsAfterStateUpdated(['email'])

或者,你可以使用 partiallyRenderAfterStateUpdated() 指示 Filament 仅重新渲染当前组件。若只有该响应式组件依赖其当前状态,这很有用:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->live()
    ->partiallyRenderAfterStateUpdated()
    ->belowContent(fn (Get $get): ?string => filled($get('name')) ? "Hi, {$get('name')}!" : null)

阻止字段更新后渲染 Livewire 组件

若希望在字段更新时阻止 Livewire 组件重新渲染,可使用 skipRenderAfterStateUpdated() 方法。若要在字段更新时执行某些操作但不希望 Livewire 组件重新渲染,这很有用:

php
use Filament\Forms\Components\TextInput;

TextInput::make('name')
    ->live()
    ->skipRenderAfterStateUpdated()
    ->afterStateUpdated(function (string $state) {
        // Do something with the state, but don't re-render the Livewire component.
    })

由于在 afterStateUpdated() 函数中用 $set() 设置另一字段的状态实际上只会变更前端字段状态,一开始甚至不需要网络请求。afterStateUpdatedJs() 方法接受一个 JavaScript 表达式,每次字段值变化时都会运行。JavaScript 上下文中可使用 $state$get()$set() 工具,因此你可以用它们设置其他字段的状态:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Set;

// Old name input that is `live()`, so it makes a network request and render each time it is updated.
TextInput::make('name')
    ->live()
    ->afterStateUpdated(fn (Set $set, ?string $state) => $set('email', ((string) str($state)->replace(' ', '.')->lower()) . '@example.com'))

// New name input that uses `afterStateUpdatedJs()` to set the state of the email field and doesn't make a network request.
TextInput::make('name')
    ->afterStateUpdatedJs(<<<'JS'
        $set('email', ($state ?? '').replaceAll(' ', '.').toLowerCase() + '@example.com')
        JS)
    
TextInput::make('email')
    ->label('Email address')

DANGER

传给 afterStateUpdatedJs() 的任何 JavaScript 字符串都会在浏览器中执行,因此切勿将用户输入直接拼进该字符串,否则可能导致跨站脚本(XSS)漏洞。来自 $state$get() 的用户输入绝不应被当作 JavaScript 代码求值,但可作为字符串值安全使用,如上例所示。

响应式表单手册

本节汇集了构建高级表单时可能需要完成的常见任务配方。

有条件地隐藏字段

要有条件地隐藏或显示字段,可向 hidden() 方法传入函数,并根据是否要隐藏字段返回 truefalse。该函数可以注入工具作为参数,因此你可以检查另一字段的值等:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\TextInput;

Checkbox::make('is_company')
    ->live()

TextInput::make('company_name')
    ->hidden(fn (Get $get): bool => ! $get('is_company'))

本例中,is_company 复选框是 live() 的。这允许在 is_company 字段值变化时重新渲染 schema。你可以在 hidden() 函数中使用 $get() 工具 访问该字段的值。用 ! 取反,使得当 is_companyfalse 时隐藏 company_name 字段。

或者,你可以使用 visible() 方法有条件地显示字段。它与 hidden() 正好相反;若你更喜欢这种写法的清晰度,可以使用它:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\TextInput;

Checkbox::make('is_company')
    ->live()
    
TextInput::make('company_name')
    ->visible(fn (Get $get): bool => $get('is_company'))

TIP

使用 live() 意味着每次字段变化时 schema 都会重新加载,从而触发网络请求。 或者,你可以使用 JavaScript 根据另一字段的值隐藏字段

有条件地设字段为必填

要有条件地设字段为必填,可向 required() 方法传入函数,并根据是否要设为必填返回 truefalse。该函数可以注入工具作为参数,因此你可以检查另一字段的值等:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Components\TextInput;

TextInput::make('company_name')
    ->live(onBlur: true)
    
TextInput::make('vat_number')
    ->required(fn (Get $get): bool => filled($get('company_name')))

本例中,company_name 字段是 live(onBlur: true) 的。这允许在 company_name 值变化且用户点击离开后重新渲染 schema。你可以在 required() 函数中使用 $get() 工具 访问该字段的值。用 filled() 检查该值,使得当 company_name 不是 null 或空字符串时,vat_number 为必填。结果是:仅当填写了 company_name 时,vat_number 才必填。

使用函数也可以用类似方式让其他任意 校验规则 变为动态。

从标题生成 slug

要在用户输入时从标题生成 slug,可在标题字段上使用 afterStateUpdated() 方法,通过 $set() 设置 slug 字段的值:

php
use Filament\Schemas\Components\Utilities\Set;
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Str;

TextInput::make('title')
    ->live(onBlur: true)
    ->afterStateUpdated(fn (Set $set, ?string $state) => $set('slug', Str::slug($state)))
    
TextInput::make('slug')

本例中,title 字段是 live(onBlur: true) 的。这允许在 title 值变化且用户点击离开时重新渲染 schema。使用 afterStateUpdated()title 状态更新后运行函数。该函数注入 $set() 工具 以及 title 的新状态。Str::slug() 是 Laravel 的工具方法,用于从字符串生成 slug。然后用 $set() 更新 slug 字段。

需要注意的是,用户可能手动自定义 slug;若标题变化,我们不希望覆盖他们的修改。为防止这种情况,可以用旧版标题判断用户是否自行修改过。要访问旧版标题,可注入 $old;要在变更前获取 slug 的当前值,可使用 $get() 工具

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Str;

TextInput::make('title')
    ->live(onBlur: true)
    ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) {
        if (($get('slug') ?? '') !== Str::slug($old)) {
            return;
        }
    
        $set('slug', Str::slug($state));
    })
    
TextInput::make('slug')

依赖的选择选项

要根据另一字段的值动态更新 选择字段 的选项,可向选择字段的 options() 方法传入函数。该函数可以注入工具作为参数,因此你可以使用 $get() 工具 检查另一字段的值等:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Components\Select;

Select::make('category')
    ->options([
        'web' => 'Web development',
        'mobile' => 'Mobile development',
        'design' => 'Design',
    ])
    ->live()

Select::make('sub_category')
    ->options(fn (Get $get): array => match ($get('category')) {
        'web' => [
            'frontend_web' => 'Frontend development',
            'backend_web' => 'Backend development',
        ],
        'mobile' => [
            'ios_mobile' => 'iOS development',
            'android_mobile' => 'Android development',
        ],
        'design' => [
            'app_design' => 'Panel design',
            'marketing_website_design' => 'Marketing website design',
        ],
        default => [],
    })

本例中,category 字段是 live() 的。这允许在 category 值变化时重新渲染 schema。你可以在 options() 函数中使用 $get() 工具 访问该字段的值,并据此决定 sub_category 应有哪些选项。PHP 的 match () 语句根据 category 的值返回选项数组。结果是:sub_category 只会显示与所选 category 相关的选项。

你也可以改造本例,在函数内查询,从 Eloquent 模型或其他数据源加载选项:

php
use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Components\Select;
use Illuminate\Support\Collection;

Select::make('category')
    ->options(Category::query()->pluck('name', 'id'))
    ->live()
    
Select::make('sub_category')
    ->options(fn (Get $get): Collection => SubCategory::query()
        ->where('category', $get('category'))
        ->pluck('name', 'id'))

基于选择选项的动态字段

你可能希望根据字段(如选择框)的值渲染不同的字段集合。为此,可向任意 布局组件schema() 方法传入函数,检查该字段的值并根据该值返回不同的 schema。此外,还需要在动态 schema 中的新字段首次加载时初始化它们。

php
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Utilities\Get;

Select::make('type')
    ->options([
        'employee' => 'Employee',
        'freelancer' => 'Freelancer',
    ])
    ->live()
    ->afterStateUpdated(fn (Select $component) => $component
        ->getContainer()
        ->getComponent('dynamicTypeFields')
        ->getChildSchema()
        ->fill())
    
Grid::make(2)
    ->schema(fn (Get $get): array => match ($get('type')) {
        'employee' => [
            TextInput::make('employee_number')
                ->required(),
            FileUpload::make('badge')
                ->image()
                ->required(),
        ],
        'freelancer' => [
            TextInput::make('hourly_rate')
                ->numeric()
                ->required()
                ->prefix('€'),
            FileUpload::make('contract')
                ->required(),
        ],
        default => [],
    })
    ->key('dynamicTypeFields')

本例中,type 字段是 live() 的。这允许在 type 值变化时重新渲染 schema。使用 afterStateUpdated()type 状态更新后运行函数。此处我们注入当前选择字段实例,再用它获取同时容纳选择框与网格组件的 schema「容器」实例。有了该容器,可用我们赋给网格组件的唯一键(dynamicTypeFields)定位它。拿到该网格组件实例后,可像普通表单一样调用 fill() 进行初始化。网格组件的 schema() 方法再根据 type 的值返回不同的 schema:通过 $get() 工具 动态返回不同的 schema 数组。

自动哈希密码字段

你有一个密码字段:

php
use Filament\Forms\Components\TextInput;

TextInput::make('password')
    ->password()

你可以使用 脱水函数 在表单提交时对密码进行哈希:

php
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Facades\Hash;

TextInput::make('password')
    ->password()
    ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))

但若 schema 用于更改已有密码,字段为空时不应覆盖现有密码。若字段为 null 或空字符串,可使用 filled() 辅助函数阻止该字段被保存

php
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Facades\Hash;

TextInput::make('password')
    ->password()
    ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
    ->saved(fn (?string $state): bool => filled($state))

不过,在创建用户时应要求填写密码:可通过注入 $operation 工具,然后有条件地设字段为必填

php
use Filament\Forms\Components\TextInput;
use Illuminate\Support\Facades\Hash;

TextInput::make('password')
    ->password()
    ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
    ->saved(fn (?string $state): bool => filled($state))
    ->required(fn (string $operation): bool => $operation === 'create')

INFO

本例中,Hash::make($state) 演示了如何使用 脱水函数。但若你的模型在 casts 中使用 'password' => 'hashed' —— Laravel 会自动处理哈希,则不必这样做。

将数据保存到关系

除了能为字段提供结构外,布局组件 还能将其嵌套字段「传送」到关系中。Filament 会处理从 HasOneBelongsToMorphOne Eloquent 关系加载数据,再将数据保存回同一关系。要启用此行为,可在任意布局组件上使用 relationship() 方法:

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

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

本例中,titledescriptionimage 会自动从 metadata 关系加载,并在表单提交时再次保存。若 metadata 记录不存在,会自动创建。

此功能不仅限于 fieldset——可用于任意布局组件。例如,你可以使用没有关联样式的 Group 组件:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Group;

Group::make()
    ->relationship('customer')
    ->schema([
        TextInput::make('name')
            ->label('Customer')
            ->required(),
        TextInput::make('email')
            ->label('Email address')
            ->email()
            ->required(),
    ])

将数据保存到 BelongsToMorphTo 关系

请注意:若将数据保存到 BelongsToMorphTo 关系,数据库中的外键列必须为 nullable()。因为 Filament 会先保存 schema,再保存关系。由于 schema 先保存,外键 ID 尚不存在,因此必须可空。schema 保存后,Filament 会立即保存关系,填入外键 ID 并再次保存。

值得注意的是:若 schema 模型上有观察者,可能需要调整它,确保创建时不依赖关系已存在。例如,若有观察者在 schema 创建时向关联记录发邮件,可能需要改用在关系关联之后运行的钩子,如 updated()

若使用 MorphTo 关系,并希望 Filament 能创建 MorphTo 记录(而不仅仅是更新),需通过 relationship() 方法的 relatedModel 参数指定关联模型:

php
use App\Models\Organization;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Group;

Group::make()
    ->relationship('customer', relatedModel: Organization::class)
    ->schema([
        // ...
    ])

本例中,customerMorphTo 关系,可能是 IndividualOrganization。通过指定 relatedModel 参数,Filament 能在表单提交时创建 Organization 记录。若不指定该参数,Filament 只能更新已有记录。

TIP

relatedModel 参数也接受返回关联模型类名的函数。若要根据表单当前状态动态确定关联模型,这很有用。你可以将各种工具注入到该函数中。

有条件地将数据保存到关系

有时,保存关联记录可以是可选的。若用户填写了客户字段,则创建/更新客户;否则不创建客户,若已存在则删除。为此,可向 relationship() 传入 condition 函数参数,它可使用关联表单的 $state 判断是否应保存该关系:

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Group;

Group::make()
    ->relationship(
        'customer',
        condition: fn (?array $state): bool => filled($state['name']),
    )
    ->schema([
        TextInput::make('name')
            ->label('Customer'),
        TextInput::make('email')
            ->label('Email address')
            ->email()
            ->requiredWith('name'),
    ])

本例中,客户姓名不是 required(),邮箱仅在填写了 name 时必填。condition 函数用于检查 name 是否已填写;若已填写则创建/更新客户,否则不创建,若已存在则删除。

组件隐藏时仍保存关系数据

默认情况下,若使用 relationship() 的布局组件在表单提交时处于隐藏状态,Filament 会完全跳过它——不会创建或更新关联记录,已有记录保持不变。这通常正是你想要的,因为隐藏组件没有可保存的状态。

若需要在组件隐藏时仍让 Filament 保存关系——例如字段值由 默认值 填充——请调用 saveRelationshipsWhenHidden()

php
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Group;

Group::make()
    ->relationship('metadata')
    ->saveRelationshipsWhenHidden()
    ->hidden()
    ->schema([
        TextInput::make('source')
            ->default('admin'),
    ])

WARNING

saveRelationshipsWhenHidden() 与在组件隐藏时返回 falsecondition 组合使用,会在表单提交时删除任何已有关联记录。若只想在组件隐藏时跳过保存,请省略 saveRelationshipsWhenHidden(),改用默认行为。

全局设置

若希望全局更改字段的默认行为,可在服务提供者的 boot() 方法或中间件中调用静态方法 configureUsing(),并传入能修改组件的闭包。例如,若希望所有 复选框使用 inline(false),可以这样做:

php
use Filament\Forms\Components\Checkbox;

Checkbox::configureUsing(function (Checkbox $checkbox): void {
    $checkbox->inline(false);
});

当然,你仍可以在每个字段上单独覆盖此行为:

php
use Filament\Forms\Components\Checkbox;

Checkbox::make('is_admin')
    ->inline()