查询构建器
简介
查询构建器允许你定义一组复杂条件来筛选表格中的数据。它能处理无限嵌套的条件,你可以用「and」和「or」操作将它们组合在一起。


要使用它,你需要定义一组用于筛选数据的「约束」。Filament 包含一些遵循常见数据类型的内置约束,你也可以定义自己的自定义约束。
你可以使用 QueryBuilder 筛选器将查询构建器添加到任意表格:
use Filament\Tables\Filters\QueryBuilder;
use Filament\QueryBuilder\Constraints\BooleanConstraint;
use Filament\QueryBuilder\Constraints\DateConstraint;
use Filament\QueryBuilder\Constraints\NumberConstraint;
use Filament\QueryBuilder\Constraints\RelationshipConstraint;
use Filament\QueryBuilder\Constraints\RelationshipConstraint\Operators\IsRelatedToOperator;
use Filament\QueryBuilder\Constraints\SelectConstraint;
use Filament\QueryBuilder\Constraints\TextConstraint;
QueryBuilder::make()
->constraints([
TextConstraint::make('name'),
BooleanConstraint::make('is_visible'),
NumberConstraint::make('stock'),
SelectConstraint::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->multiple(),
DateConstraint::make('created_at'),
RelationshipConstraint::make('categories')
->multiple()
->selectable(
IsRelatedToOperator::make()
->titleAttribute('name')
->searchable()
->multiple(),
),
NumberConstraint::make('reviews.rating')
->integer(),
])当查询构建器嵌套较深时,你可能需要增加筛选器可占用的空间。一种做法是将筛选器放在表格内容上方:
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\QueryBuilder;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
QueryBuilder::make()
->constraints([
// ...
]),
], layout: FiltersLayout::AboveContent);
}可用约束
Filament 附带多种可开箱即用的约束。你也可以创建自己的自定义约束:
- [文本约束](#text-constraints)
- [布尔约束](#boolean-constraints)
- [数字约束](#number-constraints)
- [日期约束](#date-constraints)
- [选择约束](#select-constraints)
- [关联约束](#relationship-constraints)
文本约束
文本约束允许你筛选文本字段。它们可用于筛选任意文本字段,包括通过关联。
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('name') // Filter the `name` column
TextConstraint::make('creator.name') // Filter the `name` column on the `creator` relationship using dot syntax默认提供以下运算符:
- 包含 - 筛选列使其包含搜索词
- 不包含 - 筛选列使其不包含搜索词
- 以…开头 - 筛选列使其以搜索词开头
- 不以…开头 - 筛选列使其不以搜索词开头
- 以…结尾 - 筛选列使其以搜索词结尾
- 不以…结尾 - 筛选列使其不以搜索词结尾
- 等于 - 筛选列使其等于搜索词
- 不等于 - 筛选列使其不等于搜索词
- 已填写 - 筛选列使其非空
- 为空 - 筛选列使其为空
布尔约束
布尔约束允许你筛选布尔字段。它们可用于筛选任意布尔字段,包括通过关联。
use Filament\QueryBuilder\Constraints\BooleanConstraint;
BooleanConstraint::make('is_visible') // Filter the `is_visible` column
BooleanConstraint::make('creator.is_admin') // Filter the `is_admin` column on the `creator` relationship using dot syntax默认提供以下运算符:
- 为真 - 筛选列使其为 `true`
- 为假 - 筛选列使其为 `false`
数字约束
数字约束允许你筛选数值字段。它们可用于筛选任意数值字段,包括通过关联。
use Filament\QueryBuilder\Constraints\NumberConstraint;
NumberConstraint::make('stock') // Filter the `stock` column
NumberConstraint::make('orders.item_count') // Filter the `item_count` column on the `orders` relationship using dot syntax默认提供以下运算符:
- 最小值 - 筛选列使其大于或等于搜索数字
- 小于 - 筛选列使其小于搜索数字
- 最大值 - 筛选列使其小于或等于搜索数字
- 大于 - 筛选列使其大于搜索数字
- 等于 - 筛选列使其等于搜索数字
- 不等于 - 筛选列使其不等于搜索数字
- 已填写 - 筛选列使其非空
- 为空 - 筛选列使其为空
当对关联列使用数字约束时,用户还可以「聚合」关联记录。这意味着他们可以一次性按所有关联记录的总和、平均值、最小值或最大值来筛选该列。
整数约束
默认情况下,数字约束允许小数值。若只允许整数值,可以使用 integer() 方法:
use Filament\QueryBuilder\Constraints\NumberConstraint;
NumberConstraint::make('stock')
->integer()日期约束
日期约束允许你筛选日期字段。它们可用于筛选任意日期字段,包括通过关联。
use Filament\QueryBuilder\Constraints\DateConstraint;
DateConstraint::make('created_at') // Filter the `created_at` column
DateConstraint::make('creator.created_at') // Filter the `created_at` column on the `creator` relationship using dot syntax默认提供以下运算符:
- 晚于 - 筛选列使其晚于搜索日期
- 不晚于 - 筛选列使其不晚于搜索日期,或为同一日期
- 早于 - 筛选列使其早于搜索日期
- 不早于 - 筛选列使其不早于搜索日期,或为同一日期
- 为某日期 - 筛选列使其与搜索日期相同
- 不为某日期 - 筛选列使其与搜索日期不同
- 为某月 - 筛选列使其与所选月份相同
- 不为某月 - 筛选列使其与所选月份不同
- 为某年 - 筛选列使其与搜索年份相同
- 不为某年 - 筛选列使其与搜索年份不同
日期时间约束
默认情况下,日期约束只按日期筛选。若你有日期时间列并希望启用基于时间的筛选,可以使用 time() 方法:
use Filament\QueryBuilder\Constraints\DateConstraint;
DateConstraint::make('published_at')
->time()选择约束
选择约束允许你使用选择字段来筛选字段。它们可用于筛选任意字段,包括通过关联。
use Filament\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status') // Filter the `status` column
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
SelectConstraint::make('creator.department') // Filter the `department` column on the `creator` relationship using dot syntax
->options([
'sales' => 'Sales',
'marketing' => 'Marketing',
'engineering' => 'Engineering',
'purchasing' => 'Purchasing',
])DANGER
options() 只是 UI 呈现手段,不是授权边界。选项列表限制下拉框显示的内容,但约束运行前不会对照该列表校验提交值。查询构建器规则存储在 Livewire 状态中,可能被篡改——精心构造的请求可以向约束的 settings 提交任意值,而 IsOperator::apply() 会将其直接传入查询的 whereIn/where。
若你用 options() 向某类用户隐藏某些值(例如对非管理员隐藏 archived),应改为限定底层查询本身——例如在约束上使用 modifyRelationshipQueryUsing()、在资源上使用 modifyQueryUsing(),或在模型上使用全局作用域——这样无论约束提交什么,受限制的行都不可达。
可搜索的选择约束
默认情况下,选择约束不允许用户搜索选项。若希望允许用户搜索选项,可以使用 searchable() 方法:
use Filament\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status')
->searchable()
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])多选约束
默认情况下,选择约束只允许用户选择单个选项。若希望允许用户选择多个选项,可以使用 multiple() 方法:
use Filament\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status')
->multiple()
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])当用户选择多个选项时,表格会筛选出匹配任一所选选项的记录。
关联约束
关联约束允许你使用关联相关数据来筛选字段:
use Filament\QueryBuilder\Constraints\RelationshipConstraint;
use Filament\QueryBuilder\Constraints\RelationshipConstraint\Operators\IsRelatedToOperator;
RelationshipConstraint::make('creator') // Filter the `creator` relationship
->selectable(
IsRelatedToOperator::make()
->titleAttribute('name')
->searchable()
->multiple(),
)IsRelatedToOperator 用于配置「Is / Contains」和「Is not / Does not contain」运算符。它提供一个选择字段,让用户按附加到该关联的记录来筛选。titleAttribute() 方法用于指定在列表中标识每条关联记录的属性。searchable() 方法使列表可搜索。multiple() 方法允许用户选择多条关联记录;若选择了多条,表格会筛选出匹配任一所选关联记录的记录。
多重关联
默认情况下,关联约束只包含适合筛选单一关联(如 BelongsTo)的运算符。若你有 HasMany 或 BelongsToMany 等关联,可能希望将约束标记为 multiple():
use Filament\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('categories')
->multiple()这会向约束添加以下运算符:
- 至少有 - 筛选列使其至少有指定数量的关联记录
- 少于 - 筛选列使其关联记录数少于指定数量
- 至多有 - 筛选列使其至多有指定数量的关联记录
- 多于 - 筛选列使其关联记录数多于指定数量
- 有 - 筛选列使其有指定数量的关联记录
- 没有 - 筛选列使其没有指定数量的关联记录
空关联约束
RelationshipConstraint 不像其他约束那样支持 nullable()。
若关联为 multiple(),约束会显示一个筛除「空」关联的选项,即该关联没有任何关联记录。若关联是单一的,可以使用 emptyable() 方法显示筛除「空」关联的选项:
use Filament\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('creator')
->emptyable()若你有必须始终至少有 1 条关联记录的 multiple() 关联,可以使用 emptyable(false) 方法隐藏筛除「空」关联的选项:
use Filament\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('categories')
->emptyable(false)可空约束
默认情况下,约束不会显示筛选 null 值的选项。若希望显示筛选 null 值的选项,可以使用 nullable() 方法:
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('name')
->nullable()现在还会提供以下运算符:
- 已填写 - 筛选列使其非空
- 为空 - 筛选列使其为空
限制规则树的大小
用户构建的规则集存储在 Livewire 组件的状态中,并随每次请求提交。为防止包含过大或嵌套过深的规则集的请求消耗过多服务器内存或 CPU,你可以限制规则数量及其嵌套深度。
默认不应用任何限制。你可以使用 maxRules() 和 maxNestingDepth() 方法设置它们:
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->maxRules(100)
->maxNestingDepth(10)
->constraints([
// ...
])这两个方法也接受函数以动态计算该值:
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->maxRules(fn (): int => auth()->user()->isAdmin() ? 200 : 50)
->constraints([
// ...
])maxRules() 限制只统计单独的条件。「OR」组是结构容器,本身不计入该限制——嵌套深度由 maxNestingDepth() 单独限制。
设置限制后,UI 会强制执行:一旦达到最大规则数,「add rule」和克隆按钮会禁用,并显示说明原因的工具提示;一旦达到最大嵌套深度,「OR」分组选项会被隐藏。作为对绕过 UI 的篡改请求的防护,若提交的规则树仍超出任一限制,会被安全忽略,不应用任何约束,而不是筛选表格。
TIP
由于这些限制对每个查询构建器都相同,通常希望全局应用它们,而不是在每个筛选器上重复。你可以在服务提供者的 boot() 方法中使用 configureUsing() 来做到这一点,从而约束应用中的每个查询构建器:
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::configureUsing(function (QueryBuilder $queryBuilder): void {
$queryBuilder
->maxRules(100)
->maxNestingDepth(10);
});限定关联范围
使用关联约束时,你可以使用 modifyRelationshipQueryUsing() 方法限定关联范围以筛选关联记录:
use Filament\QueryBuilder\Constraints\TextConstraint;
use Illuminate\Database\Eloquent\Builder;
TextConstraint::make('creator.name')
->label('Admin creator name')
->modifyRelationshipQueryUsing(fn (Builder $query) => $query->where('is_admin', true))自定义约束图标
每种约束类型都有默认图标,显示在选择器中标签旁边。你可以通过向 icon() 方法传入图标名称来自定义约束的图标:
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author.name')
->icon('heroicon-m-user')覆盖默认运算符
每种约束类型都有一组默认运算符,你可以使用 operators() 方法自定义:
use Filament\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author.name')
->operators([
IsFilledOperator::make(),
])这会移除所有运算符,并注册 EqualsOperator。
若希望在列表末尾添加运算符,改用 pushOperators():
use Filament\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author.name')
->pushOperators([
IsFilledOperator::class,
])若希望在列表开头添加运算符,改用 unshiftOperators():
use Filament\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author.name')
->unshiftOperators([
IsFilledOperator::class,
])创建自定义约束
自定义约束可以与其他约束「内联」创建,使用 Constraint::make() 方法。你还应向 icon() 方法传入一个图标:
use Filament\QueryBuilder\Constraints\Constraint;
Constraint::make('subscribed')
->icon('heroicon-m-bell')
->operators([
// ...
]),若要自定义约束的标签,可以使用 label() 方法:
use Filament\QueryBuilder\Constraints\Constraint;
Constraint::make('subscribed')
->label('Subscribed to updates')
->icon('heroicon-m-bell')
->operators([
// ...
]),use Filament\QueryBuilder\Constraints\Constraint;
use Filament\QueryBuilder\Constraints\Operators\IsFilledOperator;
Constraint::make('subscribed')
->label('Subscribed to updates')
->icon('heroicon-m-bell')
->operators([
// ...
IsFilledOperator::class,
]),创建自定义运算符
自定义运算符可以使用 Operator::make() 方法创建:
use Filament\QueryBuilder\Constraints\Operators\Operator;
Operator::make('subscribed')
->label(fn (bool $isInverse): string => $isInverse ? 'Not subscribed' : 'Subscribed')
->summary(fn (bool $isInverse): string => $isInverse ? 'You are not subscribed' : 'You are subscribed')
->baseQuery(fn (Builder $query, bool $isInverse) => $query->{$isInverse ? 'whereDoesntHave' : 'whereHas'}(
'subscriptions.user',
fn (Builder $query) => $query->whereKey(auth()->user()),
)),在此示例中,该运算符可根据已认证用户是否订阅该记录来筛选记录。订阅记录在表格的 subscriptions 关联中。
baseQuery() 方法用于定义筛选记录的查询。选择「Subscribed」选项时 $isInverse 为 false,选择「Not subscribed」选项时为 true。该函数应用于表格的基础查询,可在其中使用 whereHas()。若你的函数无需应用于表格的基础查询(例如只使用简单的 where() 或 whereIn()),可以改用 query() 方法,其额外好处是能在嵌套的「OR」组中使用。
label() 方法用于在运算符选择器中渲染选项。每个运算符会注册两个选项:一个用于未反转时,一个用于已反转时。
summary() 方法在约束应用于查询时用于约束的标题,以提供当前活动约束的概览。
自定义约束选择器
更改约束选择器中的列数
约束选择器默认只有 1 列。你可以通过向 constraintPickerColumns() 传入列数来自定义:
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->constraintPickerColumns(2)
->constraints([
// ...
])该方法可以有几种不同用法:
- 你可以传入整数,例如 `constraintPickerColumns(2)`。该整数是 `lg` 断点及以上使用的列数。所有更小的设备将只有 1 列。
- 你可以传入数组,其中键为断点、值为列数。例如,`constraintPickerColumns(['md' => 2, 'xl' => 4])` 会在中等设备上创建 2 列布局,在超大设备上创建 4 列布局。更小设备的默认断点使用 1 列,除非你使用 `default` 数组键。
断点(sm、md、lg、xl、2xl)由 Tailwind 定义,可在 Tailwind 文档 中找到。
增大约束选择器的宽度
当你增加列数时,下拉菜单的宽度应逐步增大以容纳额外列。若需要更多控制,可以使用 constraintPickerWidth() 方法手动为下拉菜单设置最大宽度。选项对应 Tailwind 的 max-width 比例。可选值为 xs、sm、md、lg、xl、2xl、3xl、4xl、5xl、6xl、7xl:
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->constraintPickerColumns(3)
->constraintPickerWidth('2xl')
->constraints([
// ...
])