Filament TernaryFilter 三元筛选器完全指南:布尔列与可空列的三种状态筛选
【免费下载链接】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 表格(Table)中,TernaryFilter(三元筛选器)是一个基于SelectFilter内置的选择型筛选组件,它允许用户在一个下拉框里从"真(true)"、"假(false)"与"空白(blank,即不筛选)"三种状态中选择,从而以极简的交互完成对布尔列或可空列的筛选。本文以 TernaryFilter 官方文档 为核心骨架,结合 TernaryFilter.php 源码与 TernaryFilterTest.php 测试用例,完整讲解它的默认行为、nullable()、attribute()、标签定制、queries()自定义查询逻辑以及内置的TrashedFilter软删除筛选,帮你写出可直接落地的筛选代码。
认识 TernaryFilter:为什么需要"三种状态"
在 表格筛选器概览文档 中,默认的Filter::make()渲染的是一个复选框,只有"勾选/未勾选"两种状态。而很多业务场景天然是三种状态的:
- 布尔列(如
is_featured、is_published):需要"只看为 true 的""只看为 false 的""全部显示"三个选项; - 可空列(如
email_verified_at):需要"只看已填值的""只看为 NULL 的""全部显示"三个选项。
TernaryFilter恰好满足这类需求——它把复选框替换成一个Select下拉框,提供三个选项。从源码看,TernaryFilter直接继承自SelectFilter(见 TernaryFilter.php),因此它天然具备 Select 筛选器的全部能力,并在此基础上把"选项"固化为 true / false / blank 三个语义状态。
最小用法
对名为is_featured的布尔列进行 true / false 筛选,只需一行代码:
use Filament\Tables\Filters\TernaryFilter; TernaryFilter::make('is_featured')在 TernaryFilterTest.php 中可以看到其行为被测试验证:filterTable('is_published', 1)时只显示is_published = true的记录,filterTable('is_published', 0)时只显示is_published = false的记录,而resetTableFilters()后全部记录恢复显示。
默认查询逻辑:boolean()方法
创建TernaryFilter时,其setUp()会自动调用boolean()方法(见 TernaryFilter.php),为三个状态注册默认查询闭包:
| 状态 | 默认生成的 SQL 条件 |
|---|---|
| true | where('is_featured', true) |
| false | where('is_featured', false) |
| blank | 不添加任何条件 |
源码实现如下(TernaryFilter.php):
public function boolean(): static { $this->queries( true: fn (Builder $query): Builder => $query->where($this->getAttribute(), true), false: fn (Builder $query): Builder => $query->where($this->getAttribute(), false), ); return $this; }注意boolean()在查询关系(relationship)时使用whereRelation()处理,这里不再展开,但说明 TernaryFilter 同样兼容关联表列的场景。
用 nullable() 筛选可空列
布尔筛选处理的是"值本身是 true/false"的列;而很多业务列(如email_verified_at、deleted_at、content)存储的是时间戳或可空值,判断标准是"是否为 NULL"。此时应调用nullable()方法:
use Filament\Tables\Filters\TernaryFilter; TernaryFilter::make('email_verified_at') ->nullable()nullable()的实现(见 TernaryFilter.php)为三个状态注册如下查询:
| 状态 | 生成的 SQL 条件 |
|---|---|
| true | whereNotNull('email_verified_at')(已认证用户) |
| false | whereNull('email_verified_at')(未认证用户) |
| blank | 不添加任何条件(显示全部用户) |
测试 TernaryFilterTest.php 中,以content列为对象验证了nullable()行为:filterTable('content', 1)只显示content非空的记录,filterTable('content', 0)只显示content为 NULL 的记录。
用 attribute() 自定义筛选作用的列
TernaryFilter默认使用make()传入的筛选器名称作为查询作用的列名。如果筛选器名称与数据库列名不一致,可以用attribute()指定实际列:
use Filament\Tables\Filters\TernaryFilter; TernaryFilter::make('verified') ->nullable() ->attribute('status_id')从源码看,attribute()定义在父类SelectFilter中(SelectFilter.php),getAttribute()返回attribute属性的值,若未设置则回退到getName()(SelectFilter.php)。也就是说->attribute('status_id')会把where/whereNull/whereNotNull作用到status_id列上,而上例中nullable()生成的 SQL 即whereNotNull('status_id')/whereNull('status_id')。
另外,SelectFilter中的column()方法是attribute()的弃用别名(SelectFilter.php),新代码请统一使用attribute()。
定制三种状态的标签与占位符
三个状态的文案默认是英文的"true / false / -"。TernaryFilter提供三个方法定制它们(见 TernaryFilter.php):
trueLabel():true 选项的标签;falseLabel():false 选项的标签;placeholder():默认(空白)选项的标签,即下拉框未选择时的占位文案。
use Filament\Tables\Filters\TernaryFilter; TernaryFilter::make('email_verified_at') ->label('Email verification') ->nullable() ->placeholder('All users') ->trueLabel('Verified users') ->falseLabel('Not verified users')setUp()中的默认值为trueLabel(__('filament-forms::components.select.boolean.true'))、falseLabel(__('filament-forms::components.select.boolean.false'))、placeholder('-')(见 TernaryFilter.php)。其中trueLabel()与falseLabel()均接受字符串或闭包,最终通过getTrueLabel()/getFalseLabel()求值;测试 TernaryFilterTest.php 验证了字符串与闭包两种写法。
当筛选激活时,表格上方的筛选指示条(indicator)会显示"标签: 状态文案",例如"Email verification: Verified users"。该逻辑由setUp()中的indicateUsing()闭包实现(TernaryFilter.php):当状态为空白时返回空数组(不显示指示条),否则根据状态值选用 true/false 标签拼接指示条。
用 queries() 完全掌控查询逻辑
nullable()和boolean()只是两个预置方案。当你需要完全自定义三种状态各自的查询时,使用queries()方法,传入三个闭包,分别对应 true、false、blank 状态:
use Illuminate\Database\Eloquent\Builder; use Filament\Tables\Filters\TernaryFilter; TernaryFilter::make('email_verified_at') ->label('Email verification') ->placeholder('All users') ->trueLabel('Verified users') ->falseLabel('Not verified users') ->queries( true: fn (Builder $query) => $query->whereNotNull('email_verified_at'), false: fn (Builder $query) => $query->whereNull('email_verified_at'), blank: fn (Builder $query) => $query, // 本例中 blank 时不希望过滤,直接返回原查询 )queries() 的底层实现
queries()本身并不直接拼接 SQL,而是把这些闭包组合成query()回调(TernaryFilter.php):
public function queries(Closure $true, Closure $false, ?Closure $blank = null): static { $this->query(function (Builder $query, array $data) use ($blank, $false, $true) { if (blank($data['value'] ?? null)) { return $blank instanceof Closure ? $blank($query, $data) : $query; } return $data['value'] ? $true($query, $data) : $false($query, $data); }); return $this; }逻辑非常直观:当筛选状态值为空时执行$blank闭包(未提供则原样返回查询);否则按状态值的真假分发到$true或$false。因此:
- blank 闭包是可选的,不传时 blank 状态不做任何筛选;
- 三个闭包都接收
Builder $query与array $data(包含value键),返回修改后的查询构建器; - 状态值
1视为 true,0视为 false(getDefaultState()还会把布尔默认值转成整数1/0以匹配 Select 选项值,见 TernaryFilter.php)。
给筛选器设置默认状态
如果你希望表格加载时就默认启用某个状态,可以用default()方法(定义于 HasDefaultState.php):
TernaryFilter::make('is_published') ->default() // 默认显示 true 的记录,等价于 ->default(true)也支持显式布尔值:->default(true)或->default(false)。测试 TernaryFilterTest.php 验证了布尔默认值会被转换为整数1/0,非布尔值(如null)则原样传递。
开箱即用的 TrashedFilter:软删除记录筛选
TrashedFilter是 Filament 内置的一个TernaryFilter子类,专用于筛选软删除记录,直接使用即可:
use Filament\Tables\Filters\TrashedFilter; TrashedFilter::make()它的名称默认是trashed(getDefaultName()返回'trashed',见 TrashedFilter.php),所以make()可以不带参数。
从 TrashedFilter.php 源码可以看到它的完整配置:
| 配置项 | 内容 |
|---|---|
| label | 翻译键filament-tables::table.filters.trashed.label |
| placeholder | without_trashed(不含已删除) |
| trueLabel | with_trashed(含已删除) |
| falseLabel | only_trashed(仅已删除) |
| queries | true →withTrashed(),false →onlyTrashed(),blank →withoutTrashed() |
| baseQuery | 移除SoftDeletingScope全局作用域 |
| excludeWhenResolvingRecord | 解析记录时排除此筛选器 |
其中两个关键点值得注意:
baseQuery() 移除全局作用域:
TernaryFilter及所有筛选器的query()回调都作用在加了SoftDeletingScope的查询上,若不先移除该全局作用域,onlyTrashed()等操作无法生效。TrashedFilter通过baseQuery()在基础查询层面移除了SoftDeletingScope(TrashedFilter.php)。baseQuery()方法定义于 InteractsWithTableQuery.php,它允许直接修改基础查询(例如移除全局作用域),而query()只修改受限作用域内的查询——这正是 筛选器概览文档 中 "Modifying the base query" 一节所强调的区别。excludeWhenResolvingRecord():当用户与表格中的记录交互(如点击行操作)时,Filament 会按当前筛选条件解析记录。
TrashedFilter调用了excludeWhenResolvingRecord()(TrashedFilter.php),使该筛选器的query()回调在解析记录时不被应用(但baseQuery()回调仍会应用),从而保证已删除记录的交互不受软删除筛选的阻碍。该方法的通用形式定义于 InteractsWithTableQuery.php。需要强调的是:切勿在承载权限控制的筛选器(如按租户、按用户归属限制记录的筛选器)上使用excludeWhenResolvingRecord(),否则可能造成越权访问。
组合示例:一个完整的用户认证状态筛选
将上述 API 组合起来,一个完整的"邮箱认证状态"筛选器可以写成:
use Illuminate\Database\Eloquent\Builder; use Filament\Tables\Filters\TernaryFilter; use Filament\Tables\Table; public function table(Table $table): Table { return $table ->query(User::query()) ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('email'), ]) ->filters([ TernaryFilter::make('verified') ->label('Email verification') ->placeholder('All users') ->trueLabel('Verified users') ->falseLabel('Not verified users') ->nullable() ->attribute('email_verified_at'), ]); }这里make('verified')只决定筛选器在代码中的唯一标识,attribute('email_verified_at')决定真正筛选的列,nullable()提供基于 NULL 的三态查询,四个 label 类方法完成全部 UI 文案定制。
总结
TernaryFilter用最少的状态模型(true / false / blank)覆盖了布尔列与可空列这两类最常见的筛选诉求:
- 布尔列直接
TernaryFilter::make('is_featured'),由默认的boolean()提供 where true/false 查询; - 可空列加
->nullable(),自动切换为whereNotNull()/whereNull(); - 列名与筛选器名不一致时用
->attribute('实际列名'); - 三个状态的文案分别用
trueLabel()/falseLabel()/placeholder()定制,并可通过default()预设初始状态; - 需要完全自定义时用
queries(true: ..., false: ..., blank: ...)接管全部查询逻辑; - 软删除场景直接使用内置的
TrashedFilter,它借助baseQuery()与excludeWhenResolvingRecord()正确隔离全局作用域与记录解析。
需要进一步探索时,可继续阅读 筛选器概览(了解deferFilters()、persistFiltersInSession()等表格级配置)、SelectFilter 文档(了解继承自 Select 的searchable()、native()等能力),或直接研读 TernaryFilter 源码、TrashedFilter 源码 与 TernaryFilter 测试 加深理解。
【免费下载链接】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),仅供参考