テーブルのエクスポート——CSV/Excel出力の実装
エクスポートの概要
Filamentのエクスポート機能(エピソード5でも触れましたが、このエピソードではより深く解説)は非同期Jobを使って大量データを効率的にエクスポートします。ユーザーがエクスポートを開始すると、バックグラウンドでJobが実行され、完了したら通知でダウンロードリンクが届きます。
Exporterクラスの詳細設計
php artisan make:filament-exporter Order
namespace App\Filament\Exports;
use App\Models\Order;
use Filament\Actions\Exports\ExportColumn;
use Filament\Actions\Exports\Exporter;
use Filament\Actions\Exports\Models\Export;
use Illuminate\Support\Carbon;
class OrderExporter extends Exporter
{
protected static ?string $model = Order::class;
public static function getColumns(): array
{
return [
ExportColumn::make('id')
->label('注文ID'),
ExportColumn::make('order_number')
->label('注文番号'),
ExportColumn::make('customer.name')
->label('顧客名'),
ExportColumn::make('customer.email')
->label('メールアドレス'),
ExportColumn::make('status')
->label('ステータス')
->formatStateUsing(fn (string $state): string => match($state) {
'pending' => '処理待ち',
'processing' => '処理中',
'shipped' => '発送済み',
'delivered' => '配達完了',
'cancelled' => 'キャンセル',
default => $state,
}),
ExportColumn::make('total_amount')
->label('合計金額')
->formatStateUsing(fn (int $state): string =>
'¥' . number_format($state)
),
ExportColumn::make('items_count')
->label('商品点数')
->counts('orderItems'),
ExportColumn::make('created_at')
->label('注文日時')
->formatStateUsing(fn (?Carbon $state): string =>
$state?->format('Y/m/d H:i:s') ?? ''
),
ExportColumn::make('shipped_at')
->label('発送日時')
->formatStateUsing(fn (?Carbon $state): string =>
$state?->format('Y/m/d H:i:s') ?? ''
),
// 計算値カラム
ExportColumn::make('processing_days')
->label('処理日数')
->state(fn (Order $record): string => $record->shipped_at
? $record->created_at->diffInDays($record->shipped_at) . '日'
: '-'
),
];
}
public static function getCompletedNotificationBody(Export $export): string
{
$body = "注文データのエクスポートが完了しました。\n";
$body .= "{$export->successful_rows}件エクスポートしました。";
if ($failedRowsCount = $export->getFailedRowsCount()) {
$body .= "\n({$failedRowsCount}件の処理に失敗しました)";
}
return $body;
}
}
ユーザーが選択できるカラム
->enabledByDefault(false)でデフォルトでは選択されない(オプション)カラムを定義できます。
public static function getColumns(): array
{
return [
ExportColumn::make('id')->label('ID'),
ExportColumn::make('order_number')->label('注文番号'),
ExportColumn::make('customer.name')->label('顧客名'),
// デフォルトでは非表示(ユーザーが選択した場合のみ出力)
ExportColumn::make('customer.phone')
->label('顧客電話番号')
->enabledByDefault(false),
ExportColumn::make('shipping_address')
->label('配送先住所')
->enabledByDefault(false),
ExportColumn::make('internal_notes')
->label('社内メモ')
->enabledByDefault(false),
];
}
ExportActionのカスタマイズ
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Enums\ExportFormat;
protected function getHeaderActions(): array
{
return [
ExportAction::make()
->exporter(OrderExporter::class)
->label('エクスポート')
->icon('heroicon-o-arrow-down-tray')
->formats([
ExportFormat::Csv,
ExportFormat::Xlsx,
])
->fileName(fn (): string =>
'orders-' . now()->format('Y-m-d_H-i-s')
)
->chunkSize(200) // 1Jobあたりの処理件数
->maxRows(50000) // エクスポート上限
];
}
フィルタされたデータのエクスポート
テーブルに適用されたフィルタをエクスポートにも反映させる場合、テーブルのヘッダーアクションとしてExportActionを追加します。
public function table(Table $table): Table
{
return $table
->filters([
SelectFilter::make('status')
->options([
'pending' => '処理待ち',
'shipped' => '発送済み',
]),
Filter::make('date_range')
->form([
DatePicker::make('from')->label('開始日'),
DatePicker::make('until')->label('終了日'),
])
->query(fn (Builder $query, array $data): Builder =>
$query
->when($data['from'], fn ($q, $v) => $q->whereDate('created_at', '>=', $v))
->when($data['until'], fn ($q, $v) => $q->whereDate('created_at', '<=', $v))
),
])
->headerActions([
ExportAction::make()
->exporter(OrderExporter::class)
// テーブルのフィルタが自動的に適用される
]);
}
テーブルの->headerActions()にExportActionを置くと、現在のフィルタ・検索条件が自動的にエクスポートに適用されます。
エクスポート用の専用クエリ
class OrderExporter extends Exporter
{
public static function modifyQuery(Builder $query): Builder
{
// Eager Loadingを追加(N+1問題防止)
return $query->with(['customer', 'orderItems', 'shippingAddress'])
->withCount('orderItems');
}
}
XLSXのセルスタイル設定
XLSXエクスポートでは、maatwebsite/excelパッケージを使ったスタイル設定が可能です。
// 数値カラムの書式設定
ExportColumn::make('total_amount')
->label('合計金額')
->formatStateUsing(fn (int $state): int => $state)
// XLSXでは数値として出力(Excelで集計可能)
より高度なXLSXカスタマイズ(セルの背景色・ヘッダーのスタイルなど)が必要な場合は、Exporterのafter処理でOpenSpoutなどを使ったカスタム実装が必要になります。
エクスポートジョブのキュー設定
本番環境では専用のキューを設定してエクスポートJobが管理画面のレスポンスに影響しないようにします。
# .env
FILAMENT_EXPORT_QUEUE=exports
// app/Filament/Exports/OrderExporter.php
public static function getJobQueue(): ?string
{
return 'exports'; // 専用キュー
}
# キューワーカーの起動
php artisan queue:work --queue=exports
コツ・注意点・ハマりポイント
コツ: ExportColumn::make()->counts('relation')でリレーションのカウントをエクスポートできます。ただしN+1を防ぐため、modifyQuery()でwithCount()を追加することをセットで行ってください。
注意点: エクスポートファイルはstorage/app/filament-exports/に保存されます。本番環境では定期的なクリーンアップが必要です。Filamentが提供するfilament:prune-exportsコマンドをCronで実行してください。
# 7日以上前のエクスポートファイルを削除
php artisan filament:prune-exports --days=7
ハマりポイント: XLSXエクスポートで日本語(マルチバイト文字)が文字化けする場合は、ファイルのエンコーディング設定を確認してください。CSVの場合はBOM付きUTF-8にすることでExcelでの文字化けを防げます。カスタムエクスポータで--bomオプションを付けることで対処できます。
まとめ
FilamentのExportActionとExporterクラスを使えば、フォーマット変換・非同期処理・ユーザーによるカラム選択まで含めた本格的なエクスポート機能が実装できます。テーブルフィルタとの連携でフィルタされたデータのみエクスポートする設計が現場では最も使いやすいパターンです。