Import action
概述
Filament v3.1 引入了一个可以从 CSV 导入数据的预制 Action。点击触发按钮后,模态框向用户请求文件。文件上传后,可以将 CSV 中的每一列映射到数据库的字段中。如果有任何行验证失败,这些行将被编译到一个可下载的 CSV 中,在其他的行导入后,供用户进行查阅。用户也可以下载包含可被导入的所有列信息的 CSV 示例文件。
该特性使用了批量队列作业及数据库通知,因此你需要发布这些迁移到 Laravel 中。同时,也需要发布 Filament 用于存储导入数据的迁移表:
php artisan queue:batches-table
php artisan notifications:table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate
如果你使用的是 PostgreSQL,请确保通知迁移中的
data字段使用json():$table->json('data')。
如果
User模型使用了 UUID,请确保通知迁移的notifiable字段使用uuidMorphs():$table->uuidMorphs('notifiable')。
ImportAction 可以像这样使用:
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;
ImportAction::make()
->importer(ProductImporter::class)
如果你想添加 Action 到表格头部,可以使用 Filament\Tables\Actions\ImportAction:
use App\Filament\Imports\ProductImporter;
use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
ImportAction::make()
->importer(ProductImporter::class)
]);
}
需要创建 "importer" 类,告知 Filament 如何导入 CSV 的每一行。
如果在某些地方有多个 ImportAction,你需要在 make() 方法中为每个 Action 都指定一个唯一的名称:
ImportAction::make('importProducts')
->importer(ProductImporter::class)
ImportAction::make('importBrands')
->importer(BrandImporter::class)
创建 importer
要为模型创建 importer 类,可以使用 make:filament-importer 命令,并传 入模型名称:
php artisan make:filament-importer Product
该命令将在 app/Filament/Imports 目录下创建一个新的类。你需要定义可被导入的字段。
自动生成 importer 字段
如果你想节省时间,Filament 可以使用 --generate,基于模型的数据库字段,为你自动生成字段:
php artisan make:filament-importer Product --generate
定义 importer 字段
要定义可被导入的字段,你需要在 importer 类中重写 getColumns() 方法,返回 ImportColumn 对象数组:
use Filament\Actions\Imports\ImportColumn;
public function getColumns(): array
{
return [
ImportColumn::make('name')
->requiredMapping()
->rules(['required', 'max:255']),
ImportColumn::make('sku')
->label('SKU')
->requiredMapping()
->rules(['required', 'max:32']),
ImportColumn::make('price')
->numeric()
->rules(['numeric', 'min:0']),
];
}
自定义导入字段的标签
每个字段的标签将由该字段名自动生成,不过你也可以调用 label() 方法对其进行重写:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->label('SKU')
要求 importer 字段映射到 CSV 字段
你可以调用 requiredMapping() 方法,使得一个映射到 CSV 中的字段为必需。数据库中必需的字段也应该是映射时必需的:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->requiredMapping()
如果数据库中的字段是必需的,你也需要确保它的验证规则有 ['required'] 验证规则。
验证 CSV 数据
可以调用 rules() 方法将验证规则添加到字段中。这些规则将在 CSV 中的每一行中的数据保存到数据库之前对其进行检查:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->rules(['required', 'max:32'])
任何未通过验证的行都不会被导入。相反,它们将被编译成一个新的 "失败行" CSV,用户可以在导入完成后下载。用户将看到每一行失败的验证错误列表。
强制转换状态
在验证之前,CSV 数据可以被转换。这对于将字符串强制转换为正确的数据类型很有用,否则验证可能会失败。比如,如果你的 CSV 中有一个 price 字段,你可能想将其强制转换成浮点型:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->castStateUsing(function (string $state): ?float {
if (blank($state)) {
return null;
}
$state = preg_replace('/[^0-9.]/', '', $state);
$state = floatval($state);
return round($state, precision: 2);
})
本例中,我们传入一个用于强制转换 $state 的函数。此函数从字符串中删除任何非数字字符,将其强制转换为浮点值,并将其四舍五入到小数点后两位。
请注意,如果字段在验证中不是必需的,且字段为空,它不会被转换。
Filament 也同时附带了一些预制的转换方法:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric() // Casts the state to a float.
ImportColumn::make('price')
->numeric(decimalPlaces: 2) // Casts the state to a float, and rounds it to 2 decimal places.
ImportColumn::make('quantity')
->integer() // Casts the state to an integer.
ImportColumn::make('is_visible')
->boolean() // Casts the state to a boolean.
强制转换后改变状态
如果你使用了内置转 方法或者数组转换,将函数传入到 castStateUsing() 方法,你可以在转换后改变其状态:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric()
->castStateUsing(function (float $state): ?float {
if (blank($state)) {
return null;
}
return round($state * 100);
})
通过在函数中定义一个 $originalState 参数,你甚至可以访问强制转换之前原始状态:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric()
->castStateUsing(function (float $state, mixed $originalState): ?float {
// ...
})
导入关联
你可以使用 relationship() 方法导入关联。目前只支持 BelongsTo 关联。比如,你的 CSV 中有一个 category 字段,你想导入到 category 关联:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship()
本例中,CSV 中的 author 字段会与数据库中的 author_id 映射。该 CSV 中应该包含 author 的主键,通常为 id。
如果字段有值,但找不到作者(author),则导入将无法通过验证。Filament 会自动向 所有关联列添加验证,以确保关联在必需时不为空。
自定义关联导入解析
如果你想使用不同的字段查询关联记录,你可以将字段名传入到 resolveUsing:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: 'email')
你可以传入多个字段到 resolveUsing 中,这些字段将被用于以 "or" 方式查找作者。比如,如果你传入 ['email', 'username'],将通过邮箱(email)或用户名(username)查询记录:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: ['email', 'username'])
你也可以传入函数到 resolveUsing 自定义解析过程,该函数返回带有关联的记录:
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: function (array $state): ?Author {
return Author::query()
->where('email', $state)
->orWhere('username', $state)
->first();
})
你甚至可以使用该函数,动态确定哪个字段用于解析记录:
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: function (array $state): ?Author {
if (filter_var($state, FILTER_VALIDATE_EMAIL)) {
return 'email';
}
return 'username';
})