Excelデータインポートの分離設計:PhpSpreadsheetとメッセージキューの活用

大規模データの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);
});

タグ: php-spreadsheet message-queue data-processing

9月7日 23:24 投稿