news 2026/9/10 21:52:40

Filament 表单验证完全指南:字段级校验规则、前端实时反馈与数据库唯一性检查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Filament 表单验证完全指南:字段级校验规则、前端实时反馈与数据库唯一性检查

Filament 表单验证完全指南:字段级校验规则、前端实时反馈与数据库唯一性检查

【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament

表单验证是 Filament(Laravel + Livewire 开源 UI 框架)构建后台应用时保障数据质量的第一道防线。本文以 packages/forms/docs/23-validation.md 为骨架,系统讲解 Filament 表单字段支持的近 50 种内置校验方法、字段间比较、数据库唯一性/存在性校验(含scopedUnique/scopedExists的全局作用域处理)、自定义规则与错误消息定制,并深入CanBeValidatedtrait 源码,说明这些声明式 API 最终如何被编译为 Laravel Validator 规则并在前端实时生效。读完本文,你将能够在任意 Filament 表单字段上组合出完整、安全、可复用的验证逻辑。

一、为什么需要字段级验证

在 Laravel 中,验证规则通常以数组['required', 'max:255']或组合字符串required|max:255的形式定义,这在后端配合 FormRequest 使用时没有问题。但 Filament 更进一步:它能够把验证规则同步到前端,让用户在发起任何后端请求之前就能看到错误提示并即时修正,提升表单交互体验。

Filament 为字段提供了两类能力:

  • 专用验证方法(即下文"可用规则"一节),例如->required()->email()
  • 透传 Laravel 原生规则,包括任意 其他 Laravel 验证规则 与 自定义规则。

⚠️ 注意:某些默认的 Laravel 验证规则依赖正确的 attribute 名称,直接通过rule()/rules()传入时可能无法正常工作。只要能用专用验证方法,就优先使用专用方法。

二、内置可用规则(Available Rules)

所有专用验证方法都定义在 packages/forms/src/Components/Concerns/CanBeValidated.php 的CanBeValidatedtrait 中。以下按用途分组,逐一说明语义、参数与代码示例。

2.1 基础格式类规则

方法校验语义
activeUrl()根据 PHPdns_get_record()判断字段必须拥有有效的 A 或 AAAA 记录(真实可访问的域名)
alpha()字段必须全部为字母字符
alphaDash()字段可包含字母数字字符,以及短横线-和下划线_
alphaNum()字段必须全部为字母数字字符
ascii()字段必须全部为 7 位 ASCII 字符
json()字段必须是合法的 JSON 字符串
hexColor()字段必须是合法的十六进制颜色值
macAddress()字段必须是合法的 MAC 地址
string()字段必须是字符串
ulid()字段必须是合法的 ULID(通用唯一字典序可排序标识符)
uuid()字段必须是合法的 RFC 4122(版本 1、3、4 或 5)UUID
ip()/ipv4()/ipv6()字段必须是 IP 地址 / IPv4 / IPv6 地址
regex()/notRegex()字段必须匹配 / 不得匹配给定的正则表达式

基础格式规则的使用示例:

Field::make('name')->activeUrl() Field::make('name')->alpha() Field::make('name')->alphaDash() Field::make('name')->alphaNum() Field::make('name')->ascii() Field::make('ip_address')->ip() Field::make('ip_address')->ipv4() Field::make('ip_address')->ipv6() Field::make('ip_address')->json() Field::make('mac_address')->macAddress() Field::make('number')->multipleOf(2) Field::make('email')->regex('/^.+@.+$/i') Field::make('email')->notRegex('/^.+$/i') Field::make('identifier')->ulid() Field::make('identifier')->uuid() Field::make('color')->hexColor()

从源码看,这些方法大多是对rule()的薄封装:activeUrl()内部执行$this->rule('active_url', $condition)alpha()执行$this->rule('alpha', $condition),以此类推。每个方法都接受一个bool | Closure $condition = true参数,意味着你可以传闭包实现条件化校验(例如仅当满足某条件时才启用该规则)。

