Skip to content
全部文档

导入操作

简介

Filament 包含一个能够从 CSV 导入行的操作。点击触发按钮后,模态框会要求用户选择文件。上传后,用户可以将 CSV 中的每一列映射到数据库中的真实列。若有行未通过验证,会在其余行导入完成后编译成可下载的 CSV,供用户审阅。用户还可以下载包含全部可导入列的示例 CSV 文件。

此功能使用 任务批处理数据库通知,因此你需要发布 Laravel 的这些迁移。此外,还需要发布 Filament 用于存储导入信息的表迁移:

bash
php artisan make:queue-batches-table
php artisan make:notifications-table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate

若希望在面板中接收导入通知,可以在 面板配置 中启用。

INFO

若使用 PostgreSQL,请确保通知迁移中的 data 列使用 json()$table->json('data')

INFO

User 模型使用 UUID,请确保通知迁移中的 notifiable 列使用 uuidMorphs()$table->uuidMorphs('notifiable')

你可以这样使用 ImportAction

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
导入操作模态框导入操作模态框

若要将其添加到表格页眉,可以这样做:

php
use App\Filament\Imports\ProductImporter;
use Filament\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() 方法中为每个指定唯一名称:

php
use Filament\Actions\ImportAction;

ImportAction::make('importProducts')
    ->importer(ProductImporter::class)

ImportAction::make('importBrands')
    ->importer(BrandImporter::class)

创建 importer

若要为模型创建 importer 类,可使用 make:filament-importer 命令并传入模型名称:

bash
php artisan make:filament-importer Product

这会在 app/Filament/Imports 目录中创建新类。接下来需要定义可导入的

自动生成 importer 列

若想节省时间,Filament 可以根据模型的数据库列,使用 --generate 自动生成

bash
php artisan make:filament-importer Product --generate

定义 importer 列

若要定义可导入的列,需要在 importer 类上覆盖 getColumns() 方法,返回 ImportColumn 对象数组:

php
use Filament\Actions\Imports\ImportColumn;

public static 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() 方法覆盖:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->label('SKU')

要求 importer 列必须映射到 CSV 列

你可以调用 requiredMapping() 方法,要求某列必须映射到 CSV 中的列。数据库中必填的列也应要求映射:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->requiredMapping()

若数据库中该列为必填,还需要确保它有 rules(['required']) 验证规则

若某列未映射,则不会进行验证,因为没有可验证的数据。

若导入既允许创建记录也允许 更新已有记录,但仅在创建记录时要求映射某列(因为该字段必填),可使用 requiredMappingForNewRecordsOnly() 方法代替 requiredMapping()

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->requiredMappingForNewRecordsOnly()

resolveRecord() 方法返回尚未保存到数据库的模型实例,则仅对该行要求映射该列。若用户未映射该列,且导入中有一行在数据库中尚不存在,则仅该行会失败,并在分析完所有行后将消息加入失败行 CSV。

验证 CSV 数据

你可以调用 rules() 方法为列添加验证规则。这些规则会在保存到数据库前检查 CSV 中每一行的数据:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->rules(['required', 'max:32'])

未通过验证的行不会被导入。相反,它们会被编译成「失败行」的新 CSV,用户可在导入完成后下载。用户会看到每个失败行的验证错误列表。

TIP

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

转换状态

验证 之前,可以对 CSV 数据进行类型转换。这有助于将字符串转换为正确的数据类型,否则验证可能失败。例如,若 CSV 中有 price 列,你可能希望将其转换为 float:

php
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);
    })

TIP

除了 $state 之外,castStateUsing() 方法还允许你将各种实用工具作为参数注入该函数。

在此示例中,我们传入用于转换 $state 的函数。该函数会移除字符串中的非数字字符,转换为 float,并四舍五入到两位小数。

INFO

若某列并非 验证必填,且为空,则不会进行转换。

Filament 还附带一些内置转换方法:

