大規模データのExcelインポート処理では、単一のリクエストでデータを直接DBに挿入するとシステムが重くなり、メンテナンス性が低下します。この問題を解決するため、インポート処理とビジネスロジックを分離する設計を採用します。
基本的なアプローチは以下の通りです:
- インポートAPIはアップロードされたExcelデータをメッセージキューに登録するのみ
- 別システムのコンシューマーがキューからデータを取得し、DB処理を実行
- インポート処理の遅延が他の機能に影響しない設計を実現
実装例として、PhpSpreadsheetライブラリを用いたデータ処理を示します。
1. ライブラリインストール
composer require phpoffice/phpspreadsheet
2. Excel処理ユーティリティクラス
namespace app\utils;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class DataProcessor
{
public function exportToExcel(array $rowData, array $headerMap, string $fileName = 'export_data'): string
{
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// ヘッダー設定
foreach ($headerMap as $colIndex => $label) {
$sheet->setCellValue($colIndex . '1', $label);
}
// データ行追加
$startRow = 2;
foreach ($rowData as $rowIdx => $row) {
foreach ($headerMap as $colIndex => $field) {
$sheet->setCellValue(
$colIndex . ($startRow + $rowIdx),
$row[$field] ?? ''
);
}
}
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$outputPath = './exports/' . $fileName . '_' . date('YmdHis') . '.xlsx';
$writer->save($outputPath);
return substr($outputPath, 1);
}
public function processUploadedFile(string $filePath, int $sheetIndex = 0): array
{
$reader = IOFactory::createReaderForFile($filePath);
$spreadsheet = $reader->load($filePath);
$sheet = $spreadsheet->getSheet($sheetIndex);
$maxColumn = Coordinate::columnIndexFromString($sheet->getHighestColumn());
$maxRow = $sheet->getHighestRow();
$processedData = [];
for ($row = 1; $row <= $maxRow; $row++) {
$rowData = [];
for ($col = 1; $col <= $maxColumn; $col++) {
$cell = $sheet->getCellByColumnAndRow($col, $row);
$rowData[Coordinate::stringFromColumnIndex($col)] = $cell->getValue();
}
$processedData[] = $rowData;
}
return $processedData;
}
}
3. インポートAPI実装
// インポートリクエスト処理
public function handleImport()
{
$uploadedFile = $_FILES['file']['tmp_name'];
$config = config('excel.mapping.bidding');
$processor = new DataProcessor();
$excelData = $processor->processUploadedFile($uploadedFile);
// フィールドマッピング処理
$mappedData = $this->mapExcelColumns($excelData, $config);
// メッセージキューへ登録
$queue = new MessageBroker(config('queue.host'));
$queue->enqueue('excel_import', json_encode([
'batch_id' => uniqid(),
'data' => $mappedData
]));
return response()->json(['status' => 'success']);
}
private function mapExcelColumns(array $rows, array $fieldMap): array
{
$header = array_shift($rows);
$mapped = [];
foreach ($rows as $row) {
$rowData = [];
foreach ($fieldMap as $targetField => $sourceLabel) {
$colIndex = array_search($sourceLabel, $header);
$rowData[$targetField] = $row[$colIndex] ?? null;
}
$mapped[] = $rowData;
}
return $mapped;
}
4. メッセージキュー処理例
// 別プロセスで実行されるコンシューマー
$queue = new MessageBroker(config('queue.host'));
$queue->consume('excel_import', function($message) {
$payload = json_decode($message, true);
$data = $payload['data'];
// ビジネスロジック処理
foreach ($data as $item) {
$this->saveToDatabase($item);
}
$queue->ack($message);
});