2.2 日期比较类规则

方法校验语义
after($date)字段值必须是给定日期之后的日期
afterOrEqual($date)字段值必须是大于或等于给定日期的日期
before($date)字段值必须是给定日期之前的日期
beforeOrEqual($date)字段值必须是小于或等于给定日期的日期

日期参数支持两类取值:字符串日期(会被strtotime()解析,如'tomorrow''first day of next month')或另一个字段的名称(用于字段间日期比较):

// 与具体日期比较 Field::make('start_date')->after('tomorrow') Field::make('start_date')->afterOrEqual('tomorrow') Field::make('start_date')->before('first day of next month') Field::make('start_date')->beforeOrEqual('end of this month') // 与另一字段比较 Field::make('start_date') Field::make('end_date')->after('start_date') Field::make('start_date') Field::make('end_date')->afterOrEqual('start_date') Field::make('start_date')->before('end_date') Field::make('end_date') Field::make('start_date')->beforeOrEqual('end_date') Field::make('end_date')

源码实现要点:dateComparisonRule()会先strtotime($date)判断传入的是否为可解析日期;若不是日期且未设置isStatePathAbsolute,则通过resolveRelativeStatePath()将其解析为同表单内其他字段的相对 state 路径,最终编译成after:end_date这样的 Laravel 规则字符串。

2.3 字段间比较类规则

方法校验语义
confirmed()字段必须存在匹配的{field}_confirmation字段
different($field)字段值必须与另一个字段不同
same($field)字段值必须与另一个字段相同
gt($field)字段值必须大于另一个字段
gte($field)字段值必须大于或等于另一个字段
lt($field)字段值必须小于另一个字段
lte($field)字段值必须小于或等于另一个字段
// 密码确认:需要一个名为 password_confirmation 的字段 Field::make('password')->confirmed() Field::make('password_confirmation') // 备份邮箱不得与主邮箱相同 Field::make('backup_email')->different('email') // 密码确认 Field::make('password')->same('passwordConfirmation') // 数值大小比较 Field::make('newNumber')->gt('oldNumber') Field::make('newNumber')->gte('oldNumber') Field::make('newNumber')->lt('oldNumber') Field::make('newNumber')->lte('oldNumber')

这些方法在源码中统一走fieldComparisonRule():将传入的字段名解析为相对 state 路径后编译成gt:oldNumber形式。比较规则的实现同时支持multiFieldComparisonRule()(多字段)与multiFieldValueComparisonRule()(字段+值,如required_if),其中prohibitedIfprohibitedUnlessrequiredIfrequiredUnless等均属后者,并且会自动把BackedEnum转换为->value标量后再拼进规则字符串。

2.4 集合与枚举类规则

方法校验语义
in($values)字段必须包含在给定值列表中
notIn($values)字段必须不在给定值列表中
enum(EnumClass::class)字段必须是给定枚举类的合法值
startsWith($values)字段必须以给定的某个值开头
endsWith($values)字段必须以给定的某个值结尾
doesntStartWith($values)字段不得以给定的某个值开头
doesntEndWith($values)字段不得以给定的某个值结尾
Field::make('status')->in(['pending', 'completed']) Field::make('status')->notIn(['cancelled', 'rejected']) Field::make('status')->enum(MyStatus::class) Field::make('name')->startsWith(['a']) Field::make('name')->endsWith(['bot']) Field::make('name')->doesntStartWith(['admin']) Field::make('name')->doesntEndWith(['admin'])

提示:toggle buttons、checkbox list、radio 与 select 字段会根据自身可用选项自动应用in()规则,无需手动添加。源码中getInValidationRule()会在存在inValidationRuleValues时返回Rule::in($values),否则在设置了枚举时返回Rule::enum($enum)(见 CanBeValidated.php)。此外mutateStateForValidation()会把枚举对象转为标量值,以满足 Laravelin规则对标量入参的要求。

2.5 必填、可选与禁止类规则