php
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() 方法传入函数,在转换后变更状态:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('price')
    ->numeric()
    ->castStateUsing(function (float $state): ?float {
        if (blank($state)) {
            return null;
        }
    
        return round($state * 100);
    })

你甚至可以通过在函数中定义 $originalState 参数,访问转换前的原始状态:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('price')
    ->numeric()
    ->castStateUsing(function (float $state, mixed $originalState): ?float {
        // ...
    })

TIP

除了 $state 之外,castStateUsing() 方法还允许你将各种实用工具作为参数注入该函数。

处理单列中的多个值

你可以使用 multiple() 方法将列中的值转换为数组。它接受分隔符作为第一个参数,用于将该列中的值拆分为数组。例如,若 CSV 中有 documentation_urls 列,你可能希望将其转换为 URL 数组:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('documentation_urls')
    ->multiple(',')

TIP

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

在此示例中,我们传入逗号作为分隔符,因此列中的值会按逗号拆分并转换为数组。

转换数组中的每一项

若要将数组中的每一项转换为不同的数据类型,可以链式调用 内置转换方法

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('customer_ratings')
    ->multiple(',')
    ->integer() // Casts each item in the array to an integer.

验证数组中的每一项

若要验证数组中的每一项,可以链式调用 nestedRecursiveRules() 方法:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('customer_ratings')
    ->multiple(',')
    ->integer()
    ->rules(['array'])
    ->nestedRecursiveRules(['integer', 'min:1', 'max:5'])

TIP

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

导入关联关系

你可以使用 relationship() 方法导入关联关系。目前支持 BelongsToBelongsToMany 关系。例如,若 CSV 中有 category 列,你可能希望导入 category 的 BelongsTo 关系:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('author')
    ->relationship()

在此示例中,CSV 中的 author 列会映射到数据库中的 author_id 列。CSV 应包含作者的主键,通常是 id

若该列有值但找不到作者,导入将验证失败。Filament 会自动为所有关联列添加验证,以确保在必填时关联不为空。

若要导入 BelongsToMany 关系,请确保该列设置了 multiple(),并使用正确的值分隔符:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('authors')
    ->relationship()
    ->multiple(',')

自定义关联导入解析

若要通过其他列查找关联记录,可以将列名作为 resolveUsing 传入:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('author')
    ->relationship(resolveUsing: 'email')

你可以向 resolveUsing 传入多个列,它们会以「或」的方式用于查找作者。例如,若传入 ['email', 'username'],则可通过邮箱或用户名找到记录:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('author')
    ->relationship(resolveUsing: ['email', 'username'])

你还可以通过向 resolveUsing 传入函数来自定义解析过程,该函数应返回要与关联关联的记录:

php
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('author')
    ->relationship(resolveUsing: function (string $state): ?Author {
        return Author::query()
            ->where('email', $state)
            ->orWhere('username', $state)
            ->first();
    })

TIP

传给 resolveUsing 的函数允许你将各种实用工具作为参数注入。

若使用 BelongsToMany 关系,$state 将是数组,你应返回已解析的记录集合:

php
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;
use Illuminate\Database\Eloquent\Collection;

ImportColumn::make('authors')
    ->relationship(resolveUsing: function (array $state): Collection {
        return Author::query()
            ->whereIn('email', $state)
            ->orWhereIn('username', $state)
            ->get();
    })

你甚至可以用此函数动态决定用哪些列来解析记录:

php
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('author')
    ->relationship(resolveUsing: function (string $state): ?Author {
        if (filter_var($state, FILTER_VALIDATE_EMAIL)) {
            return 'email';
        }
    
        return 'username';
    })

将列数据标记为敏感

当导入行验证失败时,它们会被记录到数据库,以便在导入完成后导出。你可能希望从该日志中排除某些列,以避免以纯文本存储敏感数据。为此,可以在 ImportColumn 上使用 sensitive() 方法,阻止其数据被记录:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('ssn')
    ->label('Social security number')
    ->sensitive()
    ->rules(['required', 'digits:9'])

自定义如何将列填充到记录