方法校验语义
required()字段值不得为空
nullable()字段值可以为空(未加required时默认如此)
filled()字段存在时不得为空
prohibited()字段值必须为空
prohibitedIf($field, $values)仅当另一字段为给定值时,本字段必须为空
prohibitedUnless($field, $values)除非另一字段为给定值,否则本字段必须为空
prohibits($fields)若本字段非空,则所有其他指定字段必须为空
requiredIf($field, $values)仅当另一字段为给定值时,本字段不得为空
requiredIfAccepted($field)仅当另一字段等于 yes、on、1、"1"、true 或 "true" 时,本字段不得为空
requiredUnless($field, $values)除非另一字段为给定值,否则本字段不得为空
requiredWith($fields)仅当任一其他指定字段非空时,本字段不得为空
requiredWithAll($fields)仅当所有其他指定字段均非空时,本字段不得为空
requiredWithout($fields)仅当任一其他指定字段为空时,本字段不得为空
requiredWithoutAll($fields)仅当所有其他指定字段均为空时,本字段不得为空
Field::make('name')->required() Field::make('name')->nullable() Field::make('name')->filled() Field::make('name')->prohibited() Field::make('name')->prohibitedIf('field', 'value') Field::make('name')->prohibitedUnless('field', 'value') Field::make('name')->prohibits('field') Field::make('name')->prohibits(['field', 'another_field']) Field::make('name')->requiredIf('field', 'value') Field::make('name')->requiredIfAccepted('field') Field::make('name')->requiredUnless('field', 'value') Field::make('name')->requiredWith('field,another_field') Field::make('name')->requiredWithAll('field,another_field') Field::make('name')->requiredWithout('field,another_field') Field::make('name')->requiredWithoutAll('field,another_field')

源码细节:required()并不直接产生规则字符串,而是设置isRequired标志;真正编译规则时,getRequiredValidationRule()依据isRequired()返回'required''nullable'——这正是"未标记 required 的字段默认可空"的底层实现(CanBeValidated.php)。requiredIf等条件规则由multiFieldValueComparisonRule()编译为required_if:field,value形式,支持传BackedEnum值(自动取->value)。

标记字段为必填(Marking a field as required)

默认情况下,必填字段的标签旁会显示星号*。在"所有字段都必填"的表单上你可能想隐藏星号;反之,对非必填字段也可以手动显示星号来强调。markAsRequired()正是用于控制视觉标记(注意:它本身不添加任何验证规则):

use Filament\Forms\Components\TextInput; TextInput::make('name') ->required() // 添加"必填"验证 ->markAsRequired(false); // 移除标签旁的星号 // 字段并非 required(),但仍想显示星号: TextInput::make('name') ->markAsRequired();

对应实现位于 packages/forms/src/Components/Concerns/CanBeMarkedAsRequired.php:isMarkedAsRequired()在未显式设置时回退到isRequired()的结果,即星号默认跟随必填状态。

2.6 数据库存在性校验:exists 与 scopedExists

exists()用于校验字段值必须存在于数据库中。默认情况下,若表单已关联 Eloquent 模型,则直接搜索该模型对应的表(关于如何为表单设置模型,见 packages/forms/docs/02-form.md);你也可以指定自定义的表名或模型、列名:

use App\Models\Invitation; // 默认使用表单关联模型的表 Field::make('invitation')->exists() // 指定模型/表 Field::make('invitation')->exists(table: Invitation::class) // 指定列 Field::make('invitation')->exists(column: 'id') // 通过 modifyRuleUsing 进一步定制规则(此处为闭包注入) use Illuminate\Validation\Rules\Exists; Field::make('invitation') ->exists(modifyRuleUsing: function (Exists $rule) { return $rule->where('is_active', 1); })

关键限制(务必理解):Laravel 原生的exists规则不会通过 Eloquent 模型查询数据库,因此:

  • 不会应用模型上定义的任何全局作用域(包括软删除作用域)——即使存在同值的软删除记录,校验也会通过;
  • Filament 的多租户功能同样不会默认将查询限定到当前租户。