若要自定义如何将列状态填充到记录中,可以向 fillRecordUsing() 方法传入函数:

php
use App\Models\Product;
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->fillRecordUsing(function (Product $record, string $state): void {
        $record->sku = strtoupper($state);
    })

TIP

传给 fillRecordUsing() 方法的函数允许你将各种实用工具作为参数注入。

在导入列下方添加帮助文本

有时,你可能希望在验证前为用户提供额外信息。可以通过向列添加 helperText() 实现,它会显示在映射选择框下方:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('skus')
    ->multiple(',')
    ->helperText('A comma-separated list of SKUs.')

导入时更新已有记录

生成 importer 类时,你会看到此 resolveRecord() 方法:

php
use App\Models\Product;

public function resolveRecord(): ?Product
{
    // return Product::firstOrNew([
    //     // Update existing records, matching them by `$this->data['column_name']`
    //     'email' => $this->data['email'],
    // ]);

    return new Product();
}

该方法会对 CSV 中的每一行调用,负责返回一个模型实例,该实例会用 CSV 数据填充并保存到数据库。默认情况下,它会为每一行创建新记录。不过,你可以自定义此行为以改为更新已有记录。例如,若产品已存在则更新,否则创建新记录。为此,可以取消注释 firstOrNew() 行,并传入要匹配的列名。对于产品,我们可能希望按 sku 列匹配:

php
use App\Models\Product;

public function resolveRecord(): ?Product
{
    return Product::firstOrNew([
        'sku' => $this->data['sku'],
    ]);
}

仅更新已有记录的导入

若要编写仅更新已有记录、不创建新记录的 importer,可以在找不到记录时返回 null

php
use App\Models\Product;

public function resolveRecord(): ?Product
{
    return Product::query()
        ->where('sku', $this->data['sku'])
        ->first();
}

若希望在找不到记录时使该导入行失败,可以抛出带消息的 RowImportFailedException

php
use App\Models\Product;
use Filament\Actions\Imports\Exceptions\RowImportFailedException;

public function resolveRecord(): ?Product
{
    $product = Product::query()
        ->where('sku', $this->data['sku'])
        ->first();

    if (! $product) {
        throw new RowImportFailedException("No product found with SKU [{$this->data['sku']}].");
    }

    return $product;
}

导入完成后,用户可以下载包含错误消息的失败行 CSV。

忽略导入列的空白状态

默认情况下,若 CSV 中某列为空、且已被用户映射、且验证不要求必填,则该列会以 null 导入数据库。若希望忽略空白状态并改用数据库中的现有值,可以调用 ignoreBlankState() 方法:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('price')
    ->ignoreBlankState()

使用导入选项

导入操作可以渲染用户在导入 CSV 时可交互的额外表单组件。这有助于让用户自定义 importer 的行为。例如,你可能希望用户选择导入时是更新已有记录,还是仅创建新记录。为此,可以在 importer 类的 getOptionsFormComponents() 方法中返回选项表单组件:

php
use Filament\Forms\Components\Checkbox;

public static function getOptionsFormComponents(): array
{
    return [
        Checkbox::make('updateExisting')
            ->label('Update existing records'),
    ];
}

或者,你可以通过操作上的 options() 方法向 importer 传入一组静态选项:

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->options([
        'updateExisting' => true,
    ])

现在,你可以在 importer 类中通过调用 $this->options 访问这些选项的数据。例如,你可能希望在 resolveRecord() 中用它来 更新已有产品

php
use App\Models\Product;

public function resolveRecord(): ?Product
{
    if ($this->options['updateExisting'] ?? false) {
        return Product::firstOrNew([
            'sku' => $this->data['sku'],
        ]);
    }

    return new Product();
}

改进导入列映射猜测

默认情况下,Filament 会尝试「猜测」CSV 中哪些列对应数据库中的哪些列,以节省用户时间。做法是尝试匹配列名的不同组合(含空格、-_),且不区分大小写。若希望改进猜测,可以调用 guess() 方法,并传入 CSV 中可能出现的更多列名示例:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->guess(['id', 'number', 'stock-keeping unit'])