若希望校验遵守模型的全局作用域(包括软删除与多租户),请改用scopedExists()——它用基于模型的查询替换 Laravel 原生exists实现:

use Filament\Forms\Components\TextInput; TextInput::make('email') ->scopedExists()

如需修改用于存在性检查的 Eloquent 查询(例如移除某个全局作用域),通过modifyQueryUsing传入函数:

use Filament\Forms\Components\TextInput; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; TextInput::make('email') ->scopedExists(modifyQueryUsing: function (Builder $query) { return $query->withoutGlobalScope(SoftDeletingScope::class); })

源码印证:scopedExists()会基于$component->getModel()发起$model::query()->where($column, $value)查询,并默认withoutGlobalScope(SoftDeletingScope::class),再通过modifyQueryUsing钩子允许你移除或追加作用域(CanBeValidated.php)。失败时使用validation.exists语言包消息(可被validationMessages()覆盖)。

2.7 数据库唯一性校验:unique 与 scopedUnique

unique()用于校验字段值在数据库中必须唯一:

Field::make('email')->unique()

如果 Filament 表单已经关联了 Eloquent 模型(例如在 panel 资源中,见 docs/03-resources/01-overview.md),Filament 会自动使用该模型。也可以显式指定表/模型与列:

use App\Models\User; Field::make('email')->unique(table: User::class) Field::make('email')->unique(column: 'email_address')
忽略当前记录(更新场景的关键)

在"编辑资料"表单中(包含姓名、邮箱、所在地),通常仍要校验邮箱唯一——但如果用户只改了姓名、没动邮箱,就不该因为邮箱属于本人而报错。只要表单已关联 Eloquent 模型(如 panel 资源),Filament 默认就会忽略当前记录。可用参数控制:

// 禁止 Filament 自动忽略当前 Eloquent 记录 Field::make('email')->unique(ignoreRecord: false) // 指定忽略某条 Eloquent 记录 Field::make('email')->unique(ignorable: $ignoredUser) // 通过 modifyRuleUsing 进一步定制 use Illuminate\Validation\Rules\Unique; Field::make('email') ->unique(modifyRuleUsing: function (Unique $rule) { return $rule->where('is_active', 1); })

源码中ignoreRecord的默认值来自shouldUniqueValidationIgnoreRecordByDefault()(默认true),配合ignorable时通过Rule::unique(...)->ignore($record->getOriginal($key), $record->getQualifiedKeyName())构造忽略逻辑(CanBeValidated.php)。

同样的全局作用域限制:Laravel 原生unique规则不会经过 Eloquent 模型,因此不会应用全局作用域(含软删除与多租户)。这可能导致"同值软删除记录也会导致校验失败"。解决方案是scopedUnique()

use Filament\Forms\Components\TextInput; TextInput::make('email') ->scopedUnique()

同样支持忽略当前记录与修改查询:

use Filament\Forms\Components\TextInput; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; TextInput::make('email') ->scopedUnique(modifyQueryUsing: function (Builder $query) { return $query->withoutGlobalScope(SoftDeletingScope::class); })

从源码看,scopedUnique()基于模型查询$model::query()->where($column, $value),并通过whereKeyNot($ignorable)排除当前记录,然后交给modifyQueryUsing进一步调整(CanBeValidated.php)。

三、其他 Laravel 规则:rules() 方法

任何 Laravel 原生验证规则都可以通过rules()方法附加到字段上,支持数组或|分隔的字符串两种形式:

TextInput::make('slug')->rules(['alpha_dash']) // 等价写法 TextInput::make('slug')->rules('alpha_dash|max:255')

从源码看,rules()内部会将字符串按|拆分为数组,每条规则连同条件一起存入$this->rules;它还接受闭包作为规则来源,以及bool | Closure $condition条件参数,实现规则的条件启停(CanBeValidated.php)。完整规则清单可查阅 Laravel 官方验证文档。

四、自定义规则(Custom Rules)

Filament 完全兼容 Laravel 的自定义验证规则:

// 规则类对象 TextInput::make('slug')->rules([new Uppercase()]) // 闭包规则 use Closure; TextInput::make('slug')->rules([ fn (): Closure => function (string $attribute, $value, Closure $fail) { if ($value === 'foo') { $fail('The :attribute is invalid.'); } }, ])

在自定义规则中注入其他字段状态

如果你的自定义规则需要引用表单中其他字段的值,可以利用 Filament 的字段工具注入(utility injection,见 packages/forms/docs/01-overview.md),把$get等工具注入到闭包规则中。方法是将闭包规则再包一层函数:

use Filament\Schemas\Components\Utilities\Get; TextInput::make('slug')->rules([ fn (Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { if ($get('other_field') === 'foo' && $value !== 'bar') { $fail("The {$attribute} is invalid."); } }, ])

外层函数让 Filament 得以解析并注入Get工具,内层闭包再通过use ($get)捕获它——这是引用同表单其他字段状态的标准模式。

五、自定义校验属性名(validationAttribute)

字段校验失败时,错误消息中使用的属性名默认取自字段的 label。可用validationAttribute()自定义:

use Filament\Forms\Components\TextInput; TextInput::make('name') ->validationAttribute('full name')

除了静态字符串,该方法也接受一个函数来动态计算属性名(可注入各种工具)。源码中getValidationAttribute()在显式设置时返回其求值结果,否则回退到Str::lcfirst($label)(即标签首字母小写)(CanBeValidated.php)。

六、定制验证错误消息(validationMessages)

默认使用 Laravel 的标准错误消息。通过validationMessages()按规则名覆盖:

use Filament\Forms\Components\TextInput; TextInput::make('email') ->unique(/* ... */) ->validationMessages([ 'unique' => 'The :attribute has already been registered.', ])

每条消息既可以写静态字符串,也可以用函数动态计算(可注入工具)。消息在getValidationMessages()中被逐条求值(CanBeValidated.php),并最终通过dehydrateValidationMessages(){statePath}.{rule}为键合入整表单的消息表。

允许在验证消息中渲染 HTML

出于 XSS 防护,验证消息默认以纯文本渲染。某些场景(如展示列表或链接)需要渲染 HTML,可显式开启:

use Filament\Forms\Components\TextInput; TextInput::make('password') ->required() ->rules([ new CustomRule(), // 返回包含 HTML 的验证消息的自定义规则 ]) ->allowHtmlValidationMessages()

⚠️ 危险操作提示:开启该选项等于对该字段的验证消息放弃转义。请确保每一条消息——包括来自自定义规则或翻译文件的——都足够安全。不可信内容可能导致 XSS。源码中allowHtmlValidationMessages()的注释也明确记录了这一点(CanBeValidated.php)。

七、禁用未保存字段的校验(validatedWhenNotDehydrated)

默认情况下,即使字段设置了"不保存"(saved(false)/dehydrated(false),见 packages/forms/docs/01-overview.md),它仍然参与校验。如果希望未保存的字段不再校验,使用validatedWhenNotDehydrated(false)

use Filament\Forms\Components\TextInput; TextInput::make('name') ->required() ->saved(false) ->validatedWhenNotDehydrated(false)

该方法同样支持传函数动态计算。对应实现位于 packages/schemas/src/Components/Concerns/HasState.php,而规则收集阶段的过滤逻辑在 Schema 层:getValidationRules()会跳过isNeitherDehydratedNorValidated()的组件(packages/schemas/src/Concerns/CanBeValidated.php)。

八、验证规则的底层流水线:从字段到 Validator

了解规则如何从字段方法变成 Laravel Validator 的输入,有助于调试复杂表单:

  1. 收集:Schema 的getValidationRules()遍历所有组件(含隐藏组件),对每个实现HasValidationRules契约的组件调用dehydrateValidationRules($rules),以state 路径为键写入规则表(CanBeValidated.php)。
  2. 编译:字段的getValidationRules()将"必填/可空"规则、长度规则(若字段实现CanBeLengthConstrained)、in/enum规则、正则规则以及所有通过rule()/rules()追加的规则汇总为数组(CanBeValidated.php)。条件为闭包时逐条求值,不满足条件的规则被跳过。
  3. 执行:Schema 的validate()最终调用$livewire->validate($rules, $messages, $attributes)(packages/schemas/src/Concerns/CanBeValidated.php),走 Laravel 标准验证流程——同时为 Livewire 前端实时校验与后端提交校验复用同一套规则。
  4. 测试佐证:仓库测试 tests/src/Forms/ValidationTest.php 覆盖了必填规则触发(->required()产生Required失败键)、自定义规则透传(->rule('email'))、条件校验(->required($bool)的随机开关)以及"未脱水字段默认仍校验 / 配置后不再校验"等行为,可作为自定义验证逻辑的行为参照。

九、实战建议小结

  • 优先专用方法->required()->email()这类方法语义清晰、自动处理 attribute 与 state 路径,尽量避免手写rules(['required', ...])
  • 记住默认可空:未加required的字段默认注入nullable,空值不会触发格式类规则报错。
  • 区分原生与 scoped 数据库规则:涉及软删除模型或多租户表单时,使用scopedUnique()/scopedExists()才能让全局作用域生效。
  • 更新表单务必忽略自身记录unique默认忽略当前关联记录,跨场景显式传ignorableignoreRecord更稳妥。
  • HTML 消息默认关闭:除非消息内容完全可信,否则不要开启allowHtmlValidationMessages()
  • 未保存字段默认仍校验:需要跳过时显式validatedWhenNotDehydrated(false)

通过组合上述规则、字段工具注入与条件闭包,你可以为任何 Filament 表单构建从客户端实时反馈到服务端兜底的完整验证体系。

【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 21:47:33

DenseUnet超声甲状腺结节分割实战指南

简介:本资源是一套面向医学图像分割初学者与AI医疗实践者的PyTorch实战项目,聚焦超声甲状腺结节的精准语义分割任务。提供DenseUnet与Unet双网络实现,支持一键训练与推理,内置cosine学习率调度、AdamW优化器及Dice/IoU/Recall/Pre…

作者头像 李华
网站建设 2026/9/10 21:47:29

智能体职业教育的应用现状与技术挑战

1. 智能体职业教育的发展现状与争议 最近两年,智能体职业教育突然成为教育科技领域的热门话题。从最初几家创业公司的小规模尝试,到现在各大教育平台纷纷布局,这个细分领域正在经历爆发式增长。但与此同时,质疑声也不绝于耳&#…

作者头像 李华
网站建设 2026/9/10 21:47:23

SSM框架房屋代管租赁系统设计与实现

1. 项目背景与核心需求 作为一名经历过毕业设计洗礼的老程序员,我深知房屋租赁管理系统这类课题在计算机专业毕业设计中的热门程度。每年都有大量学生选择这个方向,但真正能把系统做完整、做出亮点的却不多。这个基于SSM框架的房屋代管租赁系统&#xff…

作者头像 李华
网站建设 2026/9/10 21:46:43

危化品仓库智能分区与溯源管理实践

1. 危化品仓库管理的痛点与挑战危化品仓库作为特殊物资存储场所,其安全管理一直是行业内的重点难点。我从事化工行业安全管理十余年,见过太多因管理不当引发的事故案例。去年华东某化工厂的爆燃事故,直接经济损失超过2亿元,起因就…

作者头像 李华
网站建设 2026/9/10 21:46:38

TVBoxOSC 电视盒子管理 Docker 部署完整指南:十分钟从零到首次启动

TVBoxOSC 电视盒子管理 Docker 部署完整指南:十分钟从零到首次启动 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库,用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC 上次手动部署电视…

作者头像 李华