提供示例 CSV 数据

在用户上传 CSV 之前,他们可以选择下载示例 CSV 文件,其中包含全部可导入列。这很有用,因为用户可以直接将该文件导入电子表格软件并填写。

你还可以向 CSV 添加示例行,向用户展示数据应是什么样子。要填充该示例行,可以向 example() 方法传入示例列值:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->example('ABC123')

若要添加多行示例,可以向 examples() 方法传入数组:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->examples(['ABC123', 'DEF456'])

默认情况下,示例 CSV 的表头使用列名。你可以使用 exampleHeader() 按列自定义表头:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('sku')
    ->exampleHeader('SKU')

使用自定义用户模型

默认情况下,imports 表有一个 user_id 列,该列约束到 users 表:

php
$table->foreignId('user_id')->constrained()->cascadeOnDelete();

Import 模型中,user() 关系定义为到 App\Models\User 模型的 BelongsTo 关系。若 App\Models\User 模型不存在,或你想使用其他模型,可以在服务提供者的 register() 方法中将新的 Authenticatable 模型绑定到容器:

php
use App\Models\Admin;
use Illuminate\Contracts\Auth\Authenticatable;

$this->app->bind(Authenticatable::class, Admin::class);

若你的可认证模型使用的不是 users 表,应将表名传给 constrained()

php
$table->foreignId('user_id')->constrained('admins')->cascadeOnDelete();

使用多态用户关系

若要将导入与多个用户模型关联,可以改用多态 MorphTo 关系。为此,需要替换 imports 表中的 user_id 列:

php
$table->morphs('user');

然后,在服务提供者的 boot() 方法中,应调用 Import::polymorphicUserRelationship(),将 Import 模型上的 user() 关系切换为 MorphTo 关系:

php
use Filament\Actions\Imports\Models\Import;

Import::polymorphicUserRelationship();

限制可导入的最大行数

为防止服务器过载,你可能希望限制单个 CSV 文件可导入的最大行数。可以在操作上调用 maxRows() 方法:

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->maxRows(100000)

TIP

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

更改导入分块大小

Filament 会将 CSV 分块,并在不同的队列任务中处理每个分块。默认每次分块为 100 行。你可以在操作上调用 chunkSize() 方法更改:

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->chunkSize(250)

TIP

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

TIP

若在导入大型 CSV 文件时遇到内存或超时问题,可以考虑减小分块大小。

更改 CSV 分隔符

CSV 的默认分隔符是逗号(,)。若导入使用其他分隔符,可以在操作上调用 csvDelimiter() 方法并传入新分隔符:

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->csvDelimiter(';')

TIP

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

只能指定单个字符,否则会抛出异常。

更改列标题偏移

若列标题不在 CSV 的第一行,可以在操作上调用 headerOffset() 方法,并传入要跳过的行数:

php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->headerOffset(5)

TIP

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

自定义完成通知

导入完成后,Filament 会向启动导入的用户发送通知。你可以通过在 importer 上覆盖 getCompletedNotificationTitle()getCompletedNotificationBody() 来自定义通知的标题和正文:

php
use Filament\Actions\Imports\Models\Import;

public static function getCompletedNotificationTitle(Import $import): string
{
    return 'Your product import has finished';
}

public static function getCompletedNotificationBody(Import $import): string
{
    return $import->successful_rows . ' products were imported.';
}

若要自定义标题和正文以外的内容——例如更改通知颜色、添加额外操作或替换图标——请覆盖 modifyCompletedNotification()。你可以修改传入的 Notification 并返回它,也可以构建并返回一个全新的通知:

php
use Filament\Actions\Action;
use Filament\Actions\Imports\Models\Import;
use Filament\Notifications\Notification;

public static function modifyCompletedNotification(Notification $notification, Import $import): Notification
{
    $notification->icon('heroicon-o-shopping-bag');

    if ($import->getOptions()['sendWelcomeEmails'] ?? false) {
        $notification->actions([
            ...$notification->getActions(),
            Action::make('viewWelcomeEmails')
                ->url(route('emails.sent')),
        ]);
    }

    return $notification;
}

Import 模型通过 $import->getColumnMap()$import->getOptions() 暴露用户选择的列映射和选项,因此你可以根据用户导入的内容定制通知。

自定义失败行的下载方式

默认情况下,失败行会编译成 CSV 并以流式响应返回。你可以通过覆盖 getFailedRowsDownloader() 方法,自定义 importer 的下载方式:

php
use App\Filament\Imports\Downloaders\CustomFailedRowsDownloader;
use Filament\Actions\Imports\Downloaders\Contracts\Downloader;

public static function getFailedRowsDownloader(): Downloader
{
    return app(CustomFailedRowsDownloader::class);
}

Downloader 是一个可调用类,接受 Import 模型并返回 Symfony Response。该响应可以流式下载、返回文件,或将用户重定向到远程文件系统上的临时 URL。

若自定义 downloader 仅更改生成内容的交付方式,可以使用 CsvImportFailureContentGenerator 将失败行写入 League CSV Writer。Filament 会从容器解析该类,以便你复用内置内容生成而无需重复实现。

自定义导入任务

处理导入的默认任务是 Filament\Actions\Imports\Jobs\ImportCsv。若要扩展该类并覆盖其任何方法,可以在服务提供者的 register() 方法中替换原始类:

php
use App\Jobs\ImportCsv;
use Filament\Actions\Imports\Jobs\ImportCsv as BaseImportCsv;

$this->app->bind(BaseImportCsv::class, ImportCsv::class);

或者,你可以将新的任务类传给操作上的 job() 方法,以自定义特定导入的任务:

php
use App\Filament\Imports\ProductImporter;
use App\Jobs\ImportCsv;
use Filament\Actions\ImportAction;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->job(ImportCsv::class)

自定义导入队列与连接

默认情况下,导入系统使用默认队列和连接。若要自定义某个 importer 的任务所用队列,可以在 importer 类中覆盖 getJobQueue() 方法:

php
public function getJobQueue(): ?string
{
    return 'imports';
}

你也可以通过在 importer 类中覆盖 getJobConnection() 方法,自定义某个 importer 的任务所用连接:

php
public function getJobConnection(): ?string
{
    return 'sqs';
}

自定义导入任务中间件

默认情况下,导入系统对每次导入一次只处理一个任务。这是为了防止服务器过载,以及避免其他任务被大型导入拖慢。该功能在 importer 类上的 WithoutOverlapping 中间件中定义:

php
use Illuminate\Queue\Middleware\WithoutOverlapping;

public function getJobMiddleware(): array
{
    return [
        (new WithoutOverlapping("import{$this->import->getKey()}"))->expireAfter(600),
    ];
}

若要自定义某个 importer 任务所应用的中间件,可以在 importer 类中覆盖此方法。关于任务中间件的更多信息,请参阅 Laravel 文档

自定义导入任务重试

默认情况下,导入系统会在 24 小时内重试任务,或直到因未处理异常失败 5 次(以先发生者为准)。这是为了让数据库不可用等临时问题有时间解决。你可以更改任务重试的时间段,该时间段在 importer 类的 getJobRetryUntil() 方法中定义:

php
use Carbon\CarbonInterface;

public function getJobRetryUntil(): ?CarbonInterface
{
    return now()->addHours(12);
}

关于任务重试的更多信息,请参阅 Laravel 文档

自定义导入任务退避策略

默认情况下,导入系统会在重试任务前等待 1 分钟、然后 2 分钟、然后 5 分钟、之后每次 10 分钟。这是为了防止反复失败的任务过载服务器。该功能在 importer 类的 getJobBackoff() 方法中定义:

php
/**
* @return int | array<int> | null
 */
public function getJobBackoff(): int | array | null
{
    return [60, 120, 300, 600];
}

关于任务退避(包括如何配置指数退避)的更多信息,请参阅 Laravel 文档

自定义导入任务标签

默认情况下,导入系统会用导入 ID 标记每个任务。这便于你轻松找到与某次导入相关的所有任务。该功能在 importer 类的 getJobTags() 方法中定义:

php
public function getJobTags(): array
{
    return ["import{$this->import->getKey()}"];
}

若要自定义某个 importer 任务所应用的标签,可以在 importer 类中覆盖此方法。

自定义导入任务批处理名称

默认情况下,导入系统不会为任务批处理定义任何名称。若要自定义某个 importer 的任务批处理名称,可以在 importer 类中覆盖 getJobBatchName() 方法:

php
public function getJobBatchName(): ?string
{
    return 'product-import';
}

自定义导入验证消息

导入系统会在导入前自动验证 CSV 文件。若有任何错误,会向用户显示错误列表,且不会处理导入。若要覆盖任何默认验证消息,可以在 importer 类上覆盖 getValidationMessages() 方法:

php
public function getValidationMessages(): array
{
    return [
        'name.required' => 'The name column must not be empty.',
    ];
}

要了解有关自定义验证消息的更多信息,请阅读 Laravel 文档

自定义导入验证属性

当列验证失败时,错误消息会使用其标签。若要自定义字段错误消息中使用的标签,请使用 validationAttribute() 方法:

php
use Filament\Actions\Imports\ImportColumn;

ImportColumn::make('name')
    ->validationAttribute('full name')

自定义导入文件验证

你可以使用 fileRules() 方法为导入文件添加新的 Laravel 验证规则

php
use Filament\Actions\ImportAction;
use Illuminate\Validation\Rules\File;

ImportAction::make()
    ->importer(ProductImporter::class)
    ->fileRules([
        'max:1024',
        // or
        File::types(['csv', 'txt'])->max(1024),
    ]),

TIP

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

生命周期钩子

可以使用钩子在 importer 生命周期的不同节点执行代码,例如在记录保存之前。要设置钩子,请在 importer 类上创建与钩子同名的受保护方法:

php
protected function beforeSave(): void
{
    // ...
}

在此示例中,beforeSave() 方法中的代码会在将已验证的 CSV 数据保存到数据库之前调用。

Importer 有若干可用钩子:

php
use Filament\Actions\Imports\Importer;

class ProductImporter extends Importer
{
    // ...

    protected function beforeValidate(): void
    {
        // Runs before the CSV data for a row is validated.
    }

    protected function afterValidate(): void
    {
        // Runs after the CSV data for a row is validated.
    }

    protected function beforeFill(): void
    {
        // Runs before the validated CSV data for a row is filled into a model instance.
    }

    protected function afterFill(): void
    {
        // Runs after the validated CSV data for a row is filled into a model instance.
    }

    protected function beforeSave(): void
    {
        // Runs before a record is saved to the database.
    }

    protected function beforeCreate(): void
    {
        // Similar to `beforeSave()`, but only runs when creating a new record.
    }

    protected function beforeUpdate(): void
    {
        // Similar to `beforeSave()`, but only runs when updating an existing record.
    }

    protected function afterSave(): void
    {
        // Runs after a record is saved to the database.
    }
    
    protected function afterCreate(): void
    {
        // Similar to `afterSave()`, but only runs when creating a new record.
    }
    
    protected function afterUpdate(): void
    {
        // Similar to `afterSave()`, but only runs when updating an existing record.
    }
}

在 trait 中定义生命周期钩子

若要在 trait 中定义生命周期钩子,请在钩子名称后加上 trait 名称作为后缀。这遵循 Eloquent 使用的 boot{TraitName}() 约定以及 Livewire 使用的 mount{TraitName}() 约定,使可复用的 trait 能接入 importer 生命周期,而不会与 importer 自身定义的钩子冲突:

php
use Filament\Actions\Imports\Importer;

trait LogsImports
{
    protected function afterSaveLogsImports(): void
    {
        // Runs after a record is saved to the database, in addition to the
        // hook on the importer.
    }
}

class ProductImporter extends Importer
{
    use LogsImports;

    protected function afterSave(): void
    {
        // Both lifecycle hooks are called.
    }
}

先调用 importer 自身的钩子,然后是每个 trait 钩子。被其他 trait 使用的 trait 中的钩子也会被调用。Trait 钩子会自动调用,因此你不应再从 importer 自身的钩子中调用它们。

在这些钩子中,你可以使用 $this->data 访问当前行的数据。还可以使用 $this->originalData 访问 CSV 中在 转换 或映射之前的原始行数据。

当前记录(若已存在)可在 $this->record 中访问,导入表单选项 可通过 $this->options 访问。

授权

默认情况下,仅启动导入的用户可以访问导入部分失败时生成的失败 CSV 文件。若要自定义授权逻辑,可以创建 ImportPolicy 类,并 AuthServiceProvider 中注册

php
use App\Policies\ImportPolicy;
use Filament\Actions\Imports\Models\Import;

protected $policies = [
    Import::class => ImportPolicy::class,
];

策略的 view() 方法将用于授权访问失败 CSV 文件。

请注意,若你定义了策略,原先「仅启动导入的用户可访问失败 CSV」的逻辑将被移除。若要保留该逻辑,需要将其加入你的策略:

php
use App\Models\User;
use Filament\Actions\Imports\Models\Import;

public function view(User $user, Import $import): bool
{
    return $import->user()->is($user);
}

安全性

按记录授权

导入系统在创建或更新记录时不会执行按记录的授权检查。CSV 中的每一行都会由 importer 的 resolveRecord()fillRecord()saveRecord() 方法处理,而不会咨询应用的 Laravel 策略。这意味着若用户被允许触发导入,他们就可以创建或更新 importer 支持的任何记录,无论他们是否通常有权通过应用 UI 这样做。

若导入时需要按记录授权,应在 importer 的 生命周期钩子(例如 beforeCreate()beforeUpdate())中添加检查,以根据记录授权当前用户。

DANGER

若应用允许不受信任的用户触发导入,你应实现按记录的授权检查,以防止未经授权的记录创建或修改。

CSV 公式注入

导入时行验证失败后,Filament 会将它们编译成可下载的 CSV 供用户审阅。该失败 CSV 包含上传文件中的原始数据,完全按提交时的样子,不做任何转换。若上传的 CSV 包含以 =+-@ 等字符开头的值,它们会原样出现在失败 CSV 中。在 Microsoft Excel 或 Google Sheets 等电子表格软件中打开时,这些值可能被解释为公式;若原始 CSV 来自不受信任的来源,这可能构成安全风险。你应确保用户在审阅失败 CSV 时了解此风险,或在 importer 的生命周期钩子中实现清理,在将潜在危险值存储为失败行之前将其中和。

或者,你可以选择启用 Filament 对失败 CSV 的内置保护。启用后,任何以公式触发字符(=+-@、制表符或回车符)开头的单元格都会加上单引号(')前缀,使电子表格软件将其视为纯文本。由于失败 CSV 包含上传文件的每一列——即使未映射到 ImportColumn 的列——此保护会应用于整个文件,而不是单个列。

若要为单个 importer 启用,请在 importer 类上设置 $shouldPreventFormulaInjection 属性:

php
use Filament\Actions\Imports\Importer;

class ProductImporter extends Importer
{
    protected static bool $shouldPreventFormulaInjection = true;
}

若要为应用中的每个 importer 启用,请在服务提供者的 boot() 方法中调用 preventFormulaInjection() 方法:

php
use Filament\Actions\Imports\Importer;

Importer::preventFormulaInjection();

WARNING

此保护为 选择性启用,默认关闭,因为失败 CSV 本意是供修正后重新上传。加上单引号前缀会改变合法数据——例如 -5 或电话号码 +44 1234 567890 都是有效的公式触发值,会被改写为 '-5'+44 1234 567890,随后导入时会保留前导引号。仅当上传文件可能来自不受信任的来源时再启用。